aboutsummaryrefslogtreecommitdiff
path: root/src/mm-broadband-modem.c
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:30 +0100
committerGuido Günther <agx@sigxcpu.org>2014-02-05 08:38:30 +0100
commit13ed135b9ae78c692dc359976eb8b54d0a3629b8 (patch)
treeae2ea713ad51d73980cf83db1411d6589dac5e8b /src/mm-broadband-modem.c
parent14d771b90f5a7d3887e5e900d1fb4737477ad305 (diff)
Imported Upstream version 0.7.991upstream/0.7.991
Diffstat (limited to 'src/mm-broadband-modem.c')
-rw-r--r--src/mm-broadband-modem.c9760
1 files changed, 9760 insertions, 0 deletions
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
new file mode 100644
index 0000000..13fc377
--- /dev/null
+++ b/src/mm-broadband-modem.c
@@ -0,0 +1,9760 @@
+/* -*- 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>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-modem-at.h"
+#include "mm-broadband-modem.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-3gpp-ussd.h"
+#include "mm-iface-modem-cdma.h"
+#include "mm-iface-modem-simple.h"
+#include "mm-iface-modem-location.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-iface-modem-time.h"
+#include "mm-iface-modem-firmware.h"
+#include "mm-broadband-bearer.h"
+#include "mm-bearer-list.h"
+#include "mm-sms-list.h"
+#include "mm-sim.h"
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+#include "mm-error-helpers.h"
+#include "mm-qcdm-serial-port.h"
+#include "libqcdm/src/errors.h"
+#include "libqcdm/src/commands.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface);
+static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
+static void iface_modem_simple_init (MMIfaceModemSimple *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
+static void iface_modem_time_init (MMIfaceModemTime *iface);
+static void iface_modem_firmware_init (MMIfaceModemFirmware *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModem, mm_broadband_modem, MM_TYPE_BASE_MODEM, 0,
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIMPLE, iface_modem_simple_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init))
+
+enum {
+ PROP_0,
+ PROP_MODEM_DBUS_SKELETON,
+ PROP_MODEM_3GPP_DBUS_SKELETON,
+ PROP_MODEM_3GPP_USSD_DBUS_SKELETON,
+ PROP_MODEM_CDMA_DBUS_SKELETON,
+ PROP_MODEM_SIMPLE_DBUS_SKELETON,
+ PROP_MODEM_LOCATION_DBUS_SKELETON,
+ PROP_MODEM_MESSAGING_DBUS_SKELETON,
+ PROP_MODEM_TIME_DBUS_SKELETON,
+ PROP_MODEM_FIRMWARE_DBUS_SKELETON,
+ PROP_MODEM_SIM,
+ PROP_MODEM_BEARER_LIST,
+ PROP_MODEM_STATE,
+ PROP_MODEM_3GPP_REGISTRATION_STATE,
+ PROP_MODEM_3GPP_CS_NETWORK_SUPPORTED,
+ PROP_MODEM_3GPP_PS_NETWORK_SUPPORTED,
+ PROP_MODEM_3GPP_EPS_NETWORK_SUPPORTED,
+ PROP_MODEM_3GPP_IGNORED_FACILITY_LOCKS,
+ PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE,
+ PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE,
+ PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED,
+ PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED,
+ PROP_MODEM_MESSAGING_SMS_LIST,
+ PROP_MODEM_MESSAGING_SMS_PDU_MODE,
+ PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE,
+ PROP_MODEM_SIMPLE_STATUS,
+ PROP_LAST
+};
+
+/* When CIND is supported, invalid indicators are marked with this value */
+#define CIND_INDICATOR_INVALID 255
+#define CIND_INDICATOR_IS_VALID(u) (u != CIND_INDICATOR_INVALID)
+
+typedef struct _PortsContext PortsContext;
+
+struct _MMBroadbandModemPrivate {
+ /* Broadband modem specific implementation */
+ PortsContext *enabled_ports_ctx;
+ gboolean modem_init_run;
+
+ /*<--- Modem interface --->*/
+ /* Properties */
+ GObject *modem_dbus_skeleton;
+ MMSim *modem_sim;
+ MMBearerList *modem_bearer_list;
+ MMModemState modem_state;
+ /* Implementation helpers */
+ MMModemCharset modem_current_charset;
+ gboolean modem_cind_support_checked;
+ gboolean modem_cind_supported;
+ guint modem_cind_indicator_signal_quality;
+ guint modem_cind_min_signal_quality;
+ guint modem_cind_max_signal_quality;
+ guint modem_cind_indicator_roaming;
+ guint modem_cind_indicator_service;
+
+ /*<--- Modem 3GPP interface --->*/
+ /* Properties */
+ GObject *modem_3gpp_dbus_skeleton;
+ MMModem3gppRegistrationState modem_3gpp_registration_state;
+ gboolean modem_3gpp_cs_network_supported;
+ gboolean modem_3gpp_ps_network_supported;
+ gboolean modem_3gpp_eps_network_supported;
+ /* Implementation helpers */
+ GPtrArray *modem_3gpp_registration_regex;
+ MMModem3gppFacility modem_3gpp_ignored_facility_locks;
+
+ /*<--- Modem 3GPP USSD interface --->*/
+ /* Properties */
+ GObject *modem_3gpp_ussd_dbus_skeleton;
+ /* Implementation helpers */
+ gboolean use_unencoded_ussd;
+ GSimpleAsyncResult *pending_ussd_action;
+
+ /*<--- Modem CDMA interface --->*/
+ /* Properties */
+ GObject *modem_cdma_dbus_skeleton;
+ MMModemCdmaRegistrationState modem_cdma_cdma1x_registration_state;
+ MMModemCdmaRegistrationState modem_cdma_evdo_registration_state;
+ gboolean modem_cdma_cdma1x_network_supported;
+ gboolean modem_cdma_evdo_network_supported;
+ GCancellable *modem_cdma_pending_registration_cancellable;
+ /* Implementation helpers */
+ gboolean checked_sprint_support;
+ gboolean has_spservice;
+ gboolean has_speri;
+
+ /*<--- Modem Simple interface --->*/
+ /* Properties */
+ GObject *modem_simple_dbus_skeleton;
+ MMSimpleStatus *modem_simple_status;
+
+ /*<--- Modem Location interface --->*/
+ /* Properties */
+ GObject *modem_location_dbus_skeleton;
+
+ /*<--- Modem Messaging interface --->*/
+ /* Properties */
+ GObject *modem_messaging_dbus_skeleton;
+ MMSmsList *modem_messaging_sms_list;
+ gboolean modem_messaging_sms_pdu_mode;
+ MMSmsStorage modem_messaging_sms_default_storage;
+ /* Implementation helpers */
+ gboolean sms_supported_modes_checked;
+ gboolean mem1_storage_locked;
+ MMSmsStorage current_sms_mem1_storage;
+ gboolean mem2_storage_locked;
+ MMSmsStorage current_sms_mem2_storage;
+
+
+ /*<--- Modem Time interface --->*/
+ /* Properties */
+ GObject *modem_time_dbus_skeleton;
+
+ /*<--- Modem Firmware interface --->*/
+ /* Properties */
+ GObject *modem_firmware_dbus_skeleton;
+};
+
+/*****************************************************************************/
+
+static gboolean
+response_processor_string_ignore_at_errors (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ if (error) {
+ /* Ignore AT errors (ie, ERROR or CMx ERROR) */
+ if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command)
+ *result_error = g_error_copy (error);
+
+ return FALSE;
+ }
+
+ *result = g_variant_new_string (response);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMBearer *bearer;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return NULL;
+
+ bearer = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ mm_dbg ("New bearer created at DBus path '%s'", mm_bearer_get_path (bearer));
+
+ return g_object_ref (bearer);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ MMBearer *bearer = NULL;
+ GError *error = NULL;
+
+ bearer = mm_broadband_bearer_new_finish (res, &error);
+ if (!bearer)
+ g_simple_async_result_take_error (simple, error);
+ else
+ g_simple_async_result_set_op_res_gpointer (simple,
+ bearer,
+ (GDestroyNotify)g_object_unref);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_create_bearer (MMIfaceModem *self,
+ MMBearerProperties *properties,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ /* Set a new ref to the bearer object as result */
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_create_bearer);
+
+ /* We just create a MMBroadbandBearer */
+ mm_dbg ("Creating Broadband bearer in broadband modem");
+ mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+ properties,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)broadband_bearer_new_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Create SIM (Modem inteface) */
+
+static MMSim *
+modem_create_sim_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_sim_new_finish (res, error);
+}
+
+static void
+modem_create_sim (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* New generic SIM */
+ mm_sim_new (MM_BASE_MODEM (self),
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Capabilities loading (Modem interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMModemCapability caps;
+ MMQcdmSerialPort *qcdm_port;
+} LoadCapabilitiesContext;
+
+static void
+load_capabilities_context_complete_and_free (LoadCapabilitiesContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ if (ctx->qcdm_port) {
+ mm_serial_port_close (MM_SERIAL_PORT (ctx->qcdm_port));
+ g_object_unref (ctx->qcdm_port);
+ }
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_slice_free (LoadCapabilitiesContext, ctx);
+}
+
+static MMModemCapability
+modem_load_current_capabilities_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ MMModemCapability caps;
+ gchar *caps_str;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return MM_MODEM_CAPABILITY_NONE;
+
+ caps = (MMModemCapability) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
+ caps_str = mm_modem_capability_build_string_from_mask (caps);
+ mm_dbg ("loaded current capabilities: %s", caps_str);
+ g_free (caps_str);
+ return caps;
+}
+
+static void
+current_capabilities_ws46_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ LoadCapabilitiesContext *ctx)
+{
+ const gchar *response;
+
+ /* Completely ignore errors in AT+WS46=? */
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (response &&
+ (strstr (response, "28") != NULL || /* 4G only */
+ strstr (response, "30") != NULL || /* 2G/4G */
+ strstr (response, "31") != NULL)) { /* 3G/4G */
+ /* Add LTE caps */
+ ctx->caps |= MM_MODEM_CAPABILITY_LTE;
+ }
+
+ g_simple_async_result_set_op_res_gpointer (
+ ctx->result,
+ GUINT_TO_POINTER (ctx->caps),
+ NULL);
+ load_capabilities_context_complete_and_free (ctx);
+}
+
+typedef struct {
+ gchar *name;
+ MMModemCapability bits;
+} ModemCaps;
+
+static const ModemCaps modem_caps[] = {
+ { "+CGSM", MM_MODEM_CAPABILITY_GSM_UMTS },
+ { "+CLTE2", MM_MODEM_CAPABILITY_LTE }, /* Novatel */
+ { "+CLTE1", MM_MODEM_CAPABILITY_LTE }, /* Novatel */
+ { "+CLTE", MM_MODEM_CAPABILITY_LTE },
+ { "+CIS707-A", MM_MODEM_CAPABILITY_CDMA_EVDO },
+ { "+CIS707A", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Cmotech */
+ { "+CIS707", MM_MODEM_CAPABILITY_CDMA_EVDO },
+ { "CIS707", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Qualcomm Gobi */
+ { "+CIS707P", MM_MODEM_CAPABILITY_CDMA_EVDO },
+ { "CIS-856", MM_MODEM_CAPABILITY_CDMA_EVDO },
+ { "+IS-856", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Cmotech */
+ { "CIS-856-A", MM_MODEM_CAPABILITY_CDMA_EVDO },
+ { "CIS-856A", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Kyocera KPC680 */
+ { "+WIRIDIUM", MM_MODEM_CAPABILITY_IRIDIUM }, /* Iridium satellite modems */
+ { "CDMA 1x", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Huawei Data07, ATI reply */
+ /* TODO: FCLASS, MS, ES, DS? */
+ { NULL }
+};
+
+static gboolean
+parse_caps_gcap (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **variant,
+ GError **result_error)
+{
+ const ModemCaps *cap = modem_caps;
+ guint32 ret = 0;
+
+ if (!response)
+ return FALSE;
+
+ /* Some modems (Huawei E160g) won't respond to +GCAP with no SIM, but
+ * will respond to ATI. Ignore the error and continue.
+ */
+ if (strstr (response, "+CME ERROR:"))
+ return FALSE;
+
+ while (cap->name) {
+ if (strstr (response, cap->name))
+ ret |= cap->bits;
+ cap++;
+ }
+
+ /* No result built? */
+ if (ret == 0)
+ return FALSE;
+
+ *variant = g_variant_new_uint32 (ret);
+ return TRUE;
+}
+
+static gboolean
+parse_caps_cpin (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ if (!response)
+ return FALSE;
+
+ if (strcasestr (response, "SIM PIN") ||
+ strcasestr (response, "SIM PUK") ||
+ strcasestr (response, "PH-SIM PIN") ||
+ strcasestr (response, "PH-FSIM PIN") ||
+ strcasestr (response, "PH-FSIM PUK") ||
+ strcasestr (response, "SIM PIN2") ||
+ strcasestr (response, "SIM PUK2") ||
+ strcasestr (response, "PH-NET PIN") ||
+ strcasestr (response, "PH-NET PUK") ||
+ strcasestr (response, "PH-NETSUB PIN") ||
+ strcasestr (response, "PH-NETSUB PUK") ||
+ strcasestr (response, "PH-SP PIN") ||
+ strcasestr (response, "PH-SP PUK") ||
+ strcasestr (response, "PH-CORP PIN") ||
+ strcasestr (response, "PH-CORP PUK") ||
+ strcasestr (response, "READY")) {
+ /* At least, it's a GSM modem */
+ *result = g_variant_new_uint32 (MM_MODEM_CAPABILITY_GSM_UMTS);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+parse_caps_cgmm (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ if (!response)
+ return FALSE;
+
+ /* This check detects some really old Motorola GPRS dongles and phones */
+ if (strstr (response, "GSM900") ||
+ strstr (response, "GSM1800") ||
+ strstr (response, "GSM1900") ||
+ strstr (response, "GSM850")) {
+ /* At least, it's a GSM modem */
+ *result = g_variant_new_uint32 (MM_MODEM_CAPABILITY_GSM_UMTS);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static const MMBaseModemAtCommand capabilities[] = {
+ { "+GCAP", 2, TRUE, parse_caps_gcap },
+ { "I", 1, TRUE, parse_caps_gcap }, /* yes, really parse as +GCAP */
+ { "+CPIN?", 1, FALSE, parse_caps_cpin },
+ { "+CGMM", 1, TRUE, parse_caps_cgmm },
+ { NULL }
+};
+
+static void
+capabilities_sequence_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ LoadCapabilitiesContext *ctx)
+{
+ GError *error = NULL;
+ GVariant *result;
+
+ result = mm_base_modem_at_sequence_finish (self, res, NULL, &error);
+ if (!result) {
+ if (error)
+ g_simple_async_result_take_error (ctx->result, error);
+ else {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%s",
+ "Failed to determine modem capabilities.");
+ }
+ load_capabilities_context_complete_and_free (ctx);
+ return;
+ }
+
+ ctx->caps = (MMModemCapability)g_variant_get_uint32 (result);
+
+ /* Some modems (e.g. Sierra Wireless MC7710 or ZTE MF820D) won't report LTE
+ * capabilities even if they have them. So just run AT+WS46=? as well to see
+ * if the current supported modes includes any LTE-specific mode.
+ * This is not a big deal, as the AT+WS46=? command is a test command with a
+ * cache-able result.
+ *
+ * E.g.:
+ * AT+WS46=?
+ * +WS46: (12,22,25,28,29)
+ * OK
+ *
+ */
+ if (ctx->caps & MM_MODEM_CAPABILITY_GSM_UMTS &&
+ !(ctx->caps & MM_MODEM_CAPABILITY_LTE)) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (ctx->self),
+ "+WS46=?",
+ 3,
+ TRUE, /* allow caching, it's a test command */
+ (GAsyncReadyCallback)current_capabilities_ws46_test_ready,
+ ctx);
+ return;
+ }
+
+ /* Otherwise, just set the already retrieved capabilities */
+ g_simple_async_result_set_op_res_gpointer (
+ ctx->result,
+ GUINT_TO_POINTER (ctx->caps),
+ NULL);
+ load_capabilities_context_complete_and_free (ctx);
+}
+
+static void
+load_current_capabilities_at (LoadCapabilitiesContext *ctx)
+{
+ /* Launch sequence, we will expect a "u" GVariant */
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (ctx->self),
+ capabilities,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ (GAsyncReadyCallback)capabilities_sequence_ready,
+ ctx);
+}
+
+static void
+mode_pref_qcdm_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ LoadCapabilitiesContext *ctx)
+{
+ QcdmResult *result;
+ gint err = QCDM_SUCCESS;
+ u_int8_t pref = 0;
+
+ if (error) {
+ /* Fall back to AT checking */
+ mm_dbg ("Failed to load NV ModePref: %s", error->message);
+ goto at_caps;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_nv_get_mode_pref_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (!result) {
+ mm_dbg ("Failed to parse NV ModePref result: %d", err);
+ goto at_caps;
+ }
+
+ err = qcdm_result_get_u8 (result, QCDM_CMD_NV_GET_MODE_PREF_ITEM_MODE_PREF, &pref);
+ if (err) {
+ mm_dbg ("Failed to read NV ModePref: %d", err);
+ goto at_caps;
+ }
+
+ /* Only parse explicit modes; for 'auto' just fall back to whatever
+ * the AT current capabilities probing figures out.
+ */
+ switch (pref) {
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_HDR_LTE_ONLY:
+ ctx->caps |= MM_MODEM_CAPABILITY_LTE;
+ /* Fall through */
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_ONLY:
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_HDR_ONLY:
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_HDR_ONLY:
+ ctx->caps |= MM_MODEM_CAPABILITY_CDMA_EVDO;
+ break;
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_GSM_UMTS_LTE_ONLY:
+ ctx->caps |= MM_MODEM_CAPABILITY_LTE;
+ /* Fall through */
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_GPRS_ONLY:
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_UMTS_ONLY:
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_GSM_UMTS_ONLY:
+ ctx->caps |= MM_MODEM_CAPABILITY_GSM_UMTS;
+ break;
+ case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_LTE_ONLY:
+ ctx->caps |= MM_MODEM_CAPABILITY_LTE;
+ break;
+ default:
+ break;
+ }
+
+ if (ctx->caps != MM_MODEM_CAPABILITY_NONE) {
+ g_simple_async_result_set_op_res_gpointer (
+ ctx->result,
+ GUINT_TO_POINTER (ctx->caps),
+ NULL);
+ load_capabilities_context_complete_and_free (ctx);
+ return;
+ }
+
+at_caps:
+ load_current_capabilities_at (ctx);
+}
+
+static void
+load_current_capabilities_qcdm (LoadCapabilitiesContext *ctx)
+{
+ GByteArray *cmd;
+ GError *error = NULL;
+
+ ctx->qcdm_port = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (ctx->self));
+ g_assert (ctx->qcdm_port);
+
+ if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->qcdm_port), &error)) {
+ mm_dbg ("Failed to open QCDM port for NV ModePref request: %s",
+ error->message);
+ g_error_free (error);
+ ctx->qcdm_port = NULL;
+ load_current_capabilities_at (ctx);
+ return;
+ }
+
+ g_object_ref (ctx->qcdm_port);
+
+ cmd = g_byte_array_sized_new (300);
+ cmd->len = qcdm_cmd_nv_get_mode_pref_new ((char *) cmd->data, 300, 0);
+ g_assert (cmd->len);
+
+ mm_qcdm_serial_port_queue_command (ctx->qcdm_port,
+ cmd,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn) mode_pref_qcdm_ready,
+ ctx);
+}
+
+static void
+modem_load_current_capabilities (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LoadCapabilitiesContext *ctx;
+
+ mm_dbg ("loading current capabilities...");
+
+ ctx = g_slice_new0 (LoadCapabilitiesContext);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_current_capabilities);
+
+ if (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self)))
+ load_current_capabilities_qcdm (ctx);
+ else
+ load_current_capabilities_at (ctx);
+}
+
+/*****************************************************************************/
+/* Manufacturer loading (Modem interface) */
+
+static gchar *
+sanitize_info_reply (GVariant *v, const char *prefix)
+{
+ const gchar *reply, *p;
+ gchar *sanitized;
+
+ /* Strip any leading command reply */
+ reply = g_variant_get_string (v, NULL);
+ p = strstr (reply, prefix);
+ if (p)
+ reply = p + strlen (prefix);
+ sanitized = g_strdup (reply);
+ return mm_strip_quotes (g_strstrip (sanitized));
+}
+
+static gchar *
+modem_load_manufacturer_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GVariant *result;
+ gchar *manufacturer = NULL;
+
+ result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
+ if (result) {
+ manufacturer = sanitize_info_reply (result, "GMI:");
+ mm_dbg ("loaded manufacturer: %s", manufacturer);
+ }
+ return manufacturer;
+}
+
+static const MMBaseModemAtCommand manufacturers[] = {
+ { "+CGMI", 3, TRUE, response_processor_string_ignore_at_errors },
+ { "+GMI", 3, TRUE, response_processor_string_ignore_at_errors },
+ { NULL }
+};
+
+static void
+modem_load_manufacturer (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading manufacturer...");
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ manufacturers,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Model loading (Modem interface) */
+
+static gchar *
+modem_load_model_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GVariant *result;
+ gchar *model = NULL;
+
+ result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
+ if (result) {
+ model = sanitize_info_reply (result, "GMM:");
+ mm_dbg ("loaded model: %s", model);
+ }
+ return model;
+}
+
+static const MMBaseModemAtCommand models[] = {
+ { "+CGMM", 3, TRUE, response_processor_string_ignore_at_errors },
+ { "+GMM", 3, TRUE, response_processor_string_ignore_at_errors },
+ { NULL }
+};
+
+static void
+modem_load_model (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading model...");
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ models,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Revision loading */
+
+static gchar *
+modem_load_revision_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GVariant *result;
+ gchar *revision = NULL;
+
+ result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
+ if (result) {
+ revision = sanitize_info_reply (result, "GMR:");
+ mm_dbg ("loaded revision: %s", revision);
+ }
+ return revision;
+}
+
+static const MMBaseModemAtCommand revisions[] = {
+ { "+CGMR", 3, TRUE, response_processor_string_ignore_at_errors },
+ { "+GMR", 3, TRUE, response_processor_string_ignore_at_errors },
+ { NULL }
+};
+
+static void
+modem_load_revision (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading revision...");
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ revisions,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Equipment ID loading (Modem interface) */
+
+static gchar *
+modem_load_equipment_identifier_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GVariant *result;
+ gchar *equip_id = NULL, *esn = NULL, *meid = NULL, *imei = NULL;
+
+ result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
+ if (result) {
+ equip_id = sanitize_info_reply (result, "GSN:");
+
+ /* Modems put all sorts of things into the GSN response; sanitize it */
+ if (mm_parse_gsn (equip_id, &imei, &meid, &esn)) {
+ g_free (equip_id);
+
+ if (imei)
+ equip_id = g_strdup (imei);
+ else if (meid)
+ equip_id = g_strdup (meid);
+ else if (esn)
+ equip_id = g_strdup (esn);
+ g_free (esn);
+ g_free (meid);
+ g_free (imei);
+
+ g_assert (equip_id);
+ } else {
+ /* Leave whatever the modem returned alone */
+ }
+ mm_dbg ("loaded equipment identifier: %s", equip_id);
+ }
+ return equip_id;
+}
+
+static const MMBaseModemAtCommand equipment_identifiers[] = {
+ { "+CGSN", 3, TRUE, response_processor_string_ignore_at_errors },
+ { "+GSN", 3, TRUE, response_processor_string_ignore_at_errors },
+ { NULL }
+};
+
+static void
+modem_load_equipment_identifier (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ const MMBaseModemAtCommand *commands = equipment_identifiers;
+
+ mm_dbg ("loading equipment identifier...");
+
+ /* On CDMA-only (non-3GPP) modems, just try +GSN */
+ if (mm_iface_modem_is_cdma_only (self))
+ commands++;
+
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ commands,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Device identifier loading (Modem interface) */
+
+typedef struct {
+ gchar *ati;
+ gchar *ati1;
+} DeviceIdentifierContext;
+
+static void
+device_identifier_context_free (DeviceIdentifierContext *ctx)
+{
+ g_free (ctx->ati);
+ g_free (ctx->ati1);
+ g_free (ctx);
+}
+
+static gchar *
+modem_load_device_identifier_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gpointer ctx = NULL;
+ gchar *device_identifier;
+
+ mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, &ctx, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ g_assert (ctx != NULL);
+ device_identifier = (mm_broadband_modem_create_device_identifier (
+ MM_BROADBAND_MODEM (self),
+ ((DeviceIdentifierContext *)ctx)->ati,
+ ((DeviceIdentifierContext *)ctx)->ati1));
+ mm_dbg ("loaded device identifier: %s", device_identifier);
+ return device_identifier;
+}
+
+static gboolean
+parse_ati_reply (MMBaseModem *self,
+ DeviceIdentifierContext *ctx,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ /* Store the proper string in the proper place */
+ if (!error) {
+ if (g_str_equal (command, "ATI1"))
+ ctx->ati1 = g_strdup (response);
+ else
+ ctx->ati = g_strdup (response);
+ }
+
+ /* Always keep on, this is a sequence where all the steps should be taken */
+ return TRUE;
+}
+
+static const MMBaseModemAtCommand device_identifier_steps[] = {
+ { "ATI", 3, TRUE, (MMBaseModemAtResponseProcessor)parse_ati_reply },
+ { "ATI1", 3, TRUE, (MMBaseModemAtResponseProcessor)parse_ati_reply },
+ { NULL }
+};
+
+static void
+modem_load_device_identifier (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading device identifier...");
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ device_identifier_steps,
+ g_new0 (DeviceIdentifierContext, 1),
+ (GDestroyNotify)device_identifier_context_free,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load own numbers (Modem interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMQcdmSerialPort *qcdm;
+} OwnNumbersContext;
+
+static void
+own_numbers_context_complete_and_free (OwnNumbersContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ if (ctx->qcdm) {
+ mm_serial_port_close (MM_SERIAL_PORT (ctx->qcdm));
+ g_object_unref (ctx->qcdm);
+ }
+ g_free (ctx);
+}
+
+static GStrv
+modem_load_own_numbers_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return NULL;
+
+ return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+}
+
+static void
+mdn_qcdm_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ OwnNumbersContext *ctx)
+{
+ QcdmResult *result;
+ gint err = QCDM_SUCCESS;
+ const char *numbers[2] = { NULL, NULL };
+
+ if (error) {
+ g_simple_async_result_set_from_error (ctx->result, error);
+ own_numbers_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_nv_get_mdn_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (!result) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse NV MDN command result: %d",
+ err);
+ own_numbers_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (qcdm_result_get_string (result, QCDM_CMD_NV_GET_MDN_ITEM_MDN, &numbers[0]) >= 0) {
+ gboolean valid = TRUE;
+ const char *p = numbers[0];
+
+ /* Returned NV item data is read directly out of NV memory on the card,
+ * so minimally verify it.
+ */
+ if (strlen (numbers[0]) < 6 || strlen (numbers[0]) > 15)
+ valid = FALSE;
+
+ /* MDN is always decimal digits; allow + for good measure */
+ while (p && *p && valid)
+ valid = g_ascii_isdigit (*p++) || (*p == '+');
+
+ if (valid) {
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ g_strdupv ((gchar **) numbers),
+ NULL);
+ } else {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%s",
+ "MDN from NV memory appears invalid");
+ }
+ } else {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "%s",
+ "Failed retrieve MDN");
+ }
+
+ qcdm_result_unref (result);
+ own_numbers_context_complete_and_free (ctx);
+}
+
+static void
+modem_load_own_numbers_done (MMIfaceModem *self,
+ GAsyncResult *res,
+ OwnNumbersContext *ctx)
+{
+ const gchar *result;
+ GError *error = NULL;
+ GStrv numbers;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!result) {
+ /* try QCDM */
+ if (ctx->qcdm) {
+ GByteArray *mdn;
+
+ g_clear_error (&error);
+
+ mdn = g_byte_array_sized_new (200);
+ mdn->len = qcdm_cmd_nv_get_mdn_new ((char *) mdn->data, 200, 0);
+ g_assert (mdn->len);
+
+ mm_qcdm_serial_port_queue_command (ctx->qcdm,
+ mdn,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn)mdn_qcdm_ready,
+ ctx);
+ return;
+ }
+ } else {
+ numbers = mm_3gpp_parse_cnum_exec_response (result, &error);
+ if (numbers)
+ g_simple_async_result_set_op_res_gpointer (ctx->result, numbers, NULL);
+ }
+
+ if (error)
+ g_simple_async_result_take_error (ctx->result, error);
+
+ own_numbers_context_complete_and_free (ctx);
+}
+
+static void
+modem_load_own_numbers (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ OwnNumbersContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_new0 (OwnNumbersContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_own_numbers);
+ ctx->qcdm = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
+ if (ctx->qcdm) {
+ if (mm_serial_port_open (MM_SERIAL_PORT (ctx->qcdm), &error)) {
+ ctx->qcdm = g_object_ref (ctx->qcdm);
+ } else {
+ mm_dbg ("Couldn't open QCDM port: (%d) %s",
+ error ? error->code : -1,
+ error ? error->message : "(unknown)");
+ ctx->qcdm = NULL;
+ }
+ }
+
+ mm_dbg ("loading own numbers...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNUM",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)modem_load_own_numbers_done,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Check if unlock required (Modem interface) */
+
+typedef struct {
+ const gchar *result;
+ MMModemLock code;
+} CPinResult;
+
+static CPinResult unlock_results[] = {
+ /* Longer entries first so we catch the correct one with strcmp() */
+ { "READY", MM_MODEM_LOCK_NONE },
+ { "SIM PIN2", MM_MODEM_LOCK_SIM_PIN2 },
+ { "SIM PUK2", MM_MODEM_LOCK_SIM_PUK2 },
+ { "SIM PIN", MM_MODEM_LOCK_SIM_PIN },
+ { "SIM PUK", MM_MODEM_LOCK_SIM_PUK },
+ { "PH-NETSUB PIN", MM_MODEM_LOCK_PH_NETSUB_PIN },
+ { "PH-NETSUB PUK", MM_MODEM_LOCK_PH_NETSUB_PUK },
+ { "PH-FSIM PIN", MM_MODEM_LOCK_PH_FSIM_PIN },
+ { "PH-FSIM PUK", MM_MODEM_LOCK_PH_FSIM_PUK },
+ { "PH-CORP PIN", MM_MODEM_LOCK_PH_CORP_PIN },
+ { "PH-CORP PUK", MM_MODEM_LOCK_PH_CORP_PUK },
+ { "PH-SIM PIN", MM_MODEM_LOCK_PH_SIM_PIN },
+ { "PH-NET PIN", MM_MODEM_LOCK_PH_NET_PIN },
+ { "PH-NET PUK", MM_MODEM_LOCK_PH_NET_PUK },
+ { "PH-SP PIN", MM_MODEM_LOCK_PH_SP_PIN },
+ { "PH-SP PUK", MM_MODEM_LOCK_PH_SP_PUK },
+ { NULL }
+};
+
+static MMModemLock
+modem_load_unlock_required_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return MM_MODEM_LOCK_UNKNOWN;
+
+ return (MMModemLock) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (res)));
+}
+
+static void
+cpin_query_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+
+ MMModemLock lock = MM_MODEM_LOCK_UNKNOWN;
+ const gchar *result;
+ GError *error = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ if (result &&
+ strstr (result, "+CPIN:")) {
+ CPinResult *iter = &unlock_results[0];
+ const gchar *str;
+
+ str = strstr (result, "+CPIN:") + 6;
+ /* Skip possible whitespaces after '+CPIN:' and before the response */
+ while (*str == ' ')
+ str++;
+
+ /* Some phones (Motorola EZX models) seem to quote the response */
+ if (str[0] == '"')
+ str++;
+
+ /* Translate the reply */
+ while (iter->result) {
+ if (g_str_has_prefix (str, iter->result)) {
+ lock = iter->code;
+ break;
+ }
+ iter++;
+ }
+ }
+
+ g_simple_async_result_set_op_res_gpointer (simple,
+ GUINT_TO_POINTER (lock),
+ NULL);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_load_unlock_required (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_unlock_required);
+
+ /* CDMA-only modems don't need this */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ mm_dbg ("Skipping unlock check in CDMA-only modem...");
+ g_simple_async_result_set_op_res_gpointer (result,
+ GUINT_TO_POINTER (MM_MODEM_LOCK_NONE),
+ NULL);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ mm_dbg ("checking if unlock required...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPIN?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cpin_query_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Supported modes loading (Modem interface) */
+
+typedef struct {
+ GSimpleAsyncResult *result;
+ MMBroadbandModem *self;
+ MMModemMode mode;
+ gboolean run_cnti;
+ gboolean run_ws46;
+ gboolean run_gcap;
+} LoadSupportedModesContext;
+
+static void
+load_supported_modes_context_complete_and_free (LoadSupportedModesContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_slice_free (LoadSupportedModesContext, ctx);
+}
+
+static GArray *
+modem_load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GArray *modes;
+ MMModemModeCombination mode;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return NULL;
+
+ /* Build a mask with all supported modes */
+ modes = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ mode.allowed = (MMModemMode) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (res)));
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (modes, mode);
+
+ return modes;
+}
+
+static void load_supported_modes_step (LoadSupportedModesContext *ctx);
+
+static void
+supported_modes_gcap_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ LoadSupportedModesContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!error) {
+ MMModemMode mode = MM_MODEM_MODE_NONE;
+
+ if (strstr (response, "IS")) {
+ /* IS-856 is the EV-DO family */
+ if (strstr (response, "856")) {
+ if (!ctx->self->priv->modem_cdma_evdo_network_supported) {
+ ctx->self->priv->modem_cdma_evdo_network_supported = TRUE;
+ g_object_notify (G_OBJECT (ctx->self), MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED);
+ }
+ mm_dbg ("Device allows (CDMA) 3G network mode");
+ mode |= MM_MODEM_MODE_3G;
+ }
+ /* IS-707 is the 1xRTT family, which we consider as 2G */
+ if (strstr (response, "707")) {
+ if (!ctx->self->priv->modem_cdma_cdma1x_network_supported) {
+ ctx->self->priv->modem_cdma_cdma1x_network_supported = TRUE;
+ g_object_notify (G_OBJECT (ctx->self), MM_IFACE_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED);
+ }
+ mm_dbg ("Device allows (CDMA) 2G network mode");
+ mode |= MM_MODEM_MODE_2G;
+ }
+ }
+
+ /* If no expected mode found, error */
+ if (mode == MM_MODEM_MODE_NONE) {
+ /* This should really never happen in the default implementation. */
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't find specific CDMA mode in capabilities string: '%s'",
+ response);
+ } else {
+ /* Keep our results */
+ ctx->mode |= mode;
+ }
+ }
+
+ if (error) {
+ mm_dbg ("Generic query of supported CDMA networks failed: '%s'", error->message);
+ g_error_free (error);
+
+ /* Use defaults */
+ if (ctx->self->priv->modem_cdma_cdma1x_network_supported) {
+ mm_dbg ("Assuming device allows (CDMA) 2G network mode");
+ ctx->mode |= MM_MODEM_MODE_2G;
+ }
+ if (ctx->self->priv->modem_cdma_evdo_network_supported) {
+ mm_dbg ("Assuming device allows (CDMA) 3G network mode");
+ ctx->mode |= MM_MODEM_MODE_3G;
+ }
+ }
+
+ /* Now keep on with the loading, we're probably finishing now */
+ ctx->run_gcap = FALSE;
+ load_supported_modes_step (ctx);
+}
+
+static void
+supported_modes_ws46_test_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ LoadSupportedModesContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!error) {
+ MMModemMode mode = MM_MODEM_MODE_NONE;
+
+ /*
+ * More than one numeric ID may appear in the list, that's why
+ * they are checked separately.
+ *
+ * NOTE: Do not skip WS46 prefix; it would break Cinterion handling.
+ *
+ * From 3GPP TS 27.007 v.11.2.0, section 5.9
+ * 12 GSM Digital Cellular Systems (GERAN only)
+ * 22 UTRAN only
+ * 25 3GPP Systems (GERAN, UTRAN and E-UTRAN)
+ * 28 E-UTRAN only
+ * 29 GERAN and UTRAN
+ * 30 GERAN and E-UTRAN
+ * 31 UTRAN and E-UTRAN
+ */
+
+ if (strstr (response, "12") != NULL) {
+ mm_dbg ("Device allows (3GPP) 2G-only network mode");
+ mode |= MM_MODEM_MODE_2G;
+ }
+
+ if (strstr (response, "22") != NULL) {
+ mm_dbg ("Device allows (3GPP) 3G-only network mode");
+ mode |= MM_MODEM_MODE_3G;
+ }
+
+ if (strstr (response, "28") != NULL) {
+ mm_dbg ("Device allows (3GPP) 4G-only network mode");
+ mode |= MM_MODEM_MODE_4G;
+ }
+
+ if (strstr (response, "29") != NULL) {
+ mm_dbg ("Device allows (3GPP) 2G/3G network mode");
+ mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ }
+
+ if (strstr (response, "30") != NULL) {
+ mm_dbg ("Device allows (3GPP) 2G/4G network mode");
+ mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
+ }
+
+ if (strstr (response, "31") != NULL) {
+ mm_dbg ("Device allows (3GPP) 3G/4G network mode");
+ mode |= (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ }
+
+ if (strstr (response, "25") != NULL) {
+ if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) {
+ mm_dbg ("Device allows every supported 3GPP network mode (2G/3G/4G)");
+ mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ } else {
+ mm_dbg ("Device allows every supported 3GPP network mode (2G/3G)");
+ mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ }
+ }
+
+ /* If no expected ID found, log error */
+ if (mode == MM_MODEM_MODE_NONE)
+ mm_dbg ("Invalid list of supported networks reported by WS46=?: '%s'", response);
+ else
+ ctx->mode |= mode;
+ } else {
+ mm_dbg ("Generic query of supported 3GPP networks with WS46=? failed: '%s'", error->message);
+ g_error_free (error);
+ }
+
+ /* Now keep on with the loading, we may need CDMA-specific checks */
+ ctx->run_ws46 = FALSE;
+ load_supported_modes_step (ctx);
+}
+
+static void
+supported_modes_cnti_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ LoadSupportedModesContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!error) {
+ MMModemMode mode = MM_MODEM_MODE_NONE;
+ gchar *lower;
+
+ lower = g_ascii_strdown (response, -1);
+
+ if (g_strstr_len (lower, -1, "gsm") ||
+ g_strstr_len (lower, -1, "gprs") ||
+ g_strstr_len (lower, -1, "edge")) {
+ mm_dbg ("Device allows (3GPP) 2G networks");
+ mode |= MM_MODEM_MODE_2G;
+ }
+
+ if (g_strstr_len (lower, -1, "umts") ||
+ g_strstr_len (lower, -1, "hsdpa") ||
+ g_strstr_len (lower, -1, "hsupa") ||
+ g_strstr_len (lower, -1, "hspa+")) {
+ mm_dbg ("Device allows (3GPP) 3G networks");
+ mode |= MM_MODEM_MODE_3G;
+ }
+
+ if (g_strstr_len (lower, -1, "lte")) {
+ mm_dbg ("Device allows (3GPP) 4G networks");
+ mode |= MM_MODEM_MODE_4G;
+ }
+
+ g_free (lower);
+
+ /* If no expected ID found, log error */
+ if (mode == MM_MODEM_MODE_NONE)
+ mm_dbg ("Invalid list of supported networks reported by *CNTI: '%s'", response);
+ else
+ ctx->mode |= mode;
+ } else {
+ mm_dbg ("Generic query of supported 3GPP networks with *CNTI failed: '%s'", error->message);
+ g_error_free (error);
+ }
+
+ /* Now keep on with the loading */
+ ctx->run_cnti = FALSE;
+ load_supported_modes_step (ctx);
+}
+
+static void
+load_supported_modes_step (LoadSupportedModesContext *ctx)
+{
+ if (ctx->run_cnti) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (ctx->self),
+ "*CNTI=2",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)supported_modes_cnti_ready,
+ ctx);
+ return;
+ }
+
+ if (ctx->run_ws46) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (ctx->self),
+ "+WS46=?",
+ 3,
+ TRUE, /* allow caching, it's a test command */
+ (GAsyncReadyCallback)supported_modes_ws46_test_ready,
+ ctx);
+ return;
+ }
+
+ if (ctx->run_gcap) {
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (ctx->self),
+ "+GCAP",
+ 3,
+ TRUE, /* allow caching */
+ (GAsyncReadyCallback)supported_modes_gcap_ready,
+ ctx);
+ return;
+ }
+
+ /* All done.
+ * If no mode found, error */
+ if (ctx->mode == MM_MODEM_MODE_NONE)
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't retrieve supported modes");
+ else
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ GUINT_TO_POINTER (ctx->mode),
+ NULL);
+ load_supported_modes_context_complete_and_free (ctx);
+}
+
+static void
+modem_load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LoadSupportedModesContext *ctx;
+
+ mm_dbg ("loading supported modes...");
+ ctx = g_slice_new0 (LoadSupportedModesContext);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_supported_modes);
+ ctx->mode = MM_MODEM_MODE_NONE;
+
+ if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) {
+ /* Run +WS46=? and *CNTI=2 */
+ ctx->run_ws46 = TRUE;
+ ctx->run_cnti = TRUE;
+ }
+
+ if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) {
+ /* Run +GCAP in order to know if the modem is CDMA1x only or CDMA1x/EV-DO */
+ ctx->run_gcap = TRUE;
+ }
+
+ load_supported_modes_step (ctx);
+}
+
+/*****************************************************************************/
+/* Supported IP families loading (Modem interface) */
+
+static MMBearerIpFamily
+modem_load_supported_ip_families_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return MM_BEARER_IP_FAMILY_NONE;
+
+ return (MMBearerIpFamily) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (res)));
+}
+
+static void
+supported_ip_families_cgdcont_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ const gchar *response;
+ GError *error = NULL;
+ MMBearerIpFamily mask = MM_BEARER_IP_FAMILY_NONE;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (response) {
+ GList *formats, *l;
+
+ formats = mm_3gpp_parse_cgdcont_test_response (response, &error);
+ for (l = formats; l; l = g_list_next (l))
+ mask |= ((MM3gppPdpContextFormat *)(l->data))->pdp_type;
+
+ mm_3gpp_pdp_context_format_list_free (formats);
+ }
+
+ if (error)
+ g_simple_async_result_take_error (simple, error);
+ else
+ g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (mask), NULL);
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_load_supported_ip_families (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ mm_dbg ("loading supported IP families...");
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_supported_ip_families);
+
+ if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
+ g_simple_async_result_set_op_res_gpointer (
+ result,
+ GUINT_TO_POINTER (MM_BEARER_IP_FAMILY_IPV4),
+ NULL);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ /* Query with CGDCONT=? */
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGDCONT=?",
+ 3,
+ TRUE, /* allow caching, it's a test command */
+ (GAsyncReadyCallback)supported_ip_families_cgdcont_test_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Signal quality loading (Modem interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMSerialPort *port;
+} SignalQualityContext;
+
+static void
+signal_quality_context_complete_and_free (SignalQualityContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ if (ctx->port)
+ g_object_unref (ctx->port);
+ g_free (ctx);
+}
+
+static guint
+modem_load_signal_quality_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return 0;
+
+ return GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (res)));
+}
+
+static void
+signal_quality_csq_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ SignalQualityContext *ctx)
+{
+ GError *error = NULL;
+ GVariant *result;
+ const gchar *result_str;
+
+ result = mm_base_modem_at_sequence_full_finish (MM_BASE_MODEM (self), res, NULL, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ signal_quality_context_complete_and_free (ctx);
+ return;
+ }
+
+ result_str = g_variant_get_string (result, NULL);
+ if (result_str) {
+ /* Got valid reply */
+ int quality;
+ int ber;
+
+ result_str = mm_strip_tag (result_str, "+CSQ:");
+ if (sscanf (result_str, "%d, %d", &quality, &ber)) {
+ /* 99 means unknown */
+ if (quality == 99) {
+ g_simple_async_result_take_error (
+ ctx->result,
+ mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK));
+ } else {
+ /* Normalize the quality */
+ quality = CLAMP (quality, 0, 31) * 100 / 31;
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ GUINT_TO_POINTER (quality),
+ NULL);
+ }
+
+ signal_quality_context_complete_and_free (ctx);
+ return;
+ }
+ }
+
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse signal quality results");
+ signal_quality_context_complete_and_free (ctx);
+}
+
+/* Some modems want +CSQ, others want +CSQ?, and some of both types
+ * will return ERROR if they don't get the command they want. So
+ * try the other command if the first one fails.
+ */
+static const MMBaseModemAtCommand signal_quality_csq_sequence[] = {
+ { "+CSQ", 3, TRUE, response_processor_string_ignore_at_errors },
+ { "+CSQ?", 3, TRUE, response_processor_string_ignore_at_errors },
+ { NULL }
+};
+
+static void
+signal_quality_csq (SignalQualityContext *ctx)
+{
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (ctx->self),
+ MM_AT_SERIAL_PORT (ctx->port),
+ signal_quality_csq_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)signal_quality_csq_ready,
+ ctx);
+}
+
+static guint
+normalize_ciev_cind_signal_quality (guint quality,
+ guint min,
+ guint max)
+{
+ if (!max) {
+ /* If we didn't get a max, assume it was 5. Note that we do allow
+ * 0, meaning no signal at all. */
+ return (quality <= 5) ? (quality * 20) : 100;
+ }
+
+ if (quality >= min &&
+ quality <= max)
+ return ((100 * (quality - min)) / (max - min));
+
+ /* Value out of range, assume no signal here. Some modems (Cinterion
+ * for example) will send out-of-range values when they cannot get
+ * the signal strength. */
+ return 0;
+}
+
+static void
+signal_quality_cind_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ SignalQualityContext *ctx)
+{
+ GError *error = NULL;
+ const gchar *result;
+ GByteArray *indicators;
+ guint quality = 0;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_clear_error (&error);
+ goto try_csq;
+ }
+
+ indicators = mm_3gpp_parse_cind_read_response (result, &error);
+ if (!indicators) {
+ mm_dbg ("(%s) Could not parse CIND signal quality results: %s",
+ mm_port_get_device (MM_PORT (ctx->port)),
+ error->message);
+ g_clear_error (&error);
+ goto try_csq;
+ }
+
+ if (indicators->len < self->priv->modem_cind_indicator_signal_quality) {
+ mm_dbg ("(%s) Could not parse CIND signal quality results; signal "
+ "index (%u) outside received range (0-%u)",
+ mm_port_get_device (MM_PORT (ctx->port)),
+ self->priv->modem_cind_indicator_signal_quality,
+ indicators->len);
+ } else {
+ quality = g_array_index (indicators,
+ guint8,
+ self->priv->modem_cind_indicator_signal_quality);
+ quality = normalize_ciev_cind_signal_quality (quality,
+ self->priv->modem_cind_min_signal_quality,
+ self->priv->modem_cind_max_signal_quality);
+ }
+ g_byte_array_free (indicators, TRUE);
+
+ if (quality > 0) {
+ /* +CIND success */
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ GUINT_TO_POINTER (quality),
+ NULL);
+ signal_quality_context_complete_and_free (ctx);
+ return;
+ }
+
+try_csq:
+ /* Always fall back to +CSQ if for whatever reason +CIND failed. Also,
+ * some QMI-based devices say they support signal via CIND, but always
+ * report zero even though they have signal. So if we get zero signal
+ * from +CIND, try CSQ too. (bgo #636040)
+ */
+ signal_quality_csq (ctx);
+}
+
+static void
+signal_quality_cind (SignalQualityContext *ctx)
+{
+ mm_base_modem_at_command_full (MM_BASE_MODEM (ctx->self),
+ MM_AT_SERIAL_PORT (ctx->port),
+ "+CIND?",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)signal_quality_cind_ready,
+ ctx);
+}
+
+static void
+signal_quality_qcdm_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ SignalQualityContext *ctx)
+{
+ QcdmResult *result;
+ guint32 num = 0, quality = 0, i;
+ gfloat best_db = -28;
+ gint err = QCDM_SUCCESS;
+
+ if (error) {
+ g_simple_async_result_set_from_error (ctx->result, error);
+ signal_quality_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_pilot_sets_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (!result) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse pilot sets command result: %d",
+ err);
+ signal_quality_context_complete_and_free (ctx);
+ return;
+ }
+
+ qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, &num);
+ for (i = 0; i < num; i++) {
+ guint32 pn_offset = 0, ecio = 0;
+ gfloat db = 0;
+
+ qcdm_cmd_pilot_sets_result_get_pilot (result,
+ QCDM_CMD_PILOT_SETS_TYPE_ACTIVE,
+ i,
+ &pn_offset,
+ &ecio,
+ &db);
+ best_db = MAX (db, best_db);
+ }
+ qcdm_result_unref (result);
+
+ if (num > 0) {
+ #define BEST_ECIO 3
+ #define WORST_ECIO 25
+
+ /* EC/IO dB ranges from roughly 0 to -31 dB. Lower == worse. We
+ * really only care about -3 to -25 dB though, since that's about what
+ * you'll see in real-world usage.
+ */
+ best_db = CLAMP (ABS (best_db), BEST_ECIO, WORST_ECIO) - BEST_ECIO;
+ quality = (guint32) (100 - (best_db * 100 / (WORST_ECIO - BEST_ECIO)));
+ }
+
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ GUINT_TO_POINTER (quality),
+ NULL);
+ signal_quality_context_complete_and_free (ctx);
+}
+
+static void
+signal_quality_qcdm (SignalQualityContext *ctx)
+{
+ GByteArray *pilot_sets;
+
+ /* Use CDMA1x pilot EC/IO if we can */
+ pilot_sets = g_byte_array_sized_new (25);
+ pilot_sets->len = qcdm_cmd_pilot_sets_new ((char *) pilot_sets->data, 25);
+ g_assert (pilot_sets->len);
+
+ mm_qcdm_serial_port_queue_command (MM_QCDM_SERIAL_PORT (ctx->port),
+ pilot_sets,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn)signal_quality_qcdm_ready,
+ ctx);
+}
+
+static void
+modem_load_signal_quality (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SignalQualityContext *ctx;
+ GError *error = NULL;
+
+ mm_dbg ("loading signal quality...");
+ ctx = g_new0 (SignalQualityContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_signal_quality);
+
+ /* Check whether we can get a non-connected AT port */
+ ctx->port = (MMSerialPort *)mm_base_modem_get_best_at_port (MM_BASE_MODEM (self), &error);
+ if (ctx->port) {
+ if (MM_BROADBAND_MODEM (self)->priv->modem_cind_supported)
+ signal_quality_cind (ctx);
+ else
+ signal_quality_csq (ctx);
+ return;
+ }
+
+ /* If no best AT port available (all connected), try with QCDM ports */
+ ctx->port = (MMSerialPort *)mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
+ if (ctx->port) {
+ g_error_free (error);
+ signal_quality_qcdm (ctx);
+ return;
+ }
+
+ /* Return the error we got when getting best AT port */
+ g_simple_async_result_take_error (ctx->result, error);
+ signal_quality_context_complete_and_free (ctx);
+}
+
+/*****************************************************************************/
+/* Load access technology (Modem interface) */
+
+typedef struct {
+ MMModemAccessTechnology access_technologies;
+ guint mask;
+} AccessTechAndMask;
+
+static gboolean
+modem_load_access_technologies_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemAccessTechnology *access_technologies,
+ guint *mask,
+ GError **error)
+{
+ AccessTechAndMask *tech;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ tech = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ g_assert (tech);
+
+ *access_technologies = tech->access_technologies;
+ *mask = tech->mask;
+ return TRUE;
+}
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMQcdmSerialPort *port;
+
+ guint32 opmode;
+ guint32 sysmode;
+ gboolean hybrid;
+
+ gboolean wcdma_open;
+ gboolean evdo_open;
+
+ MMModemAccessTechnology fallback_act;
+ guint fallback_mask;
+} AccessTechContext;
+
+static void
+access_tech_context_complete_and_free (AccessTechContext *ctx,
+ GError *error, /* takes ownership */
+ gboolean idle)
+{
+ AccessTechAndMask *tech;
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ guint mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ goto done;
+ }
+
+ if (ctx->fallback_mask) {
+ mm_dbg ("Fallback access technology: 0x%08x", ctx->fallback_act);
+ act = ctx->fallback_act;
+ mask = ctx->fallback_mask;
+ goto done;
+ }
+
+ mm_dbg ("QCDM operating mode: %d", ctx->opmode);
+ mm_dbg ("QCDM system mode: %d", ctx->sysmode);
+ mm_dbg ("QCDM hybrid pref: %d", ctx->hybrid);
+ mm_dbg ("QCDM WCDMA open: %d", ctx->wcdma_open);
+ mm_dbg ("QCDM EVDO open: %d", ctx->evdo_open);
+
+ if (ctx->opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) {
+ switch (ctx->sysmode) {
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA:
+ if (!ctx->hybrid || !ctx->evdo_open) {
+ act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+ }
+ /* Fall through */
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR:
+ /* Assume EVDOr0; can't yet determine r0 vs. rA with QCDM */
+ if (ctx->evdo_open)
+ act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_GSM:
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA:
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_GW:
+ if (ctx->wcdma_open) {
+ /* Assume UMTS; can't yet determine UMTS/HSxPA/HSPA+ with QCDM */
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
+ } else {
+ /* Assume GPRS; can't yet determine GSM/GPRS/EDGE with QCDM */
+ act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
+ }
+ mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+ case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_LTE:
+ act = MM_MODEM_ACCESS_TECHNOLOGY_LTE;
+ mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
+ break;
+ }
+ }
+
+done:
+ if (error == NULL) {
+ tech = g_new0 (AccessTechAndMask, 1);
+ tech->access_technologies = act;
+ tech->mask = mask;
+ g_simple_async_result_set_op_res_gpointer (ctx->result, tech, g_free);
+ }
+
+ if (idle)
+ g_simple_async_result_complete_in_idle (ctx->result);
+ else
+ g_simple_async_result_complete (ctx->result);
+
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ if (ctx->port)
+ g_object_unref (ctx->port);
+ g_free (ctx);
+}
+
+static void
+access_tech_qcdm_wcdma_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ AccessTechContext *ctx)
+{
+ QcdmResult *result;
+ gint err = QCDM_SUCCESS;
+ guint8 l1;
+
+ if (error) {
+ access_tech_context_complete_and_free (ctx, g_error_copy (error), FALSE);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_wcdma_subsys_state_info_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (result) {
+ qcdm_result_get_u8 (result, QCDM_CMD_WCDMA_SUBSYS_STATE_INFO_ITEM_L1_STATE, &l1);
+ qcdm_result_unref (result);
+
+ if (l1 == QCDM_WCDMA_L1_STATE_PCH ||
+ l1 == QCDM_WCDMA_L1_STATE_FACH ||
+ l1 == QCDM_WCDMA_L1_STATE_DCH)
+ ctx->wcdma_open = TRUE;
+ }
+
+ access_tech_context_complete_and_free (ctx, NULL, FALSE);
+}
+
+static void
+access_tech_qcdm_gsm_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ AccessTechContext *ctx)
+{
+ GByteArray *cmd;
+ QcdmResult *result;
+ gint err = QCDM_SUCCESS;
+ guint8 opmode = 0;
+ guint8 sysmode = 0;
+
+ if (error) {
+ access_tech_context_complete_and_free (ctx, g_error_copy (error), FALSE);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_gsm_subsys_state_info_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (!result) {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse GSM subsys command result: %d",
+ err);
+ access_tech_context_complete_and_free (ctx, error, FALSE);
+ return;
+ }
+
+ qcdm_result_get_u8 (result, QCDM_CMD_GSM_SUBSYS_STATE_INFO_ITEM_CM_OP_MODE, &opmode);
+ qcdm_result_get_u8 (result, QCDM_CMD_GSM_SUBSYS_STATE_INFO_ITEM_CM_SYS_MODE, &sysmode);
+ qcdm_result_unref (result);
+
+ ctx->opmode = opmode;
+ ctx->sysmode = sysmode;
+
+ /* WCDMA subsystem state */
+ cmd = g_byte_array_sized_new (50);
+ cmd->len = qcdm_cmd_wcdma_subsys_state_info_new ((char *) cmd->data, 50);
+ g_assert (cmd->len);
+
+ mm_qcdm_serial_port_queue_command (port,
+ cmd,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn) access_tech_qcdm_wcdma_ready,
+ ctx);
+}
+
+static void
+access_tech_qcdm_hdr_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ AccessTechContext *ctx)
+{
+ QcdmResult *result;
+ gint err = QCDM_SUCCESS;
+ guint8 session = 0;
+ guint8 almp = 0;
+
+ if (error) {
+ access_tech_context_complete_and_free (ctx, g_error_copy (error), FALSE);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_hdr_subsys_state_info_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (result) {
+ qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &session);
+ qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &almp);
+ qcdm_result_unref (result);
+
+ if (session == QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_OPEN &&
+ (almp == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_IDLE ||
+ almp == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_CONNECTED))
+ ctx->evdo_open = TRUE;
+ }
+
+ access_tech_context_complete_and_free (ctx, NULL, FALSE);
+}
+
+static void
+access_tech_qcdm_cdma_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ AccessTechContext *ctx)
+{
+ GByteArray *cmd;
+ QcdmResult *result;
+ gint err = QCDM_SUCCESS;
+ guint32 hybrid;
+
+ if (error) {
+ access_tech_context_complete_and_free (ctx, g_error_copy (error), FALSE);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_cm_subsys_state_info_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (!result) {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse CM subsys command result: %d",
+ err);
+ access_tech_context_complete_and_free (ctx, error, FALSE);
+ return;
+ }
+
+ qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &ctx->opmode);
+ qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &ctx->sysmode);
+ qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_HYBRID_PREF, &hybrid);
+ qcdm_result_unref (result);
+
+ ctx->hybrid = !!hybrid;
+
+ /* HDR subsystem state */
+ cmd = g_byte_array_sized_new (50);
+ cmd->len = qcdm_cmd_hdr_subsys_state_info_new ((char *) cmd->data, 50);
+ g_assert (cmd->len);
+
+ mm_qcdm_serial_port_queue_command (port,
+ cmd,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn) access_tech_qcdm_hdr_ready,
+ ctx);
+}
+
+static void
+access_tech_from_cdma_registration_state (MMBroadbandModem *self,
+ AccessTechContext *ctx)
+{
+ gboolean cdma1x_registered = FALSE;
+ gboolean evdo_registered = FALSE;
+
+ if (self->priv->modem_cdma_evdo_registration_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ evdo_registered = TRUE;
+
+ if (self->priv->modem_cdma_cdma1x_registration_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ cdma1x_registered = TRUE;
+
+ if (self->priv->modem_cdma_evdo_network_supported && evdo_registered) {
+ ctx->fallback_act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
+ ctx->fallback_mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
+ } else if (self->priv->modem_cdma_cdma1x_network_supported && cdma1x_registered) {
+ ctx->fallback_act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
+ ctx->fallback_mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
+ }
+
+ mm_dbg ("EVDO registration: %d", self->priv->modem_cdma_evdo_registration_state);
+ mm_dbg ("CDMA1x registration: %d", self->priv->modem_cdma_cdma1x_registration_state);
+ mm_dbg ("Fallback access tech: 0x%08x", ctx->fallback_act);
+}
+
+static void
+modem_load_access_technologies (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ AccessTechContext *ctx;
+ GByteArray *cmd;
+ GError *error = NULL;
+
+ /* For modems where only QCDM provides detailed information, try to
+ * get access technologies via the various QCDM subsystems or from
+ * registration state
+ */
+ ctx = g_new0 (AccessTechContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->port = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_access_technologies);
+
+ if (!ctx->port) {
+ if (mm_iface_modem_is_cdma (self)) {
+ /* If we don't have a QCDM port but the modem is CDMA-only, then
+ * guess access technologies from the registration information.
+ */
+ access_tech_from_cdma_registration_state (MM_BROADBAND_MODEM (self), ctx);
+ } else {
+ error = g_error_new_literal (MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot get 3GPP access technology without a QCDM port");
+ }
+ access_tech_context_complete_and_free (ctx, error, TRUE);
+ return;
+ }
+
+ mm_dbg ("loading access technologies via QCDM...");
+
+ /* FIXME: we may want to run both the CDMA and 3GPP in sequence to ensure
+ * that a multi-mode device that's in CDMA-mode but still has 3GPP capabilities
+ * will get the correct access tech, since the 3GPP check is run first.
+ */
+
+ if (mm_iface_modem_is_3gpp (self)) {
+ cmd = g_byte_array_sized_new (50);
+ cmd->len = qcdm_cmd_gsm_subsys_state_info_new ((char *) cmd->data, 50);
+ g_assert (cmd->len);
+
+ mm_qcdm_serial_port_queue_command (ctx->port,
+ cmd,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn) access_tech_qcdm_gsm_ready,
+ ctx);
+ } else if (mm_iface_modem_is_cdma (self)) {
+ cmd = g_byte_array_sized_new (50);
+ cmd->len = qcdm_cmd_cm_subsys_state_info_new ((char *) cmd->data, 50);
+ g_assert (cmd->len);
+
+ mm_qcdm_serial_port_queue_command (ctx->port,
+ cmd,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn) access_tech_qcdm_cdma_ready,
+ ctx);
+ } else
+ g_assert_not_reached ();
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+ciev_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ MMBroadbandModem *self)
+{
+ gint ind = 0;
+ gchar *item;
+
+ item = g_match_info_fetch (info, 1);
+ if (item)
+ ind = atoi (item);
+
+ /* Handle signal quality change indication */
+ if (ind == self->priv->modem_cind_indicator_signal_quality ||
+ g_str_equal (item, "signal")) {
+ gchar *value;
+
+ value = g_match_info_fetch (info, 2);
+ if (value) {
+ gint quality = 0;
+
+ quality = atoi (value);
+
+ mm_iface_modem_update_signal_quality (
+ MM_IFACE_MODEM (self),
+ normalize_ciev_cind_signal_quality (quality,
+ self->priv->modem_cind_min_signal_quality,
+ self->priv->modem_cind_max_signal_quality));
+ g_free (value);
+ }
+ }
+
+ g_free (item);
+
+ /* FIXME: handle roaming and service indicators.
+ * ... wait, arent these already handle by unsolicited CREG responses? */
+}
+
+static void
+set_unsolicited_events_handlers (MMBroadbandModem *self,
+ gboolean enable)
+{
+ MMAtSerialPort *ports[2];
+ GRegex *ciev_regex;
+ guint i;
+
+ ciev_regex = mm_3gpp_ciev_regex_get ();
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Enable unsolicited events in given port */
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ /* Set/unset unsolicited CIEV event handler */
+ mm_dbg ("(%s) %s 3GPP unsolicited events handlers",
+ mm_port_get_device (MM_PORT (ports[i])),
+ enable ? "Setting" : "Removing");
+ mm_at_serial_port_add_unsolicited_msg_handler (
+ ports[i],
+ ciev_regex,
+ enable ? (MMAtSerialUnsolicitedMsgFn) ciev_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+
+ g_regex_unref (ciev_regex);
+}
+
+static void
+cind_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GHashTable *indicators = NULL;
+ GError *error = NULL;
+ const gchar *result;
+ MM3gppCindResponse *r;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error ||
+ !(indicators = mm_3gpp_parse_cind_test_response (result, &error))) {
+ /* unsupported indications */
+ mm_dbg ("Marking indications as unsupported: '%s'", error->message);
+ g_error_free (error);
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ /* Mark CIND as being supported and find the proper indexes for the
+ * indicators. */
+ self->priv->modem_cind_supported = TRUE;
+
+ /* Check if we support signal quality indications */
+ r = g_hash_table_lookup (indicators, "signal");
+ if (r) {
+ self->priv->modem_cind_indicator_signal_quality = mm_3gpp_cind_response_get_index (r);
+ self->priv->modem_cind_min_signal_quality = mm_3gpp_cind_response_get_min (r);
+ self->priv->modem_cind_max_signal_quality = mm_3gpp_cind_response_get_max (r);
+
+ mm_dbg ("Modem supports signal quality indications via CIND at index '%u'"
+ "(min: %u, max: %u)",
+ self->priv->modem_cind_indicator_signal_quality,
+ self->priv->modem_cind_min_signal_quality,
+ self->priv->modem_cind_max_signal_quality);
+ } else
+ self->priv->modem_cind_indicator_signal_quality = CIND_INDICATOR_INVALID;
+
+ /* Check if we support roaming indications */
+ r = g_hash_table_lookup (indicators, "roam");
+ if (r) {
+ self->priv->modem_cind_indicator_roaming = mm_3gpp_cind_response_get_index (r);
+ mm_dbg ("Modem supports roaming indications via CIND at index '%u'",
+ self->priv->modem_cind_indicator_roaming);
+ } else
+ self->priv->modem_cind_indicator_roaming = CIND_INDICATOR_INVALID;
+
+ /* Check if we support service indications */
+ r = g_hash_table_lookup (indicators, "service");
+ if (r) {
+ self->priv->modem_cind_indicator_service = mm_3gpp_cind_response_get_index (r);
+ mm_dbg ("Modem supports service indications via CIND at index '%u'",
+ self->priv->modem_cind_indicator_service);
+ } else
+ self->priv->modem_cind_indicator_service = CIND_INDICATOR_INVALID;
+
+ g_hash_table_destroy (indicators);
+
+ /* Now, keep on setting up the ports */
+ set_unsolicited_events_handlers (self, TRUE);
+
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_setup_unsolicited_events);
+
+ /* Load supported indicators */
+ if (!self->priv->modem_cind_support_checked) {
+ mm_dbg ("Checking indicator support...");
+ self->priv->modem_cind_support_checked = TRUE;
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CIND=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cind_format_check_ready,
+ result);
+ return;
+ }
+
+ /* If supported, go on */
+ if (self->priv->modem_cind_supported)
+ set_unsolicited_events_handlers (self, TRUE);
+
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_cleanup_unsolicited_events);
+
+ /* If supported, go on */
+ if (self->priv->modem_cind_support_checked && self->priv->modem_cind_supported)
+ set_unsolicited_events_handlers (self, FALSE);
+
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+/* Enabling/disabling unsolicited events (3GPP interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ gchar *command;
+ gboolean enable;
+ GSimpleAsyncResult *result;
+ gboolean cmer_primary_done;
+ gboolean cmer_secondary_done;
+} UnsolicitedEventsContext;
+
+static void
+unsolicited_events_context_complete_and_free (UnsolicitedEventsContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx->command);
+ g_free (ctx);
+}
+
+static gboolean
+modem_3gpp_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void run_unsolicited_events_setup (UnsolicitedEventsContext *ctx);
+
+static void
+unsolicited_events_setup_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ UnsolicitedEventsContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!error) {
+ /* Run on next port, if any */
+ run_unsolicited_events_setup (ctx);
+ return;
+ }
+
+ mm_dbg ("Couldn't %s event reporting: '%s'",
+ ctx->enable ? "enable" : "disable",
+ error->message);
+ g_error_free (error);
+ /* Consider this operation complete, ignoring errors */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ unsolicited_events_context_complete_and_free (ctx);
+}
+
+static void
+run_unsolicited_events_setup (UnsolicitedEventsContext *ctx)
+{
+ MMAtSerialPort *port = NULL;
+
+ if (!ctx->cmer_primary_done) {
+ ctx->cmer_primary_done = TRUE;
+ port = mm_base_modem_peek_port_primary (MM_BASE_MODEM (ctx->self));
+ } else if (!ctx->cmer_secondary_done) {
+ ctx->cmer_secondary_done = TRUE;
+ port = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (ctx->self));
+ }
+
+ /* Enable unsolicited events in given port */
+ if (port) {
+ mm_base_modem_at_command_full (MM_BASE_MODEM (ctx->self),
+ port,
+ ctx->command,
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)unsolicited_events_setup_ready,
+ ctx);
+ return;
+ }
+
+ /* If no more ports, we're fully done now */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ unsolicited_events_context_complete_and_free (ctx);
+}
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_enable_unsolicited_events);
+
+ /* If supported, go on */
+ if (self->priv->modem_cind_support_checked && self->priv->modem_cind_supported) {
+ UnsolicitedEventsContext *ctx;
+
+ ctx = g_new0 (UnsolicitedEventsContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->enable = TRUE;
+ ctx->command = g_strdup ("+CMER=3,0,0,1");
+ ctx->result = result;
+ run_unsolicited_events_setup (ctx);
+ return;
+ }
+
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+static void
+modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_disable_unsolicited_events);
+
+ /* If supported, go on */
+ if (self->priv->modem_cind_support_checked && self->priv->modem_cind_supported) {
+ UnsolicitedEventsContext *ctx;
+
+ ctx = g_new0 (UnsolicitedEventsContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->command = g_strdup ("+CMER=0");
+ ctx->result = result;
+ run_unsolicited_events_setup (ctx);
+ return;
+ }
+
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+/* Setting modem charset (Modem interface) */
+
+typedef struct {
+ GSimpleAsyncResult *result;
+ MMModemCharset charset;
+ /* Commands to try in the sequence:
+ * First one with quotes
+ * Second without.
+ * + last NUL */
+ MMBaseModemAtCommand charset_commands[3];
+} SetupCharsetContext;
+
+static void
+setup_charset_context_free (SetupCharsetContext *ctx)
+{
+ g_object_unref (ctx->result);
+ g_free (ctx->charset_commands[0].command);
+ g_free (ctx->charset_commands[1].command);
+ g_free (ctx);
+}
+
+static gboolean
+modem_setup_charset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+current_charset_query_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ SetupCharsetContext *ctx)
+{
+ GError *error = NULL;
+ const gchar *response;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response)
+ g_simple_async_result_take_error (ctx->result, error);
+ else {
+ MMModemCharset current;
+ const gchar *p;
+
+ p = response;
+ if (g_str_has_prefix (p, "+CSCS:"))
+ p += 6;
+ while (*p == ' ')
+ p++;
+
+ current = mm_modem_charset_from_string (p);
+ if (ctx->charset != current)
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Modem failed to change character set to %s",
+ mm_modem_charset_to_string (ctx->charset));
+ else {
+ /* We'll keep track ourselves of the current charset.
+ * TODO: Make this a property so that plugins can also store it. */
+ self->priv->modem_current_charset = current;
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ }
+ }
+
+ g_simple_async_result_complete (ctx->result);
+ setup_charset_context_free (ctx);
+}
+
+static void
+charset_change_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ SetupCharsetContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ g_simple_async_result_complete (ctx->result);
+ setup_charset_context_free (ctx);
+ return;
+ }
+
+ /* Check whether we did properly set the charset */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CSCS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)current_charset_query_ready,
+ ctx);
+}
+
+static void
+modem_setup_charset (MMIfaceModem *self,
+ MMModemCharset charset,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SetupCharsetContext *ctx;
+ const gchar *charset_str;
+
+ /* NOTE: we already notified that CDMA-only modems couldn't load supported
+ * charsets, so we'll never get here in such a case */
+ g_assert (mm_iface_modem_is_cdma_only (self) == FALSE);
+
+ /* Build charset string to use */
+ charset_str = mm_modem_charset_to_string (charset);
+ if (!charset_str) {
+ g_simple_async_report_error_in_idle (G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unhandled character set 0x%X",
+ charset);
+ return;
+ }
+
+ /* Setup context, including commands to try */
+ ctx = g_new0 (SetupCharsetContext, 1);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_setup_charset);
+ ctx->charset = charset;
+ /* First try, with quotes */
+ ctx->charset_commands[0].command = g_strdup_printf ("+CSCS=\"%s\"", charset_str);
+ ctx->charset_commands[0].timeout = 3;
+ ctx->charset_commands[0].allow_cached = FALSE;
+ ctx->charset_commands[0].response_processor = mm_base_modem_response_processor_no_result;
+ /* Second try.
+ * Some modems puke if you include the quotes around the character
+ * set name, so lets try it again without them.
+ */
+ ctx->charset_commands[1].command = g_strdup_printf ("+CSCS=%s", charset_str);
+ ctx->charset_commands[1].timeout = 3;
+ ctx->charset_commands[1].allow_cached = FALSE;
+ ctx->charset_commands[1].response_processor = mm_base_modem_response_processor_no_result;
+
+ /* Launch sequence */
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ ctx->charset_commands,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ (GAsyncReadyCallback)charset_change_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Loading supported charsets (Modem interface) */
+
+static MMModemCharset
+modem_load_supported_charsets_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return MM_MODEM_CHARSET_UNKNOWN;
+
+ return GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (res)));
+}
+
+static void
+cscs_format_check_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN;
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_simple_async_result_take_error (simple, error);
+ else if (!mm_3gpp_parse_cscs_test_response (response, &charsets))
+ g_simple_async_result_set_error (
+ simple,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse the supported character "
+ "sets response");
+ else
+ g_simple_async_result_set_op_res_gpointer (simple,
+ GUINT_TO_POINTER (charsets),
+ NULL);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_load_supported_charsets (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_load_supported_charsets);
+
+ /* CDMA-only modems don't need this */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ mm_dbg ("Skipping supported charset loading in CDMA-only modem...");
+ g_simple_async_result_set_op_res_gpointer (result,
+ GUINT_TO_POINTER (MM_MODEM_CHARSET_UNKNOWN),
+ NULL);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CSCS=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cscs_format_check_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* configuring flow control (Modem interface) */
+
+static gboolean
+modem_setup_flow_control_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* Completely ignore errors */
+ return TRUE;
+}
+
+static void
+modem_setup_flow_control (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ /* By default, try to set XOFF/XON flow control */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+IFC=1,1",
+ 3,
+ FALSE,
+ NULL,
+ NULL);
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_setup_flow_control);
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+/* Power state loading (Modem interface) */
+
+static MMModemPowerState
+load_power_state_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return MM_MODEM_POWER_STATE_UNKNOWN;
+
+ return (MMModemPowerState)GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
+}
+
+static void
+cfun_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ const gchar *result;
+ guint state;
+ GError *error = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!result) {
+ g_simple_async_result_take_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ /* Parse power state reply */
+ result = mm_strip_tag (result, "+CFUN:");
+ if (!mm_get_uint_from_str (result, &state)) {
+ g_simple_async_result_set_error (simple,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse +CFUN? response '%s'",
+ result);
+ } else {
+ switch (state) {
+ case 0:
+ g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (MM_MODEM_POWER_STATE_OFF), NULL);
+ break;
+ case 1:
+ g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (MM_MODEM_POWER_STATE_ON), NULL);
+ break;
+ case 4:
+ g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (MM_MODEM_POWER_STATE_LOW), NULL);
+ break;
+ default:
+ g_simple_async_result_set_error (simple,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unhandled power state: '%u'",
+ state);
+ break;
+ }
+ }
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+load_power_state (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ load_power_state);
+
+ /* CDMA-only modems don't need this */
+ if (mm_iface_modem_is_cdma_only (self)) {
+ mm_dbg ("Assuming full power state in CDMA-only modem...");
+ g_simple_async_result_set_op_res_gpointer (result, GUINT_TO_POINTER (MM_MODEM_POWER_STATE_ON), NULL);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ mm_dbg ("loading power state...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cfun_query_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Powering up the modem (Modem interface) */
+
+static gboolean
+modem_power_up_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ /* By default, errors in the power up command are ignored.
+ * Plugins wanting to treat power up errors should subclass the power up
+ * handling. */
+ return TRUE;
+}
+
+static void
+modem_power_up (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ /* CDMA-only modems don't need this */
+ if (mm_iface_modem_is_cdma_only (self))
+ mm_dbg ("Skipping Power-up in CDMA-only modem...");
+ else
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CFUN=1",
+ 5,
+ FALSE,
+ NULL,
+ NULL);
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_power_up);
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+/* Sending a command to the modem (Modem interface) */
+
+static const gchar *
+modem_command_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return mm_base_modem_at_command_finish (MM_BASE_MODEM (self),
+ res,
+ error);
+}
+
+static void
+modem_command (MMIfaceModem *self,
+ const gchar *cmd,
+ guint timeout,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self), cmd, timeout,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* IMEI loading (3GPP interface) */
+
+static gchar *
+modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *imei = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ result = mm_strip_tag (result, "+CGSN:");
+ mm_parse_gsn (result, &imei, NULL, NULL);
+ mm_dbg ("loaded IMEI: %s", imei);
+ return imei;
+}
+
+static void
+modem_3gpp_load_imei (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading IMEI...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CGSN",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Facility locks status loading (3GPP interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ guint current;
+ MMModem3gppFacility facilities;
+ MMModem3gppFacility locks;
+} LoadEnabledFacilityLocksContext;
+
+static void get_next_facility_lock_status (LoadEnabledFacilityLocksContext *ctx);
+
+static void
+load_enabled_facility_locks_context_complete_and_free (LoadEnabledFacilityLocksContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static MMModem3gppFacility
+modem_3gpp_load_enabled_facility_locks_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return MM_MODEM_3GPP_FACILITY_NONE;
+
+ return ((MMModem3gppFacility) GPOINTER_TO_UINT (
+ g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))));
+}
+
+static void
+clck_single_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ LoadEnabledFacilityLocksContext *ctx)
+{
+ const gchar *response;
+ gboolean enabled = FALSE;
+
+ response = mm_base_modem_at_command_finish (self, res, NULL);
+ if (response &&
+ mm_3gpp_parse_clck_write_response (response, &enabled) &&
+ enabled) {
+ ctx->locks |= (1 << ctx->current);
+ } else {
+ /* On errors, we'll just assume disabled */
+ ctx->locks &= ~(1 << ctx->current);
+ }
+
+ /* And go on with the next one */
+ ctx->current++;
+ get_next_facility_lock_status (ctx);
+}
+
+static void
+get_next_facility_lock_status (LoadEnabledFacilityLocksContext *ctx)
+{
+ guint i;
+
+ for (i = ctx->current; i < sizeof (MMModem3gppFacility) * 8; i++) {
+ guint32 facility = 1 << i;
+
+ /* Found the next one to query! */
+ if (ctx->facilities & facility) {
+ gchar *cmd;
+
+ /* Keep the current one */
+ ctx->current = i;
+
+ /* Query current */
+ cmd = g_strdup_printf ("+CLCK=\"%s\",2",
+ mm_3gpp_facility_to_acronym (facility));
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)clck_single_query_ready,
+ ctx);
+ g_free (cmd);
+ return;
+ }
+ }
+
+ /* No more facilities to query, all done */
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ GUINT_TO_POINTER (ctx->locks),
+ NULL);
+ load_enabled_facility_locks_context_complete_and_free (ctx);
+}
+
+static void
+clck_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ LoadEnabledFacilityLocksContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_simple_async_result_take_error (ctx->result, error);
+ load_enabled_facility_locks_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (!mm_3gpp_parse_clck_test_response (response, &ctx->facilities)) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse list of available lock facilities: '%s'",
+ response);
+ load_enabled_facility_locks_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Ignore facility locks specified by the plugins */
+ if (MM_BROADBAND_MODEM (self)->priv->modem_3gpp_ignored_facility_locks) {
+ gchar *str;
+
+ str = mm_modem_3gpp_facility_build_string_from_mask (MM_BROADBAND_MODEM (self)->priv->modem_3gpp_ignored_facility_locks);
+ mm_dbg ("Ignoring facility locks: '%s'", str);
+ g_free (str);
+
+ ctx->facilities &= ~MM_BROADBAND_MODEM (self)->priv->modem_3gpp_ignored_facility_locks;
+ }
+
+ /* Go on... */
+ get_next_facility_lock_status (ctx);
+}
+
+static void
+modem_3gpp_load_enabled_facility_locks (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LoadEnabledFacilityLocksContext *ctx;
+
+ ctx = g_new (LoadEnabledFacilityLocksContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_load_enabled_facility_locks);
+ ctx->facilities = MM_MODEM_3GPP_FACILITY_NONE;
+ ctx->locks = MM_MODEM_3GPP_FACILITY_NONE;
+ ctx->current = 0;
+
+ mm_dbg ("loading enabled facility locks...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CLCK=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)clck_test_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Operator Code loading (3GPP interface) */
+
+static gchar *
+modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *operator_code;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ operator_code = mm_3gpp_parse_operator (result, MM_MODEM_CHARSET_UNKNOWN);
+ if (operator_code)
+ mm_dbg ("loaded Operator Code: %s", operator_code);
+
+ return operator_code;
+}
+
+static void
+modem_3gpp_load_operator_code (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading Operator Code...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS=3,2;+COPS?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Operator Name loading (3GPP interface) */
+
+static gchar *
+modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *operator_name;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ operator_name = mm_3gpp_parse_operator (result, MM_BROADBAND_MODEM (self)->priv->modem_current_charset);
+ if (operator_name)
+ mm_dbg ("loaded Operator Name: %s", operator_name);
+
+ return operator_name;
+}
+
+static void
+modem_3gpp_load_operator_name (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading Operator Name...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS=3,0;+COPS?",
+ 3,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Unsolicited registration messages handling (3GPP interface) */
+
+static gboolean
+modem_3gpp_setup_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+registration_state_changed (MMAtSerialPort *port,
+ GMatchInfo *match_info,
+ MMBroadbandModem *self)
+{
+ MMModem3gppRegistrationState state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
+ gulong lac = 0, cell_id = 0;
+ MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ gboolean cgreg = FALSE;
+ gboolean cereg = FALSE;
+ GError *error = NULL;
+
+ if (!mm_3gpp_parse_creg_response (match_info,
+ &state,
+ &lac,
+ &cell_id,
+ &act,
+ &cgreg,
+ &cereg,
+ &error)) {
+ mm_warn ("error parsing unsolicited registration: %s",
+ error && error->message ? error->message : "(unknown)");
+ g_clear_error (&error);
+ return;
+ }
+
+ /* Report new registration state */
+ if (cgreg)
+ mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), state);
+ else if (cereg)
+ mm_iface_modem_3gpp_update_eps_registration_state (MM_IFACE_MODEM_3GPP (self), state);
+ else
+ mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), state);
+
+ /* Only update access technologies from CREG/CGREG response if the modem
+ * doesn't have custom commands for access technology loading, otherwise
+ * we fight with the custom commands. Plus CREG/CGREG access technologies
+ * don't have fine-grained distinction between HSxPA or GPRS/EDGE, etc.
+ */
+ if (MM_IFACE_MODEM_GET_INTERFACE (self)->load_access_technologies == modem_load_access_technologies ||
+ MM_IFACE_MODEM_GET_INTERFACE (self)->load_access_technologies == NULL)
+ mm_iface_modem_3gpp_update_access_technologies (MM_IFACE_MODEM_3GPP (self), act);
+
+ mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), lac, cell_id);
+}
+
+static void
+modem_3gpp_setup_unsolicited_registration_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ MMAtSerialPort *ports[2];
+ GPtrArray *array;
+ guint i;
+ guint j;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_setup_unsolicited_registration_events);
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Set up CREG unsolicited message handlers in both ports */
+ array = mm_3gpp_creg_regex_get (FALSE);
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ mm_dbg ("(%s) setting up 3GPP unsolicited registration messages handlers",
+ mm_port_get_device (MM_PORT (ports[i])));
+ for (j = 0; j < array->len; j++) {
+ mm_at_serial_port_add_unsolicited_msg_handler (
+ MM_AT_SERIAL_PORT (ports[i]),
+ (GRegex *) g_ptr_array_index (array, j),
+ (MMAtSerialUnsolicitedMsgFn)registration_state_changed,
+ self,
+ NULL);
+ }
+ }
+ mm_3gpp_creg_regex_destroy (array);
+
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+/* Unsolicited registration messages cleaning up (3GPP interface) */
+
+static gboolean
+modem_3gpp_cleanup_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+modem_3gpp_cleanup_unsolicited_registration_events (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ MMAtSerialPort *ports[2];
+ GPtrArray *array;
+ guint i;
+ guint j;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_cleanup_unsolicited_registration_events);
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Set up CREG unsolicited message handlers in both ports */
+ array = mm_3gpp_creg_regex_get (FALSE);
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ mm_dbg ("(%s) cleaning up unsolicited registration messages handlers",
+ mm_port_get_device (MM_PORT (ports[i])));
+
+ for (j = 0; j < array->len; j++) {
+ mm_at_serial_port_add_unsolicited_msg_handler (
+ MM_AT_SERIAL_PORT (ports[i]),
+ (GRegex *) g_ptr_array_index (array, j),
+ NULL,
+ NULL,
+ NULL);
+ }
+ }
+ mm_3gpp_creg_regex_destroy (array);
+
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+/* Scan networks (3GPP interface) */
+
+static GList *
+modem_3gpp_scan_networks_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ return mm_3gpp_parse_cops_test_response (result, error);
+}
+
+static void
+modem_3gpp_scan_networks (MMIfaceModem3gpp *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+COPS=?",
+ 120,
+ FALSE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Register in network (3GPP interface) */
+
+static gboolean
+modem_3gpp_register_in_network_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+modem_3gpp_register_in_network (MMIfaceModem3gpp *self,
+ const gchar *operator_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ gchar *command;
+
+ /* If the user sent a specific network to use, lock it in. */
+ if (operator_id)
+ command = g_strdup_printf ("+COPS=1,2,\"%s\"", operator_id);
+ /* If no specific network was given, and the modem is not registered and not
+ * searching, kick it to search for a network. Also do auto registration if
+ * the modem had been set to manual registration last time but now is not.
+ */
+ else
+ /* Note that '+COPS=0,,' (same but with commas) won't work in some Nokia
+ * phones */
+ command = g_strdup ("+COPS=0");
+
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), NULL),
+ command,
+ 120,
+ FALSE,
+ FALSE, /* raw */
+ cancellable,
+ callback,
+ user_data);
+ g_free (command);
+}
+
+/*****************************************************************************/
+/* Registration checks (3GPP interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ gboolean cs_supported;
+ gboolean ps_supported;
+ gboolean eps_supported;
+ gboolean run_cs;
+ gboolean run_ps;
+ gboolean run_eps;
+ gboolean running_cs;
+ gboolean running_ps;
+ gboolean running_eps;
+ GError *cs_error;
+ GError *ps_error;
+ GError *eps_error;
+} RunRegistrationChecksContext;
+
+static void
+run_registration_checks_context_complete_and_free (RunRegistrationChecksContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ if (ctx->cs_error)
+ g_error_free (ctx->cs_error);
+ if (ctx->ps_error)
+ g_error_free (ctx->ps_error);
+ if (ctx->eps_error)
+ g_error_free (ctx->eps_error);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+modem_3gpp_run_registration_checks_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void run_registration_checks_context_step (RunRegistrationChecksContext *ctx);
+
+static void
+registration_status_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ RunRegistrationChecksContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+ GMatchInfo *match_info;
+ guint i;
+ gboolean parsed;
+ gboolean cgreg;
+ gboolean cereg;
+ MMModem3gppRegistrationState state;
+ MMModemAccessTechnology act;
+ gulong lac;
+ gulong cid;
+
+ /* Only one must be running */
+ g_assert ((ctx->running_cs ? 1 : 0) +
+ (ctx->running_ps ? 1 : 0) +
+ (ctx->running_eps ? 1 : 0) == 1);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (!response) {
+ g_assert (error != NULL);
+ if (ctx->running_cs)
+ ctx->cs_error = error;
+ else if (ctx->running_ps)
+ ctx->ps_error = error;
+ else
+ ctx->eps_error = error;
+
+ run_registration_checks_context_step (ctx);
+ return;
+ }
+
+ /* Unsolicited registration status handlers will usually process the
+ * response for us, but just in case they don't, do that here.
+ */
+ if (!response[0]) {
+ /* Done */
+ run_registration_checks_context_step (ctx);
+ return;
+ }
+
+ /* Try to match the response */
+ for (i = 0;
+ i < self->priv->modem_3gpp_registration_regex->len;
+ i++) {
+ if (g_regex_match ((GRegex *)g_ptr_array_index (
+ self->priv->modem_3gpp_registration_regex, i),
+ response,
+ 0,
+ &match_info))
+ break;
+ g_match_info_free (match_info);
+ match_info = NULL;
+ }
+
+ if (!match_info) {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unknown registration status response: '%s'",
+ response);
+ if (ctx->running_cs)
+ ctx->cs_error = error;
+ else if (ctx->running_ps)
+ ctx->ps_error = error;
+ else
+ ctx->eps_error = error;
+
+ run_registration_checks_context_step (ctx);
+ return;
+ }
+
+ cgreg = FALSE;
+ cereg = FALSE;
+ state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
+ act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
+ lac = 0;
+ cid = 0;
+ parsed = mm_3gpp_parse_creg_response (match_info,
+ &state,
+ &lac,
+ &cid,
+ &act,
+ &cgreg,
+ &cereg,
+ &error);
+ g_match_info_free (match_info);
+
+ if (!parsed) {
+ if (!error)
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Error parsing registration response: '%s'",
+ response);
+ if (ctx->running_cs)
+ ctx->cs_error = error;
+ else if (ctx->running_ps)
+ ctx->ps_error = error;
+ else
+ ctx->eps_error = error;
+ run_registration_checks_context_step (ctx);
+ return;
+ }
+
+ /* Report new registration state */
+ if (cgreg) {
+ if (ctx->running_cs)
+ mm_dbg ("Got PS registration state when checking CS registration state");
+ else if (ctx->running_eps)
+ mm_dbg ("Got PS registration state when checking EPS registration state");
+ mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), state);
+ } else if (cereg) {
+ if (ctx->running_cs)
+ mm_dbg ("Got EPS registration state when checking CS registration state");
+ else if (ctx->running_ps)
+ mm_dbg ("Got EPS registration state when checking PS registration state");
+ mm_iface_modem_3gpp_update_eps_registration_state (MM_IFACE_MODEM_3GPP (self), state);
+ } else {
+ if (ctx->running_ps)
+ mm_dbg ("Got CS registration state when checking PS registration state");
+ else if (ctx->running_eps)
+ mm_dbg ("Got CS registration state when checking EPS registration state");
+ mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), state);
+ }
+
+ mm_iface_modem_3gpp_update_access_technologies (MM_IFACE_MODEM_3GPP (self), act);
+ mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), lac, cid);
+
+ run_registration_checks_context_step (ctx);
+}
+
+static void
+run_registration_checks_context_step (RunRegistrationChecksContext *ctx)
+{
+ ctx->running_cs = FALSE;
+ ctx->running_ps = FALSE;
+ ctx->running_eps = FALSE;
+
+ if (ctx->run_cs) {
+ ctx->running_cs = TRUE;
+ ctx->run_cs = FALSE;
+ /* Check current CS-registration state. */
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ "+CREG?",
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)registration_status_check_ready,
+ ctx);
+ return;
+ }
+
+ if (ctx->run_ps) {
+ ctx->running_ps = TRUE;
+ ctx->run_ps = FALSE;
+ /* Check current PS-registration state. */
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ "+CGREG?",
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)registration_status_check_ready,
+ ctx);
+ return;
+ }
+
+ if (ctx->run_eps) {
+ ctx->running_eps = TRUE;
+ ctx->run_eps = FALSE;
+ /* Check current EPS-registration state. */
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ "+CEREG?",
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)registration_status_check_ready,
+ ctx);
+ return;
+ }
+
+ /* If all run checks returned errors we fail */
+ if ((ctx->cs_supported || ctx->ps_supported || ctx->eps_supported) &&
+ (!ctx->cs_supported || ctx->cs_error) &&
+ (!ctx->ps_supported || ctx->ps_error) &&
+ (!ctx->eps_supported || ctx->eps_error)) {
+ /* Prefer the EPS, and then PS error if any */
+ if (ctx->eps_error) {
+ g_simple_async_result_set_from_error (ctx->result, ctx->eps_error);
+ ctx->eps_error = NULL;
+ } else if (ctx->ps_error) {
+ g_simple_async_result_set_from_error (ctx->result, ctx->ps_error);
+ ctx->ps_error = NULL;
+ } else if (ctx->cs_error) {
+ g_simple_async_result_set_from_error (ctx->result, ctx->cs_error);
+ ctx->cs_error = NULL;
+ } else
+ g_assert_not_reached ();
+ } else
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+
+ run_registration_checks_context_complete_and_free (ctx);
+}
+
+static void
+modem_3gpp_run_registration_checks (MMIfaceModem3gpp *self,
+ gboolean cs_supported,
+ gboolean ps_supported,
+ gboolean eps_supported,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ RunRegistrationChecksContext *ctx;
+
+ ctx = g_new0 (RunRegistrationChecksContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_run_registration_checks);
+ ctx->cs_supported = cs_supported;
+ ctx->ps_supported = ps_supported;
+ ctx->eps_supported = eps_supported;
+ ctx->run_cs = cs_supported;
+ ctx->run_ps = ps_supported;
+ ctx->run_eps = eps_supported;
+
+ run_registration_checks_context_step (ctx);
+}
+
+/*****************************************************************************/
+/* Enable/Disable unsolicited registration events (3GPP interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ gboolean enable; /* TRUE for enabling, FALSE for disabling */
+ gboolean run_cs;
+ gboolean run_ps;
+ gboolean run_eps;
+ gboolean running_cs;
+ gboolean running_ps;
+ gboolean running_eps;
+ GError *cs_error;
+ GError *ps_error;
+ GError *eps_error;
+ gboolean secondary_sequence;
+ gboolean secondary_done;
+} UnsolicitedRegistrationEventsContext;
+
+static void
+unsolicited_registration_events_context_complete_and_free (UnsolicitedRegistrationEventsContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ if (ctx->cs_error)
+ g_error_free (ctx->cs_error);
+ if (ctx->ps_error)
+ g_error_free (ctx->ps_error);
+ if (ctx->eps_error)
+ g_error_free (ctx->eps_error);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static UnsolicitedRegistrationEventsContext *
+unsolicited_registration_events_context_new (MMBroadbandModem *self,
+ gboolean enable,
+ gboolean cs_supported,
+ gboolean ps_supported,
+ gboolean eps_supported,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ UnsolicitedRegistrationEventsContext *ctx;
+
+ ctx = g_new0 (UnsolicitedRegistrationEventsContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ unsolicited_registration_events_context_new);
+ ctx->enable = enable;
+ ctx->run_cs = cs_supported;
+ ctx->run_ps = ps_supported;
+ ctx->run_eps = eps_supported;
+
+ return ctx;
+}
+
+static gboolean
+modem_3gpp_enable_disable_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static gboolean
+parse_registration_setup_reply (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ /* If error, try next command */
+ if (error)
+ return FALSE;
+
+ /* Set COMMAND as result! */
+ *result = g_variant_new_string (command);
+ return TRUE;
+}
+
+static const MMBaseModemAtCommand cs_registration_sequence[] = {
+ /* Enable unsolicited registration notifications in CS network, with location */
+ { "+CREG=2", 3, FALSE, parse_registration_setup_reply },
+ /* Enable unsolicited registration notifications in CS network, without location */
+ { "+CREG=1", 3, FALSE, parse_registration_setup_reply },
+ { NULL }
+};
+
+static const MMBaseModemAtCommand cs_unregistration_sequence[] = {
+ /* Disable unsolicited registration notifications in CS network */
+ { "+CREG=0", 3, FALSE, parse_registration_setup_reply },
+ { NULL }
+};
+
+static const MMBaseModemAtCommand ps_registration_sequence[] = {
+ /* Enable unsolicited registration notifications in PS network, with location */
+ { "+CGREG=2", 3, FALSE, parse_registration_setup_reply },
+ /* Enable unsolicited registration notifications in PS network, without location */
+ { "+CGREG=1", 3, FALSE, parse_registration_setup_reply },
+ { NULL }
+};
+
+static const MMBaseModemAtCommand ps_unregistration_sequence[] = {
+ /* Disable unsolicited registration notifications in PS network */
+ { "+CGREG=0", 3, FALSE, parse_registration_setup_reply },
+ { NULL }
+};
+
+static const MMBaseModemAtCommand eps_registration_sequence[] = {
+ /* Enable unsolicited registration notifications in EPS network, with location */
+ { "+CEREG=2", 3, FALSE, parse_registration_setup_reply },
+ /* Enable unsolicited registration notifications in EPS network, without location */
+ { "+CEREG=1", 3, FALSE, parse_registration_setup_reply },
+ { NULL }
+};
+
+static const MMBaseModemAtCommand eps_unregistration_sequence[] = {
+ /* Disable unsolicited registration notifications in PS network */
+ { "+CEREG=0", 3, FALSE, parse_registration_setup_reply },
+ { NULL }
+};
+
+static void unsolicited_registration_events_context_step (UnsolicitedRegistrationEventsContext *ctx);
+
+static void
+unsolicited_registration_events_sequence_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ UnsolicitedRegistrationEventsContext *ctx)
+{
+ GError *error = NULL;
+ GVariant *command;
+ MMAtSerialPort *secondary;
+
+ /* Only one must be running */
+ g_assert ((ctx->running_cs ? 1 : 0) +
+ (ctx->running_ps ? 1 : 0) +
+ (ctx->running_eps ? 1 : 0) == 1);
+
+ if (ctx->secondary_done) {
+ if (ctx->secondary_sequence)
+ mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, &error);
+ else
+ mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error);
+
+ if (error) {
+ mm_dbg ("%s unsolicited registration events in secondary port failed: '%s'",
+ ctx->enable ? "Enabling" : "Disabling",
+ error->message);
+ /* Keep errors reported */
+ if (ctx->running_cs && !ctx->cs_error)
+ ctx->cs_error = error;
+ else if (ctx->running_ps && !ctx->ps_error)
+ ctx->ps_error = error;
+ else if (ctx->running_eps && !ctx->eps_error)
+ ctx->eps_error = error;
+ else
+ g_error_free (error);
+ } else {
+ /* If successful in secondary port, cleanup primary error if any */
+ if (ctx->running_cs && ctx->cs_error) {
+ g_error_free (ctx->cs_error);
+ ctx->cs_error = NULL;
+ }
+ else if (ctx->running_ps && ctx->ps_error) {
+ g_error_free (ctx->ps_error);
+ ctx->ps_error = NULL;
+ }
+ else if (ctx->running_eps && ctx->eps_error) {
+ g_error_free (ctx->eps_error);
+ ctx->eps_error = NULL;
+ }
+ }
+
+ /* Done with primary and secondary, keep on */
+ unsolicited_registration_events_context_step (ctx);
+ return;
+ }
+
+ /* We just run the sequence in the primary port */
+ command = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, &error);
+ if (!command) {
+ if (!error)
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "AT sequence failed");
+ mm_dbg ("%s unsolicited registration events in primary port failed: '%s'",
+ ctx->enable ? "Enabling" : "Disabling",
+ error->message);
+ /* Keep errors reported */
+ if (ctx->running_cs)
+ ctx->cs_error = error;
+ else if (ctx->running_ps)
+ ctx->ps_error = error;
+ else
+ ctx->eps_error = error;
+ /* Even if primary failed, go on and try to enable in secondary port */
+ }
+
+ secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+ if (secondary) {
+ const MMBaseModemAtCommand *registration_sequence = NULL;
+
+ ctx->secondary_done = TRUE;
+
+ /* Now use the same registration setup in secondary port, if any */
+ if (command) {
+ mm_base_modem_at_command_full (
+ MM_BASE_MODEM (self),
+ secondary,
+ g_variant_get_string (command, NULL),
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)unsolicited_registration_events_sequence_ready,
+ ctx);
+ return;
+ }
+
+ /* If primary failed, run the whole sequence in secondary */
+ ctx->secondary_sequence = TRUE;
+ if (ctx->running_cs)
+ registration_sequence = ctx->enable ? cs_registration_sequence : cs_unregistration_sequence;
+ else if (ctx->running_ps)
+ registration_sequence = ctx->enable ? ps_registration_sequence : ps_unregistration_sequence;
+ else
+ registration_sequence = ctx->enable ? eps_registration_sequence : eps_unregistration_sequence;
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (self),
+ secondary,
+ registration_sequence,
+ NULL, /* response processor context */
+ NULL, /* response processor context free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)unsolicited_registration_events_sequence_ready,
+ ctx);
+ return;
+ }
+
+ /* We're done */
+ unsolicited_registration_events_context_step (ctx);
+}
+
+static void
+unsolicited_registration_events_context_step (UnsolicitedRegistrationEventsContext *ctx)
+{
+ ctx->running_cs = FALSE;
+ ctx->running_ps = FALSE;
+ ctx->running_eps = FALSE;
+ ctx->secondary_done = FALSE;
+
+ if (ctx->run_cs) {
+ ctx->running_cs = TRUE;
+ ctx->run_cs = FALSE;
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (ctx->self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (ctx->self)),
+ ctx->enable ? cs_registration_sequence : cs_unregistration_sequence,
+ NULL, /* response processor context */
+ NULL, /* response processor context free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)unsolicited_registration_events_sequence_ready,
+ ctx);
+ return;
+ }
+
+ if (ctx->run_ps) {
+ ctx->running_ps = TRUE;
+ ctx->run_ps = FALSE;
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (ctx->self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (ctx->self)),
+ ctx->enable ? ps_registration_sequence : ps_unregistration_sequence,
+ NULL, /* response processor context */
+ NULL, /* response processor context free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)unsolicited_registration_events_sequence_ready,
+ ctx);
+ return;
+ }
+
+ if (ctx->run_eps) {
+ ctx->running_eps = TRUE;
+ ctx->run_eps = FALSE;
+ mm_base_modem_at_sequence_full (
+ MM_BASE_MODEM (ctx->self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (ctx->self)),
+ ctx->enable ? eps_registration_sequence : eps_unregistration_sequence,
+ NULL, /* response processor context */
+ NULL, /* response processor context free */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)unsolicited_registration_events_sequence_ready,
+ ctx);
+ return;
+ }
+
+ /* All done!
+ * If we have any error reported, we'll propagate it. EPS errors take
+ * precendence over PS errors and PS errors take precendence over CS errors. */
+ if (ctx->eps_error) {
+ g_simple_async_result_take_error (ctx->result, ctx->eps_error);
+ ctx->eps_error = NULL;
+ } else if (ctx->ps_error) {
+ g_simple_async_result_take_error (ctx->result, ctx->ps_error);
+ ctx->ps_error = NULL;
+ } else if (ctx->cs_error) {
+ g_simple_async_result_take_error (ctx->result, ctx->cs_error);
+ ctx->cs_error = NULL;
+ } else
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ unsolicited_registration_events_context_complete_and_free (ctx);
+}
+
+static void
+modem_3gpp_disable_unsolicited_registration_events (MMIfaceModem3gpp *self,
+ gboolean cs_supported,
+ gboolean ps_supported,
+ gboolean eps_supported,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ unsolicited_registration_events_context_step (
+ unsolicited_registration_events_context_new (MM_BROADBAND_MODEM (self),
+ FALSE,
+ cs_supported,
+ ps_supported,
+ eps_supported,
+ callback,
+ user_data));
+}
+
+static void
+modem_3gpp_enable_unsolicited_registration_events (MMIfaceModem3gpp *self,
+ gboolean cs_supported,
+ gboolean ps_supported,
+ gboolean eps_supported,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ unsolicited_registration_events_context_step (
+ unsolicited_registration_events_context_new (MM_BROADBAND_MODEM (self),
+ TRUE,
+ cs_supported,
+ ps_supported,
+ eps_supported,
+ callback,
+ user_data));
+}
+
+/*****************************************************************************/
+/* Cancel USSD (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_cancel_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+cancel_command_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_simple_async_result_take_error (simple, error);
+ else
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+
+ /* Complete the pending action, if any */
+ if (self->priv->pending_ussd_action) {
+ g_simple_async_result_set_error (self->priv->pending_ussd_action,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "USSD session was cancelled");
+ g_simple_async_result_complete_in_idle (self->priv->pending_ussd_action);
+ g_object_unref (self->priv->pending_ussd_action);
+ self->priv->pending_ussd_action = NULL;
+ }
+
+ mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self),
+ MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE);
+}
+
+static void
+modem_3gpp_ussd_cancel (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_ussd_cancel);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CUSD=2",
+ 10,
+ TRUE,
+ (GAsyncReadyCallback)cancel_command_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Send command (3GPP/USSD interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ gchar *command;
+ gboolean current_is_unencoded;
+ gboolean encoded_used;
+ gboolean unencoded_used;
+} Modem3gppUssdSendContext;
+
+static void
+modem_3gpp_ussd_send_context_complete_and_free (Modem3gppUssdSendContext *ctx)
+{
+ /* We check for result, as we may have already set it in
+ * priv->pending_ussd_request */
+ if (ctx->result) {
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ }
+ g_object_unref (ctx->self);
+ g_free (ctx->command);
+ g_slice_free (Modem3gppUssdSendContext, ctx);
+}
+
+static const gchar *
+modem_3gpp_ussd_send_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return NULL;
+
+ /* We can return the string as constant because it is owned by the async
+ * result, which will be valid during the whole call of its callback, which
+ * is when we're actually calling finish() */
+ return (const gchar *)g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+}
+
+static void modem_3gpp_ussd_context_step (Modem3gppUssdSendContext *ctx);
+
+static void cusd_process_string (MMBroadbandModem *self,
+ const gchar *str);
+
+static void
+ussd_send_command_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ Modem3gppUssdSendContext *ctx)
+{
+ GError *error = NULL;
+ const gchar *reply;
+
+ g_assert (ctx->result == NULL);
+
+ reply = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ /* Some immediate error happened when sending the USSD request */
+ mm_dbg ("Error sending USSD request: '%s'", error->message);
+ g_error_free (error);
+
+ if (self->priv->pending_ussd_action) {
+ /* Recover result */
+ ctx->result = self->priv->pending_ussd_action;
+ self->priv->pending_ussd_action = NULL;
+ modem_3gpp_ussd_context_step (ctx);
+ return;
+ }
+
+ /* So the USSD action was completed already... */
+ mm_dbg ("USSD action already completed via URCs");
+ modem_3gpp_ussd_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Cache the hint for the next time we send something */
+ if (!ctx->self->priv->use_unencoded_ussd &&
+ ctx->current_is_unencoded) {
+ mm_dbg ("Will assume we want unencoded USSD commands");
+ ctx->self->priv->use_unencoded_ussd = TRUE;
+ } else if (ctx->self->priv->use_unencoded_ussd &&
+ !ctx->current_is_unencoded) {
+ mm_dbg ("Will assume we want encoded USSD commands");
+ ctx->self->priv->use_unencoded_ussd = FALSE;
+ }
+
+ if (!self->priv->pending_ussd_action)
+ mm_dbg ("USSD operation finished already via URCs");
+ else if (reply && reply[0]) {
+ reply = mm_strip_tag (reply, "+CUSD:");
+ cusd_process_string (ctx->self, reply);
+ }
+
+ modem_3gpp_ussd_send_context_complete_and_free (ctx);
+}
+
+static void
+modem_3gpp_ussd_context_send_encoded (Modem3gppUssdSendContext *ctx)
+{
+ gchar *at_command = NULL;
+ GError *error = NULL;
+ guint scheme = 0;
+ gchar *encoded;
+
+ /* Encode USSD command */
+ encoded = mm_iface_modem_3gpp_ussd_encode (MM_IFACE_MODEM_3GPP_USSD (ctx->self),
+ ctx->command,
+ &scheme,
+ &error);
+ if (!encoded) {
+ mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (ctx->self),
+ MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE);
+ g_simple_async_result_take_error (ctx->result, error);
+ modem_3gpp_ussd_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Build AT command */
+ ctx->encoded_used = TRUE;
+ ctx->current_is_unencoded = FALSE;
+ at_command = g_strdup_printf ("+CUSD=1,\"%s\",%d", encoded, scheme);
+ g_free (encoded);
+
+ /* Cache the action, as it may be completed via URCs.
+ * There shouldn't be any previous action pending. */
+ g_warn_if_fail (ctx->self->priv->pending_ussd_action == NULL);
+ ctx->self->priv->pending_ussd_action = ctx->result;
+ ctx->result = NULL;
+
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ at_command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)ussd_send_command_ready,
+ ctx);
+ g_free (at_command);
+}
+
+static void
+modem_3gpp_ussd_context_send_unencoded (Modem3gppUssdSendContext *ctx)
+{
+ gchar *at_command = NULL;
+
+ /* Build AT command with action unencoded */
+ ctx->unencoded_used = TRUE;
+ ctx->current_is_unencoded = TRUE;
+ at_command = g_strdup_printf ("+CUSD=1,\"%s\",%d",
+ ctx->command,
+ MM_MODEM_GSM_USSD_SCHEME_7BIT);
+
+ /* Cache the action, as it may be completed via URCs.
+ * There shouldn't be any previous action pending. */
+ g_warn_if_fail (ctx->self->priv->pending_ussd_action == NULL);
+ ctx->self->priv->pending_ussd_action = ctx->result;
+ ctx->result = NULL;
+
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ at_command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)ussd_send_command_ready,
+ ctx);
+ g_free (at_command);
+}
+
+static void
+modem_3gpp_ussd_context_step (Modem3gppUssdSendContext *ctx)
+{
+ if (ctx->encoded_used &&
+ ctx->unencoded_used) {
+ mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (ctx->self),
+ MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE);
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Sending USSD command failed");
+ modem_3gpp_ussd_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (ctx->self->priv->use_unencoded_ussd) {
+ if (!ctx->unencoded_used)
+ modem_3gpp_ussd_context_send_unencoded (ctx);
+ else if (!ctx->encoded_used)
+ modem_3gpp_ussd_context_send_encoded (ctx);
+ else
+ g_assert_not_reached ();
+ } else {
+ if (!ctx->encoded_used)
+ modem_3gpp_ussd_context_send_encoded (ctx);
+ else if (!ctx->unencoded_used)
+ modem_3gpp_ussd_context_send_unencoded (ctx);
+ else
+ g_assert_not_reached ();
+ }
+}
+
+static void
+modem_3gpp_ussd_send (MMIfaceModem3gppUssd *self,
+ const gchar *command,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Modem3gppUssdSendContext *ctx;
+
+ ctx = g_slice_new0 (Modem3gppUssdSendContext);
+ /* We're going to steal the string result in finish() so we must have a
+ * callback specified. */
+ g_assert (callback != NULL);
+ ctx->self = g_object_ref (self);
+ ctx->command = g_strdup (command);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_ussd_send);
+
+ mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self),
+ MM_MODEM_3GPP_USSD_SESSION_STATE_ACTIVE);
+
+ modem_3gpp_ussd_context_step (ctx);
+}
+
+/*****************************************************************************/
+/* USSD Encode/Decode (3GPP/USSD interface) */
+
+static gchar *
+modem_3gpp_ussd_encode (MMIfaceModem3gppUssd *self,
+ const gchar *command,
+ guint *scheme,
+ GError **error)
+{
+ MMBroadbandModem *broadband = MM_BROADBAND_MODEM (self);
+ GByteArray *ussd_command;
+ gchar *hex = NULL;
+
+ ussd_command = g_byte_array_new ();
+
+ /* encode to the current charset */
+ if (mm_modem_charset_byte_array_append (ussd_command,
+ command,
+ FALSE,
+ broadband->priv->modem_current_charset)) {
+ *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
+ /* convert to hex representation */
+ hex = mm_utils_bin2hexstr (ussd_command->data, ussd_command->len);
+ }
+
+ g_byte_array_free (ussd_command, TRUE);
+
+ return hex;
+}
+
+static gchar *
+modem_3gpp_ussd_decode (MMIfaceModem3gppUssd *self,
+ const gchar *reply,
+ GError **error)
+{
+ MMBroadbandModem *broadband = MM_BROADBAND_MODEM (self);
+
+ return mm_modem_charset_hex_to_utf8 (reply,
+ broadband->priv->modem_current_charset);
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited result codes (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_setup_cleanup_unsolicited_result_codes_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static gchar *
+decode_ussd_response (MMBroadbandModem *self,
+ const gchar *reply,
+ GError **error)
+{
+ gchar *p;
+ gchar *str;
+ gchar *decoded;
+
+ /* Look for the first ',' */
+ p = strchr (reply, ',');
+ if (!p) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot decode USSD response (%s): missing field separator",
+ reply);
+ return NULL;
+ }
+
+ /* Assume the string is the next field, and strip quotes. While doing this,
+ * we also skip any other additional field we may have afterwards */
+ if (p[1] == '"') {
+ str = g_strdup (&p[2]);
+ p = strchr (str, '"');
+ if (p)
+ *p = '\0';
+ } else {
+ str = g_strdup (&p[1]);
+ p = strchr (str, ',');
+ if (p)
+ *p = '\0';
+ }
+
+ /* If reply doesn't seem to be hex; just return itself... */
+ if (!mm_utils_ishexstr (str))
+ decoded = g_strdup (str);
+ else
+ decoded = mm_iface_modem_3gpp_ussd_decode (MM_IFACE_MODEM_3GPP_USSD (self), str, error);
+
+ g_free (str);
+
+ return decoded;
+}
+
+static void
+cusd_process_string (MMBroadbandModem *self,
+ const gchar *str)
+{
+ MMModem3gppUssdSessionState ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE;
+
+ if (!str || !isdigit (*str)) {
+ if (self->priv->pending_ussd_action)
+ g_simple_async_result_set_error (self->priv->pending_ussd_action,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Invalid USSD response received: '%s'",
+ str ? str : "(none)");
+ else
+ mm_warn ("Received invalid USSD network-initiated request: '%s'",
+ str ? str : "(none)");
+ } else {
+ gint status;
+
+ status = g_ascii_digit_value (*str);
+ switch (status) {
+ case 0: /* no further action required */ {
+ gchar *converted;
+ GError *error = NULL;
+
+ converted = decode_ussd_response (self, str, &error);
+ if (self->priv->pending_ussd_action) {
+ /* Response to the user's request */
+ if (error)
+ g_simple_async_result_take_error (self->priv->pending_ussd_action, error);
+ else
+ g_simple_async_result_set_op_res_gpointer (self->priv->pending_ussd_action,
+ converted,
+ g_free);
+ } else {
+ if (error) {
+ mm_warn ("Invalid network initiated USSD notification: %s",
+ error->message);
+ g_error_free (error);
+ } else {
+ /* Network-initiated USSD-Notify */
+ mm_iface_modem_3gpp_ussd_update_network_notification (
+ MM_IFACE_MODEM_3GPP_USSD (self),
+ converted);
+ g_free (converted);
+ }
+ }
+ break;
+ }
+
+ case 1: /* further action required */ {
+ gchar *converted;
+ GError *error = NULL;
+
+ ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_USER_RESPONSE;
+ converted = decode_ussd_response (self, str, &error);
+ if (self->priv->pending_ussd_action) {
+ if (error)
+ g_simple_async_result_take_error (self->priv->pending_ussd_action, error);
+ else
+ g_simple_async_result_set_op_res_gpointer (self->priv->pending_ussd_action,
+ converted,
+ g_free);
+ } else {
+ if (error) {
+ mm_warn ("Invalid network initiated USSD request: %s",
+ error->message);
+ g_error_free (error);
+ } else {
+ /* Network-initiated USSD-Request */
+ mm_iface_modem_3gpp_ussd_update_network_request (
+ MM_IFACE_MODEM_3GPP_USSD (self),
+ converted);
+ g_free (converted);
+ }
+ }
+ break;
+ }
+
+ case 2:
+ if (self->priv->pending_ussd_action)
+ g_simple_async_result_set_error (self->priv->pending_ussd_action,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "USSD terminated by network.");
+ break;
+
+ case 4:
+ if (self->priv->pending_ussd_action)
+ g_simple_async_result_set_error (self->priv->pending_ussd_action,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Operation not supported.");
+ break;
+
+ default:
+ if (self->priv->pending_ussd_action)
+ g_simple_async_result_set_error (self->priv->pending_ussd_action,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unhandled USSD reply: %s (%d)",
+ str,
+ status);
+ break;
+ }
+ }
+
+ mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self),
+ ussd_state);
+
+ /* Complete the pending action */
+ if (self->priv->pending_ussd_action) {
+ g_simple_async_result_complete_in_idle (self->priv->pending_ussd_action);
+ g_object_unref (self->priv->pending_ussd_action);
+ self->priv->pending_ussd_action = NULL;
+ }
+}
+
+static void
+cusd_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ MMBroadbandModem *self)
+{
+ gchar *str;
+
+ mm_dbg ("Unsolicited USSD URC received");
+ str = g_match_info_fetch (info, 1);
+ cusd_process_string (self, str);
+ g_free (str);
+}
+
+static void
+set_unsolicited_result_code_handlers (MMIfaceModem3gppUssd *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ MMAtSerialPort *ports[2];
+ GRegex *cusd_regex;
+ guint i;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ set_unsolicited_events_handlers);
+
+ cusd_regex = mm_3gpp_cusd_regex_get ();
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Enable unsolicited result codes in given port */
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+ /* Set/unset unsolicited CUSD event handler */
+ mm_dbg ("(%s) %s unsolicited result code handlers",
+ mm_port_get_device (MM_PORT (ports[i])),
+ enable ? "Setting" : "Removing");
+ mm_at_serial_port_add_unsolicited_msg_handler (
+ ports[i],
+ cusd_regex,
+ enable ? (MMAtSerialUnsolicitedMsgFn) cusd_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+
+ g_regex_unref (cusd_regex);
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+static void
+modem_3gpp_ussd_setup_unsolicited_result_codes (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ set_unsolicited_result_code_handlers (self, TRUE, callback, user_data);
+}
+
+static void
+modem_3gpp_ussd_cleanup_unsolicited_result_codes (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ set_unsolicited_result_code_handlers (self, FALSE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Enable/Disable URCs (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_enable_disable_unsolicited_result_codes_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+urc_enable_disable_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_3gpp_ussd_disable_unsolicited_result_codes (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_ussd_disable_unsolicited_result_codes);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CUSD=0",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)urc_enable_disable_ready,
+ result);
+}
+
+static void
+modem_3gpp_ussd_enable_unsolicited_result_codes (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_ussd_enable_unsolicited_result_codes);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CUSD=1",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)urc_enable_disable_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Check if USSD supported (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_check_support_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+cusd_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_3gpp_ussd_check_support (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_3gpp_ussd_check_support);
+
+ /* Check USSD support */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CUSD=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cusd_format_check_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Check if Messaging supported (Messaging interface) */
+
+static gboolean
+modem_messaging_check_support_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+cnmi_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ /* CNMI command is supported; assume we have full messaging capabilities */
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_messaging_check_support (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_messaging_check_support);
+
+ /* We assume that CDMA-only modems don't have messaging capabilities */
+ if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
+ g_simple_async_result_set_error (
+ result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "CDMA-only modems don't have messaging capabilities");
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ /* Check CNMI support */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CNMI=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cnmi_format_check_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Load supported SMS storages (Messaging interface) */
+
+typedef struct {
+ GArray *mem1;
+ GArray *mem2;
+ GArray *mem3;
+} SupportedStoragesResult;
+
+static void
+supported_storages_result_free (SupportedStoragesResult *result)
+{
+ if (result->mem1)
+ g_array_unref (result->mem1);
+ if (result->mem2)
+ g_array_unref (result->mem2);
+ if (result->mem3)
+ g_array_unref (result->mem3);
+ g_free (result);
+}
+
+static gboolean
+modem_messaging_load_supported_storages_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GArray **mem1,
+ GArray **mem2,
+ GArray **mem3,
+ GError **error)
+{
+ SupportedStoragesResult *result;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ result = (SupportedStoragesResult *)g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ *mem1 = g_array_ref (result->mem1);
+ *mem2 = g_array_ref (result->mem2);
+ *mem3 = g_array_ref (result->mem3);
+
+ return TRUE;
+}
+
+static void
+cpms_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ const gchar *response;
+ GError *error = NULL;
+ SupportedStoragesResult *result;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ result = g_new0 (SupportedStoragesResult, 1);
+
+ /* Parse reply */
+ if (!mm_3gpp_parse_cpms_test_response (response,
+ &result->mem1,
+ &result->mem2,
+ &result->mem3)) {
+ g_simple_async_result_set_error (simple,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse supported storages reply: '%s'",
+ response);
+ supported_storages_result_free (result);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ g_simple_async_result_set_op_res_gpointer (simple,
+ result,
+ (GDestroyNotify)supported_storages_result_free);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_messaging_load_supported_storages (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_messaging_load_supported_storages);
+
+ /* Check support storages */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CPMS=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cpms_format_check_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Lock/unlock SMS storage (Messaging interface implementation helper)
+ *
+ * The basic commands to work with SMS storages play with AT+CPMS and three
+ * different storages: mem1, mem2 and mem3.
+ * 'mem1' is the storage for reading, listing and deleting.
+ * 'mem2' is the storage for writing and sending from storage.
+ * 'mem3' is the storage for receiving.
+ *
+ * When a command is to be issued for a specific storage, we need a way to
+ * lock the access so that other actions are forbidden until the current one
+ * finishes. Just think of two sequential actions to store two different
+ * SMS into 2 different storages. If the second action is run while the first
+ * one is still running, we should issue a RETRY error.
+ *
+ * Note that mem3 cannot be locked; we just set the default mem3 and that's it.
+ *
+ * When we unlock the storage, we don't go back to the default storage
+ * automatically, we just keep track of which is the current one and only go to
+ * the default one if needed.
+ */
+
+void
+mm_broadband_modem_unlock_sms_storages (MMBroadbandModem *self,
+ gboolean mem1,
+ gboolean mem2)
+{
+ if (mem1) {
+ g_assert (self->priv->mem1_storage_locked);
+ self->priv->mem1_storage_locked = FALSE;
+ }
+
+ if (mem2) {
+ g_assert (self->priv->mem2_storage_locked);
+ self->priv->mem2_storage_locked = FALSE;
+ }
+}
+
+gboolean
+mm_broadband_modem_lock_sms_storages_finish (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+typedef struct {
+ GSimpleAsyncResult *result;
+ MMBroadbandModem *self;
+ MMSmsStorage previous_mem1;
+ gboolean mem1_locked;
+ MMSmsStorage previous_mem2;
+ gboolean mem2_locked;
+} LockSmsStoragesContext;
+
+static void
+lock_sms_storages_context_complete_and_free (LockSmsStoragesContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_slice_free (LockSmsStoragesContext, ctx);
+}
+
+static void
+lock_storages_cpms_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ LockSmsStoragesContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ /* Reset previous storages and set unlocked */
+ if (ctx->mem1_locked) {
+ ctx->self->priv->current_sms_mem1_storage = ctx->previous_mem1;
+ ctx->self->priv->mem1_storage_locked = FALSE;
+ }
+ if (ctx->mem2_locked) {
+ ctx->self->priv->current_sms_mem2_storage = ctx->previous_mem2;
+ ctx->self->priv->mem2_storage_locked = FALSE;
+ }
+ }
+ else
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+
+ lock_sms_storages_context_complete_and_free (ctx);
+}
+
+void
+mm_broadband_modem_lock_sms_storages (MMBroadbandModem *self,
+ MMSmsStorage mem1, /* reading/listing/deleting */
+ MMSmsStorage mem2, /* storing/sending */
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LockSmsStoragesContext *ctx;
+ gchar *cmd;
+ gchar *mem1_str = NULL;
+ gchar *mem2_str = NULL;
+
+ /* If storages are currently locked by someone else, just return an
+ * error */
+ if ((mem1 != MM_SMS_STORAGE_UNKNOWN && self->priv->mem1_storage_locked) ||
+ (mem2 != MM_SMS_STORAGE_UNKNOWN && self->priv->mem2_storage_locked)) {
+ g_simple_async_report_error_in_idle (
+ G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_RETRY,
+ "SMS storage currently locked, try again later");
+ return;
+ }
+
+ /* We allow locking either just one or both */
+ g_assert (mem1 != MM_SMS_STORAGE_UNKNOWN ||
+ mem2 != MM_SMS_STORAGE_UNKNOWN);
+
+ ctx = g_slice_new0 (LockSmsStoragesContext);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ mm_broadband_modem_lock_sms_storages);
+
+ if (mem1 != MM_SMS_STORAGE_UNKNOWN) {
+ ctx->mem1_locked = TRUE;
+ ctx->previous_mem1 = self->priv->current_sms_mem1_storage;
+ self->priv->mem1_storage_locked = TRUE;
+ self->priv->current_sms_mem1_storage = mem1;
+ mem1_str = g_ascii_strup (mm_sms_storage_get_string (self->priv->current_sms_mem1_storage), -1);
+ }
+
+ if (mem2 != MM_SMS_STORAGE_UNKNOWN) {
+ ctx->mem2_locked = TRUE;
+ ctx->previous_mem2 = self->priv->current_sms_mem2_storage;
+ self->priv->mem2_storage_locked = TRUE;
+ self->priv->current_sms_mem2_storage = mem2;
+ mem2_str = g_ascii_strup (mm_sms_storage_get_string (self->priv->current_sms_mem2_storage), -1);
+ }
+
+ /* We don't touch 'mem3' here */
+
+ mm_dbg ("Locking SMS storages to: mem1 (%s), mem2 (%s)...",
+ mem1_str ? mem1_str : "none",
+ mem2_str ? mem2_str : "none");
+
+ if (mem2_str)
+ cmd = g_strdup_printf ("+CPMS=\"%s\",\"%s\"",
+ mem1_str ? mem1_str : "",
+ mem2_str);
+ else if (mem1_str)
+ cmd = g_strdup_printf ("+CPMS=\"%s\"", mem1_str);
+ else
+ g_assert_not_reached ();
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)lock_storages_cpms_set_ready,
+ ctx);
+ g_free (mem1_str);
+ g_free (mem2_str);
+ g_free (cmd);
+}
+
+/*****************************************************************************/
+/* Set default SMS storage (Messaging interface) */
+
+static gboolean
+modem_messaging_set_default_storage_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+cpms_set_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_simple_async_result_take_error (simple, error);
+ else
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_messaging_set_default_storage (MMIfaceModemMessaging *_self,
+ MMSmsStorage storage,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
+ gchar *cmd;
+ GSimpleAsyncResult *result;
+ gchar *mem_str;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_messaging_set_default_storage);
+
+ /* Set defaults as current */
+ self->priv->current_sms_mem2_storage = storage;
+
+ mem_str = g_ascii_strup (mm_sms_storage_get_string (storage), -1);
+ cmd = g_strdup_printf ("+CPMS=\"\",\"%s\",\"%s\"", mem_str, mem_str);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cpms_set_ready,
+ result);
+ g_free (mem_str);
+ g_free (cmd);
+}
+
+/*****************************************************************************/
+/* Setup SMS format (Messaging interface) */
+
+static gboolean
+modem_messaging_setup_sms_format_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+cmgf_set_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ mm_dbg ("Failed to set preferred SMS mode: '%s'; assuming text mode'",
+ error->message);
+ g_error_free (error);
+ self->priv->modem_messaging_sms_pdu_mode = FALSE;
+ } else
+ mm_dbg ("Successfully set preferred SMS mode: '%s'",
+ self->priv->modem_messaging_sms_pdu_mode ? "PDU" : "text");
+
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+set_preferred_sms_format (MMBroadbandModem *self,
+ GSimpleAsyncResult *result)
+{
+ gchar *cmd;
+
+ cmd = g_strdup_printf ("+CMGF=%s",
+ self->priv->modem_messaging_sms_pdu_mode ? "0" : "1");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cmgf_set_ready,
+ result);
+ g_free (cmd);
+}
+
+static void
+cmgf_format_check_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+ const gchar *response;
+ gboolean sms_pdu_supported = FALSE;
+ gboolean sms_text_supported = FALSE;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error ||
+ !mm_3gpp_parse_cmgf_test_response (response,
+ &sms_pdu_supported,
+ &sms_text_supported,
+ &error)) {
+ mm_dbg ("Failed to query supported SMS modes: '%s'",
+ error->message);
+ g_error_free (error);
+ }
+
+ /* Only use text mode if PDU mode not supported */
+ self->priv->modem_messaging_sms_pdu_mode = TRUE;
+ if (!sms_pdu_supported) {
+ if (sms_text_supported) {
+ mm_dbg ("PDU mode not supported, will try to use Text mode");
+ self->priv->modem_messaging_sms_pdu_mode = FALSE;
+ } else
+ mm_dbg ("Neither PDU nor Text modes are reported as supported; "
+ "will anyway default to PDU mode");
+ }
+
+ self->priv->sms_supported_modes_checked = TRUE;
+
+ set_preferred_sms_format (self, simple);
+}
+
+static void
+modem_messaging_setup_sms_format (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_messaging_setup_sms_format);
+
+ /* If we already checked for supported SMS types, go on to select the
+ * preferred format. */
+ if (MM_BROADBAND_MODEM (self)->priv->sms_supported_modes_checked) {
+ set_preferred_sms_format (MM_BROADBAND_MODEM (self), result);
+ return;
+ }
+
+ /* Check supported SMS formats */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CMGF=?",
+ 3,
+ TRUE,
+ (GAsyncReadyCallback)cmgf_format_check_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Setup/cleanup messaging related unsolicited events (Messaging interface) */
+
+static gboolean
+modem_messaging_setup_cleanup_unsolicited_events_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ guint idx;
+} SmsPartContext;
+
+static void
+sms_part_context_complete_and_free (SmsPartContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static void
+sms_part_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ SmsPartContext *ctx)
+{
+ MMSmsPart *part;
+ gint rv, status, tpdu_len;
+ gchar pdu[SMS_MAX_PDU_LEN + 1];
+ const gchar *response;
+ GError *error = NULL;
+
+ /* Always always always unlock mem1 storage. Warned you've been. */
+ mm_broadband_modem_unlock_sms_storages (self, TRUE, FALSE);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ /* We're really ignoring this error afterwards, as we don't have a callback
+ * passed to the async operation, so just log the error here. */
+ mm_warn ("Couldn't retrieve SMS part: '%s'",
+ error->message);
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_part_context_complete_and_free (ctx);
+ return;
+ }
+
+ rv = sscanf (response, "+CMGR: %d,,%d %" G_STRINGIFY (SMS_MAX_PDU_LEN) "s",
+ &status, &tpdu_len, pdu);
+ if (rv != 3) {
+ error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse CMGR response (parsed %d items)", rv);
+ mm_warn ("Couldn't retrieve SMS part: '%s'", error->message);
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_part_context_complete_and_free (ctx);
+ return;
+ }
+
+ part = mm_sms_part_new_from_pdu (ctx->idx, pdu, &error);
+ if (part) {
+ mm_dbg ("Correctly parsed PDU (%d)", ctx->idx);
+ mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
+ part,
+ MM_SMS_STATE_RECEIVED,
+ self->priv->modem_messaging_sms_default_storage);
+ } else {
+ /* Don't treat the error as critical */
+ mm_dbg ("Error parsing PDU (%d): %s", ctx->idx, error->message);
+ g_error_free (error);
+ }
+
+ /* All done */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ sms_part_context_complete_and_free (ctx);
+}
+
+static void
+indication_lock_storages_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ SmsPartContext *ctx)
+{
+ gchar *command;
+ GError *error = NULL;
+
+ if (!mm_broadband_modem_lock_sms_storages_finish (self, res, &error)) {
+ /* TODO: we should either make this lock() never fail, by automatically
+ * retrying after some time, or otherwise retry here. */
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_part_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Storage now set and locked */
+
+ /* Retrieve the message */
+ command = g_strdup_printf ("+CMGR=%d", ctx->idx);
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ command,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)sms_part_ready,
+ ctx);
+ g_free (command);
+}
+
+static void
+cmti_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ MMBroadbandModem *self)
+{
+ SmsPartContext *ctx;
+ guint idx = 0;
+ MMSmsStorage storage;
+ gchar *str;
+
+ if (!mm_get_uint_from_match_info (info, 2, &idx))
+ return;
+
+ /* The match info gives us in which storage the index applies */
+ str = mm_get_string_unquoted_from_match_info (info, 1);
+ storage = mm_common_get_sms_storage_from_string (str, NULL);
+ if (storage == MM_SMS_STORAGE_UNKNOWN) {
+ mm_dbg ("Skipping CMTI indication, unknown storage '%s' reported", str);
+ g_free (str);
+ return;
+ }
+ g_free (str);
+
+ /* Don't signal multiple times if there are multiple CMTI notifications for a message */
+ if (mm_sms_list_has_part (self->priv->modem_messaging_sms_list,
+ storage,
+ idx)) {
+ mm_dbg ("Skipping CMTI indication, part already processed");
+ return;
+ }
+
+ ctx = g_new0 (SmsPartContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self), NULL, NULL, cmti_received);
+ ctx->idx = idx;
+
+ /* First, request to set the proper storage to read from */
+ mm_broadband_modem_lock_sms_storages (ctx->self,
+ storage,
+ MM_SMS_STORAGE_UNKNOWN,
+ (GAsyncReadyCallback)indication_lock_storages_ready,
+ ctx);
+}
+
+static void
+cds_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ MMBroadbandModem *self)
+{
+ GError *error = NULL;
+ MMSmsPart *part;
+ guint length;
+ gchar *pdu;
+
+ mm_dbg ("Got new non-stored message indication");
+
+ if (!mm_get_uint_from_match_info (info, 1, &length))
+ return;
+
+ pdu = g_match_info_fetch (info, 2);
+ if (!pdu)
+ return;
+
+ part = mm_sms_part_new_from_pdu (SMS_PART_INVALID_INDEX, pdu, &error);
+ if (part) {
+ mm_dbg ("Correctly parsed non-stored PDU");
+ mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
+ part,
+ MM_SMS_STATE_RECEIVED,
+ MM_SMS_STORAGE_UNKNOWN);
+ } else {
+ /* Don't treat the error as critical */
+ mm_dbg ("Error parsing non-stored PDU: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+set_messaging_unsolicited_events_handlers (MMIfaceModemMessaging *self,
+ gboolean enable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ MMAtSerialPort *ports[2];
+ GRegex *cmti_regex;
+ GRegex *cds_regex;
+ guint i;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ set_messaging_unsolicited_events_handlers);
+
+ cmti_regex = mm_3gpp_cmti_regex_get ();
+ cds_regex = mm_3gpp_cds_regex_get ();
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Enable unsolicited events in given port */
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ /* Set/unset unsolicited CMTI event handler */
+ mm_dbg ("(%s) %s messaging unsolicited events handlers",
+ mm_port_get_device (MM_PORT (ports[i])),
+ enable ? "Setting" : "Removing");
+ mm_at_serial_port_add_unsolicited_msg_handler (
+ ports[i],
+ cmti_regex,
+ enable ? (MMAtSerialUnsolicitedMsgFn) cmti_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ mm_at_serial_port_add_unsolicited_msg_handler (
+ ports[i],
+ cds_regex,
+ enable ? (MMAtSerialUnsolicitedMsgFn) cds_received : NULL,
+ enable ? self : NULL,
+ NULL);
+ }
+
+ g_regex_unref (cmti_regex);
+ g_regex_unref (cds_regex);
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+static void
+modem_messaging_setup_unsolicited_events (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ set_messaging_unsolicited_events_handlers (self, TRUE, callback, user_data);
+}
+
+static void
+modem_messaging_cleanup_unsolicited_events (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ set_messaging_unsolicited_events_handlers (self, FALSE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Enable unsolicited events (SMS indications) (Messaging interface) */
+
+static gboolean
+modem_messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+
+ mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+cnmi_response_processor (MMBaseModem *self,
+ gpointer none,
+ const gchar *command,
+ const gchar *response,
+ gboolean last_command,
+ const GError *error,
+ GVariant **result,
+ GError **result_error)
+{
+ if (error) {
+ /* If we get a not-supported error and we're not in the last command, we
+ * won't set 'result_error', so we'll keep on the sequence */
+ if (!g_error_matches (error, MM_MESSAGE_ERROR, MM_MESSAGE_ERROR_NOT_SUPPORTED) ||
+ last_command)
+ *result_error = g_error_copy (error);
+
+ return FALSE;
+ }
+
+ *result = NULL;
+ return TRUE;
+}
+
+static const MMBaseModemAtCommand cnmi_sequence[] = {
+ { "+CNMI=2,1,2,1,0", 3, FALSE, cnmi_response_processor },
+
+ /* Many Qualcomm-based devices don't support <ds> of '1', despite
+ * reporting they support it in the +CNMI=? response. But they do
+ * accept '2'.
+ */
+ { "+CNMI=2,1,2,2,0", 3, FALSE, cnmi_response_processor },
+
+ /* Last resort: turn off delivery status reports altogether */
+ { "+CNMI=2,1,2,0,0", 3, FALSE, cnmi_response_processor },
+ { NULL }
+};
+
+static void
+modem_messaging_enable_unsolicited_events (MMIfaceModemMessaging *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_base_modem_at_sequence (
+ MM_BASE_MODEM (self),
+ cnmi_sequence,
+ NULL, /* response_processor_context */
+ NULL, /* response_processor_context_free */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Load initial list of SMS parts (Messaging interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMSmsStorage list_storage;
+} ListPartsContext;
+
+static void
+list_parts_context_complete_and_free (ListPartsContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+modem_messaging_load_initial_sms_parts_finish (MMIfaceModemMessaging *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static MMSmsState
+sms_state_from_str (const gchar *str)
+{
+ /* We merge unread and read messages in the same state */
+ if (strstr (str, "REC"))
+ return MM_SMS_STATE_RECEIVED;
+
+ /* look for 'unsent' BEFORE looking for 'sent' */
+ if (strstr (str, "UNSENT"))
+ return MM_SMS_STATE_STORED;
+
+ if (strstr (str, "SENT"))
+ return MM_SMS_STATE_SENT;
+
+ return MM_SMS_STATE_UNKNOWN;
+}
+
+static MMSmsPduType
+sms_pdu_type_from_str (const gchar *str)
+{
+ /* We merge unread and read messages in the same state */
+ if (strstr (str, "REC"))
+ return MM_SMS_PDU_TYPE_DELIVER;
+
+ if (strstr (str, "STO"))
+ return MM_SMS_PDU_TYPE_SUBMIT;
+
+ return MM_SMS_PDU_TYPE_UNKNOWN;
+}
+
+static void
+sms_text_part_list_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ ListPartsContext *ctx)
+{
+ GRegex *r;
+ GMatchInfo *match_info = NULL;
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ list_parts_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* +CMGL: <index>,<stat>,<oa/da>,[alpha],<scts><CR><LF><data><CR><LF> */
+ r = g_regex_new ("\\+CMGL:\\s*(\\d+)\\s*,\\s*([^,]*),\\s*([^,]*),\\s*([^,]*),\\s*([^\\r\\n]*)\\r\\n([^\\r\\n]*)",
+ 0, 0, NULL);
+ g_assert (r);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Couldn't parse SMS list response");
+ list_parts_context_complete_and_free (ctx);
+ g_regex_unref (r);
+ return;
+ }
+
+ while (g_match_info_matches (match_info)) {
+ MMSmsPart *part;
+ guint matches, idx;
+ gchar *number, *timestamp, *text, *ucs2_text, *stat;
+ gsize ucs2_len = 0;
+ GByteArray *raw;
+
+ matches = g_match_info_get_match_count (match_info);
+ if (matches != 7) {
+ mm_dbg ("Failed to match entire CMGL response (count %d)", matches);
+ goto next;
+ }
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &idx)) {
+ mm_dbg ("Failed to convert message index");
+ goto next;
+ }
+
+ /* Get part state */
+ stat = mm_get_string_unquoted_from_match_info (match_info, 2);
+ if (!stat) {
+ mm_dbg ("Failed to get part status");
+ goto next;
+ }
+
+ /* Get and parse number */
+ number = mm_get_string_unquoted_from_match_info (match_info, 3);
+ if (!number) {
+ mm_dbg ("Failed to get message sender number");
+ g_free (stat);
+ goto next;
+ }
+
+ number = mm_broadband_modem_take_and_convert_to_utf8 (MM_BROADBAND_MODEM (self),
+ number);
+
+ /* Get and parse timestamp (always expected in ASCII) */
+ timestamp = mm_get_string_unquoted_from_match_info (match_info, 5);
+
+ /* Get and parse text */
+ text = mm_broadband_modem_take_and_convert_to_utf8 (MM_BROADBAND_MODEM (self),
+ g_match_info_fetch (match_info, 6));
+
+ /* The raw SMS data can only be GSM, UCS2, or unknown (8-bit), so we
+ * need to convert to UCS2 here.
+ */
+ ucs2_text = g_convert (text, -1, "UCS-2BE//TRANSLIT", "UTF-8", NULL, &ucs2_len, NULL);
+ g_assert (ucs2_text);
+ raw = g_byte_array_sized_new (ucs2_len);
+ g_byte_array_append (raw, (const guint8 *) ucs2_text, ucs2_len);
+ g_free (ucs2_text);
+
+ /* all take() methods pass ownership of the value as well */
+ part = mm_sms_part_new (idx,
+ sms_pdu_type_from_str (stat));
+ mm_sms_part_take_number (part, number);
+ mm_sms_part_take_timestamp (part, timestamp);
+ mm_sms_part_take_text (part, text);
+ mm_sms_part_take_data (part, raw);
+ mm_sms_part_set_class (part, -1);
+
+ mm_dbg ("Correctly parsed SMS list entry (%d)", idx);
+ mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
+ part,
+ sms_state_from_str (stat),
+ ctx->list_storage);
+ g_free (stat);
+next:
+ g_match_info_next (match_info, NULL);
+ }
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+
+ /* We consider all done */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ list_parts_context_complete_and_free (ctx);
+}
+
+static MMSmsState
+sms_state_from_index (guint index)
+{
+ /* We merge unread and read messages in the same state */
+ switch (index) {
+ case 0: /* received, unread */
+ case 1: /* received, read */
+ return MM_SMS_STATE_RECEIVED;
+ case 2:
+ return MM_SMS_STATE_STORED;
+ case 3:
+ return MM_SMS_STATE_SENT;
+ default:
+ return MM_SMS_STATE_UNKNOWN;
+ }
+}
+
+static void
+sms_pdu_part_list_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ ListPartsContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+ GList *info_list;
+ GList *l;
+
+ /* Always always always unlock mem1 storage. Warned you've been. */
+ mm_broadband_modem_unlock_sms_storages (self, TRUE, FALSE);
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ list_parts_context_complete_and_free (ctx);
+ return;
+ }
+
+ info_list = mm_3gpp_parse_pdu_cmgl_response (response, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ list_parts_context_complete_and_free (ctx);
+ return;
+ }
+
+ for (l = info_list; l; l = g_list_next (l)) {
+ MM3gppPduInfo *info = l->data;
+ MMSmsPart *part;
+
+ part = mm_sms_part_new_from_pdu (info->index, info->pdu, &error);
+ if (part) {
+ mm_dbg ("Correctly parsed PDU (%d)", info->index);
+ mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
+ part,
+ sms_state_from_index (info->status),
+ ctx->list_storage);
+ } else {
+ /* Don't treat the error as critical */
+ mm_dbg ("Error parsing PDU (%d): %s", info->index, error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ mm_3gpp_pdu_info_list_free (info_list);
+
+ /* We consider all done */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ list_parts_context_complete_and_free (ctx);
+}
+
+static void
+list_parts_lock_storages_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ ListPartsContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_broadband_modem_lock_sms_storages_finish (self, res, &error)) {
+ /* TODO: we should either make this lock() never fail, by automatically
+ * retrying after some time, or otherwise retry here. */
+ g_simple_async_result_take_error (ctx->result, error);
+ list_parts_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Storage now set and locked */
+
+ /* Get SMS parts from ALL types.
+ * Different command to be used if we are on Text or PDU mode */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ (MM_BROADBAND_MODEM (self)->priv->modem_messaging_sms_pdu_mode ?
+ "+CMGL=4" :
+ "+CMGL=\"ALL\""),
+ 20,
+ FALSE,
+ (GAsyncReadyCallback) (MM_BROADBAND_MODEM (self)->priv->modem_messaging_sms_pdu_mode ?
+ sms_pdu_part_list_ready :
+ sms_text_part_list_ready),
+ ctx);
+}
+
+static void
+modem_messaging_load_initial_sms_parts (MMIfaceModemMessaging *self,
+ MMSmsStorage storage,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ListPartsContext *ctx;
+
+ ctx = g_new0 (ListPartsContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_messaging_load_initial_sms_parts);
+ ctx->list_storage = storage;
+
+ mm_dbg ("Listing SMS parts in storage '%s'",
+ mm_sms_storage_get_string (storage));
+
+ /* First, request to set the proper storage to read from */
+ mm_broadband_modem_lock_sms_storages (ctx->self,
+ storage,
+ MM_SMS_STORAGE_UNKNOWN,
+ (GAsyncReadyCallback)list_parts_lock_storages_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Create SMS (Messaging interface) */
+
+static MMSms *
+modem_messaging_create_sms (MMIfaceModemMessaging *self)
+{
+ return mm_sms_new (MM_BASE_MODEM (self));
+}
+
+/*****************************************************************************/
+/* ESN loading (CDMA interface) */
+
+static gchar *
+modem_cdma_load_esn_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *esn = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ result = mm_strip_tag (result, "+GSN:");
+ mm_parse_gsn (result, NULL, NULL, &esn);
+ mm_dbg ("loaded ESN: %s", esn);
+ return esn;
+}
+
+static void
+modem_cdma_load_esn (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ mm_dbg ("loading ESN...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+GSN",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* MEID loading (CDMA interface) */
+
+static gchar *
+modem_cdma_load_meid_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *meid = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ result = mm_strip_tag (result, "+GSN:");
+ mm_parse_gsn (result, NULL, &meid, NULL);
+ mm_dbg ("loaded MEID: %s", meid);
+ return meid;
+}
+
+static void
+modem_cdma_load_meid (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Some devices return both the MEID and the ESN in the +GSN response */
+ mm_dbg ("loading MEID...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+GSN",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* HDR state check (CDMA interface) */
+
+typedef struct {
+ guint8 hybrid_mode;
+ guint8 session_state;
+ guint8 almp_state;
+} HdrStateResults;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMQcdmSerialPort *qcdm;
+} HdrStateContext;
+
+static void
+hdr_state_context_complete_and_free (HdrStateContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->qcdm);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+modem_cdma_get_hdr_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ guint8 *hybrid_mode,
+ guint8 *session_state,
+ guint8 *almp_state,
+ GError **error)
+{
+ HdrStateResults *results;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ results = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ *hybrid_mode = results->hybrid_mode;
+ *session_state = results->session_state;
+ *almp_state = results->almp_state;
+ return TRUE;
+}
+
+static void
+hdr_subsys_state_info_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ HdrStateContext *ctx)
+{
+ QcdmResult *result;
+ HdrStateResults *results;
+ gint err = QCDM_SUCCESS;
+
+ if (error) {
+ g_simple_async_result_set_from_error (ctx->result, error);
+ hdr_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_hdr_subsys_state_info_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (!result) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse HDR subsys state info command result: %d",
+ err);
+ hdr_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Build results */
+ results = g_new0 (HdrStateResults, 1);
+ qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_HDR_HYBRID_MODE, &results->hybrid_mode);
+ results->session_state = QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_CLOSED;
+ qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &results->session_state);
+ results->almp_state = QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_INACTIVE;
+ qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &results->almp_state);
+ qcdm_result_unref (result);
+
+ g_simple_async_result_set_op_res_gpointer (ctx->result, results, (GDestroyNotify)g_free);
+ hdr_state_context_complete_and_free (ctx);
+}
+
+static void
+modem_cdma_get_hdr_state (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMQcdmSerialPort *qcdm;
+ HdrStateContext *ctx;
+ GByteArray *hdrstate;
+
+ qcdm = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
+ if (!qcdm) {
+ g_simple_async_report_error_in_idle (G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot get HDR state without a QCDM port");
+ return;
+ }
+
+ /* Setup context */
+ ctx = g_new0 (HdrStateContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_cdma_get_hdr_state);
+ ctx->qcdm = g_object_ref (qcdm);
+
+ /* Setup command */
+ hdrstate = g_byte_array_sized_new (25);
+ hdrstate->len = qcdm_cmd_hdr_subsys_state_info_new ((gchar *) hdrstate->data, 25);
+ g_assert (hdrstate->len);
+
+ mm_qcdm_serial_port_queue_command (ctx->qcdm,
+ hdrstate,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn)hdr_subsys_state_info_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Call Manager state check (CDMA interface) */
+
+typedef struct {
+ guint system_mode;
+ guint operating_mode;
+} CallManagerStateResults;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMQcdmSerialPort *qcdm;
+} CallManagerStateContext;
+
+static void
+call_manager_state_context_complete_and_free (CallManagerStateContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->qcdm);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+modem_cdma_get_call_manager_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ guint *system_mode,
+ guint *operating_mode,
+ GError **error)
+{
+ CallManagerStateResults *results;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ results = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ *system_mode = results->system_mode;
+ *operating_mode = results->operating_mode;
+ return TRUE;
+}
+
+static void
+cm_subsys_state_info_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ CallManagerStateContext *ctx)
+{
+ QcdmResult *result;
+ CallManagerStateResults *results;
+ gint err = QCDM_SUCCESS;
+
+ if (error) {
+ g_simple_async_result_set_from_error (ctx->result, error);
+ call_manager_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Parse the response */
+ result = qcdm_cmd_cm_subsys_state_info_result ((const gchar *) response->data,
+ response->len,
+ &err);
+ if (!result) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse CM subsys state info command result: %d",
+ err);
+ call_manager_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Build results */
+ results = g_new0 (CallManagerStateResults, 1);
+ qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &results->operating_mode);
+ qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &results->system_mode);
+ qcdm_result_unref (result);
+
+ g_simple_async_result_set_op_res_gpointer (ctx->result, results, (GDestroyNotify)g_free);
+ call_manager_state_context_complete_and_free (ctx);
+}
+
+static void
+modem_cdma_get_call_manager_state (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMQcdmSerialPort *qcdm;
+ CallManagerStateContext *ctx;
+ GByteArray *cmstate;
+
+ qcdm = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
+ if (!qcdm) {
+ g_simple_async_report_error_in_idle (G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Cannot get call manager state without a QCDM port");
+ return;
+ }
+
+ /* Setup context */
+ ctx = g_new0 (CallManagerStateContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_cdma_get_call_manager_state);
+ ctx->qcdm = g_object_ref (qcdm);
+
+ /* Setup command */
+ cmstate = g_byte_array_sized_new (25);
+ cmstate->len = qcdm_cmd_cm_subsys_state_info_new ((gchar *) cmstate->data, 25);
+ g_assert (cmstate->len);
+
+ mm_qcdm_serial_port_queue_command (ctx->qcdm,
+ cmstate,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn)cm_subsys_state_info_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Serving System check (CDMA interface) */
+
+typedef struct {
+ guint sid;
+ guint nid;
+ guint class;
+ guint band;
+} Cdma1xServingSystemResults;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMQcdmSerialPort *qcdm;
+} Cdma1xServingSystemContext;
+
+static void
+cdma1x_serving_system_context_complete_and_free (Cdma1xServingSystemContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ if (ctx->qcdm)
+ g_object_unref (ctx->qcdm);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static GError *
+cdma1x_serving_system_no_service_error (void)
+{
+ /* NOTE: update get_cdma1x_serving_system_ready() in mm-iface-modem-cdma.c
+ * if this error changes */
+ return g_error_new_literal (MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK,
+ "No CDMA service");
+}
+
+static gboolean
+modem_cdma_get_cdma1x_serving_system_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ guint *class,
+ guint *band,
+ guint *sid,
+ guint *nid,
+ GError **error)
+{
+ Cdma1xServingSystemResults *results;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ results = (Cdma1xServingSystemResults *)g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ *sid = results->sid;
+ *nid = results->nid;
+ *class = results->class;
+ *band = results->band;
+ return TRUE;
+}
+
+static void
+css_query_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ Cdma1xServingSystemContext *ctx)
+{
+ GError *error = NULL;
+ const gchar *result;
+ gint class = 0;
+ gint sid = MM_MODEM_CDMA_SID_UNKNOWN;
+ gint num;
+ guchar band = 'Z';
+ gboolean class_ok = FALSE;
+ gboolean band_ok = FALSE;
+ gboolean success = FALSE;
+ Cdma1xServingSystemResults *results;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ cdma1x_serving_system_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Strip any leading command tag and spaces */
+ result = mm_strip_tag (result, "+CSS:");
+ num = sscanf (result, "? , %d", &sid);
+ if (num == 1) {
+ /* UTStarcom and Huawei modems that use IS-707-A format; note that
+ * this format obviously doesn't have other indicators like band and
+ * class and thus SID 0 will be reported as "no service" (see below).
+ */
+ class = 0;
+ band = 'Z';
+ success = TRUE;
+ } else {
+ GRegex *r;
+ GMatchInfo *match_info;
+
+ /* Format is "<band_class>,<band>,<sid>" */
+ r = g_regex_new ("\\s*([^,]*?)\\s*,\\s*([^,]*?)\\s*,\\s*(\\d+)", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ if (!r) {
+ g_simple_async_result_set_error (
+ ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse Serving System results (regex creation failed).");
+ cdma1x_serving_system_context_complete_and_free (ctx);
+ return;
+ }
+
+ g_regex_match (r, result, 0, &match_info);
+ if (g_match_info_get_match_count (match_info) >= 3) {
+ gint override_class = 0;
+ gchar *str;
+
+ /* band class */
+ str = g_match_info_fetch (match_info, 1);
+ class = mm_cdma_normalize_class (str);
+ g_free (str);
+
+ /* band */
+ str = g_match_info_fetch (match_info, 2);
+ band = mm_cdma_normalize_band (str, &override_class);
+ if (override_class)
+ class = override_class;
+ g_free (str);
+
+ /* sid */
+ str = g_match_info_fetch (match_info, 3);
+ if (!mm_get_int_from_str (str, &sid))
+ sid = MM_MODEM_CDMA_SID_UNKNOWN;
+ g_free (str);
+
+ success = TRUE;
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
+ }
+
+ if (!success) {
+ g_simple_async_result_set_error (
+ ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Could not parse Serving System results");
+ cdma1x_serving_system_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Normalize the SID */
+ if (sid < 0 || sid > 32767)
+ sid = MM_MODEM_CDMA_SID_UNKNOWN;
+
+ if (class == 1 || class == 2)
+ class_ok = TRUE;
+ if (band != 'Z')
+ band_ok = TRUE;
+
+ /* Return 'no service' if none of the elements of the +CSS response
+ * indicate that the modem has service. Note that this allows SID 0
+ * when at least one of the other elements indicates service.
+ * Normally we'd treat SID 0 as 'no service' but some modems
+ * (Sierra 5725) sometimes return SID 0 even when registered.
+ */
+ if (sid == 0 && !class_ok && !band_ok)
+ sid = MM_MODEM_CDMA_SID_UNKNOWN;
+
+ /* 99999 means unknown/no service */
+ if (sid == MM_MODEM_CDMA_SID_UNKNOWN) {
+ g_simple_async_result_take_error (ctx->result,
+ cdma1x_serving_system_no_service_error ());
+ cdma1x_serving_system_context_complete_and_free (ctx);
+ return;
+ }
+
+ results = g_new0 (Cdma1xServingSystemResults, 1);
+ results->sid = sid;
+ results->band = band;
+ results->class = class;
+ /* No means to get NID with AT commands right now */
+ results->nid = MM_MODEM_CDMA_NID_UNKNOWN;
+
+ g_simple_async_result_set_op_res_gpointer (ctx->result, results, (GDestroyNotify)g_free);
+ cdma1x_serving_system_context_complete_and_free (ctx);
+}
+
+static void
+qcdm_cdma_status_ready (MMQcdmSerialPort *port,
+ GByteArray *response,
+ GError *error,
+ Cdma1xServingSystemContext *ctx)
+{
+ Cdma1xServingSystemResults *results;
+ QcdmResult *result;
+ guint32 sid = MM_MODEM_CDMA_SID_UNKNOWN;
+ guint32 nid = MM_MODEM_CDMA_NID_UNKNOWN;
+ guint32 rxstate = 0;
+ gint err = QCDM_SUCCESS;
+
+ if (error ||
+ (result = qcdm_cmd_cdma_status_result ((const gchar *) response->data,
+ response->len,
+ &err)) == NULL) {
+ if (err != QCDM_SUCCESS)
+ mm_dbg ("Failed to parse cdma status command result: %d", err);
+ /* If there was some error, fall back to use +CSS like we did before QCDM */
+ mm_base_modem_at_command (MM_BASE_MODEM (ctx->self),
+ "+CSS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)css_query_ready,
+ ctx);
+ return;
+ }
+
+ qcdm_result_get_u32 (result, QCDM_CMD_CDMA_STATUS_ITEM_RX_STATE, &rxstate);
+ qcdm_result_get_u32 (result, QCDM_CMD_CDMA_STATUS_ITEM_SID, &sid);
+ qcdm_result_get_u32 (result, QCDM_CMD_CDMA_STATUS_ITEM_NID, &nid);
+ qcdm_result_unref (result);
+
+ /* 99999 means unknown/no service */
+ if (rxstate == QCDM_CMD_CDMA_STATUS_RX_STATE_ENTERING_CDMA) {
+ sid = MM_MODEM_CDMA_SID_UNKNOWN;
+ nid = MM_MODEM_CDMA_NID_UNKNOWN;
+ }
+
+ mm_dbg ("CDMA 1x Status RX state: %d", rxstate);
+ mm_dbg ("CDMA 1x Status SID: %d", sid);
+ mm_dbg ("CDMA 1x Status NID: %d", nid);
+
+ results = g_new0 (Cdma1xServingSystemResults, 1);
+ results->sid = sid;
+ results->nid = nid;
+ if (sid != MM_MODEM_CDMA_SID_UNKNOWN) {
+ results->band = 'Z';
+ results->class = 0;
+ }
+
+ g_simple_async_result_set_op_res_gpointer (ctx->result, results, (GDestroyNotify)g_free);
+ cdma1x_serving_system_context_complete_and_free (ctx);
+}
+
+static void
+modem_cdma_get_cdma1x_serving_system (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Cdma1xServingSystemContext *ctx;
+
+ /* Setup context */
+ ctx = g_new0 (Cdma1xServingSystemContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_cdma_get_cdma1x_serving_system);
+ ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
+
+ if (ctx->qcdm) {
+ GByteArray *cdma_status;
+
+ /* Setup command */
+ cdma_status = g_byte_array_sized_new (25);
+ cdma_status->len = qcdm_cmd_cdma_status_new ((char *) cdma_status->data, 25);
+ g_assert (cdma_status->len);
+ mm_qcdm_serial_port_queue_command (ctx->qcdm,
+ cdma_status,
+ 3,
+ NULL,
+ (MMQcdmSerialResponseFn)qcdm_cdma_status_ready,
+ ctx);
+ return;
+ }
+
+ /* Try with AT if we don't have QCDM */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CSS?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)css_query_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Service status, analog/digital check (CDMA interface) */
+
+static gboolean
+modem_cdma_get_service_status_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ gboolean *has_cdma_service,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ *has_cdma_service = g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res));
+ return TRUE;
+}
+
+static void
+cad_query_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+ const gchar *result;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_simple_async_result_take_error (simple, error);
+ else {
+ guint cad;
+
+ /* Strip any leading command tag and spaces */
+ result = mm_strip_tag (result, "+CAD:");
+ if (!mm_get_uint_from_str (result, &cad))
+ g_simple_async_result_set_error (simple,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse +CAD response '%s'",
+ result);
+ else
+ /* 1 == CDMA service */
+ g_simple_async_result_set_op_res_gboolean (simple, (cad == 1));
+ }
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+modem_cdma_get_service_status (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_cdma_get_service_status);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CAD?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)cad_query_ready,
+ result);
+}
+
+/*****************************************************************************/
+/* Detailed registration state (CDMA interface) */
+
+typedef struct {
+ MMModemCdmaRegistrationState detailed_cdma1x_state;
+ MMModemCdmaRegistrationState detailed_evdo_state;
+} DetailedRegistrationStateResults;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ MMAtSerialPort *port;
+ MMModemCdmaRegistrationState cdma1x_state;
+ MMModemCdmaRegistrationState evdo_state;
+ GError *error;
+} DetailedRegistrationStateContext;
+
+static void
+detailed_registration_state_context_complete_and_free (DetailedRegistrationStateContext *ctx)
+{
+ if (ctx->error)
+ g_simple_async_result_take_error (ctx->result, ctx->error);
+ else {
+ DetailedRegistrationStateResults *results;
+
+ results = g_new (DetailedRegistrationStateResults, 1);
+ results->detailed_cdma1x_state = ctx->cdma1x_state;
+ results->detailed_evdo_state = ctx->evdo_state;
+ g_simple_async_result_set_op_res_gpointer (ctx->result, results, g_free);
+ }
+
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->port);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+modem_cdma_get_detailed_registration_state_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ MMModemCdmaRegistrationState *detailed_cdma1x_state,
+ MMModemCdmaRegistrationState *detailed_evdo_state,
+ GError **error)
+{
+ DetailedRegistrationStateResults *results;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ results = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ *detailed_cdma1x_state = results->detailed_cdma1x_state;
+ *detailed_evdo_state = results->detailed_evdo_state;
+ return TRUE;
+}
+
+static void
+speri_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ DetailedRegistrationStateContext *ctx)
+{
+ gboolean roaming = FALSE;
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ /* silently discard SPERI errors */
+ g_error_free (error);
+ detailed_registration_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Try to parse the results */
+ response = mm_strip_tag (response, "$SPERI:");
+ if (!response ||
+ !mm_cdma_parse_eri (response, &roaming, NULL, NULL)) {
+ mm_warn ("Couldn't parse SPERI response '%s'", response);
+ detailed_registration_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (roaming) {
+ /* Change the 1x and EVDO registration states to roaming if they were
+ * anything other than UNKNOWN.
+ */
+ if (ctx->cdma1x_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ ctx->cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+ if (ctx->evdo_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+ ctx->evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
+ } else {
+ /* Change 1x and/or EVDO registration state to home if home/roaming wasn't previously known */
+ if (ctx->cdma1x_state == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED)
+ ctx->cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+ if (ctx->evdo_state == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED)
+ ctx->evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
+ }
+
+ detailed_registration_state_context_complete_and_free (ctx);
+}
+
+static void
+spservice_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ DetailedRegistrationStateContext *ctx)
+{
+ const gchar *response;
+ MMModemCdmaRegistrationState cdma1x_state;
+ MMModemCdmaRegistrationState evdo_state;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &ctx->error);
+ if (ctx->error) {
+ detailed_registration_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Try to parse the results */
+ cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ if (!mm_cdma_parse_spservice_read_response (response,
+ &cdma1x_state,
+ &evdo_state)) {
+ ctx->error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse SPSERVICE response '%s'",
+ response);
+ detailed_registration_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Store new intermediate results */
+ ctx->cdma1x_state = cdma1x_state;
+ ctx->evdo_state = evdo_state;
+
+ /* If SPERI not supported, we're done */
+ if (!ctx->self->priv->has_speri) {
+ detailed_registration_state_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Get roaming status to override generic registration state */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$SPERI?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)speri_ready,
+ ctx);
+}
+
+static void
+modem_cdma_get_detailed_registration_state (MMIfaceModemCdma *self,
+ MMModemCdmaRegistrationState cdma1x_state,
+ MMModemCdmaRegistrationState evdo_state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMAtSerialPort *port;
+ GError *error = NULL;
+ DetailedRegistrationStateContext *ctx;
+
+ /* The default implementation to get detailed registration state
+ * requires the use of an AT port; so if we cannot get any, just
+ * return the error */
+ port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), &error);
+ if (!port) {
+ g_simple_async_report_take_gerror_in_idle (G_OBJECT (self),
+ callback,
+ user_data,
+ error);
+ return;
+ }
+
+ /* Setup context */
+ ctx = g_new0 (DetailedRegistrationStateContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_cdma_get_detailed_registration_state);
+ ctx->port = g_object_ref (port);
+ ctx->cdma1x_state = cdma1x_state;
+ ctx->evdo_state = evdo_state;
+
+ /* NOTE: If we get this generic implementation of getting detailed
+ * registration state called, we DO know that we have Sprint commands
+ * supported, we checked it in setup_registration_checks() */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+SPSERVICE?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)spservice_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Setup registration checks (CDMA interface) */
+
+typedef struct {
+ gboolean skip_qcdm_call_manager_step;
+ gboolean skip_qcdm_hdr_step;
+ gboolean skip_at_cdma_service_status_step;
+ gboolean skip_at_cdma1x_serving_system_step;
+ gboolean skip_detailed_registration_state;
+} SetupRegistrationChecksResults;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ GError *error;
+ gboolean has_qcdm_port;
+ gboolean has_sprint_commands;
+} SetupRegistrationChecksContext;
+
+static void
+setup_registration_checks_context_complete_and_free (SetupRegistrationChecksContext *ctx)
+{
+ if (ctx->error)
+ g_simple_async_result_take_error (ctx->result, ctx->error);
+ else {
+ SetupRegistrationChecksResults *results;
+
+ results = g_new0 (SetupRegistrationChecksResults, 1);
+
+ /* Skip QCDM steps if no QCDM port */
+ if (!ctx->has_qcdm_port) {
+ mm_dbg ("Will skip all QCDM-based registration checks");
+ results->skip_qcdm_call_manager_step = TRUE;
+ results->skip_qcdm_hdr_step = TRUE;
+ }
+
+ if (MM_IFACE_MODEM_CDMA_GET_INTERFACE (ctx->self)->get_detailed_registration_state ==
+ modem_cdma_get_detailed_registration_state) {
+ /* Skip CDMA1x Serving System check if we have Sprint specific
+ * commands AND if the default detailed registration checker
+ * is the generic one. Implementations knowing that their
+ * CSS response is undesired, should either setup NULL callbacks
+ * for the specific step, or subclass this setup and return
+ * FALSE themselves. */
+ if (ctx->has_sprint_commands) {
+ mm_dbg ("Will skip CDMA1x Serving System check, "
+ "we do have Sprint commands");
+ results->skip_at_cdma1x_serving_system_step = TRUE;
+ } else {
+ /* If there aren't Sprint specific commands, and the detailed
+ * registration state getter wasn't subclassed, skip the step */
+ mm_dbg ("Will skip generic detailed registration check, we "
+ "don't have Sprint commands");
+ results->skip_detailed_registration_state = TRUE;
+ }
+ }
+
+ g_simple_async_result_set_op_res_gpointer (ctx->result, results, g_free);
+ }
+
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+modem_cdma_setup_registration_checks_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ gboolean *skip_qcdm_call_manager_step,
+ gboolean *skip_qcdm_hdr_step,
+ gboolean *skip_at_cdma_service_status_step,
+ gboolean *skip_at_cdma1x_serving_system_step,
+ gboolean *skip_detailed_registration_state,
+ GError **error)
+{
+ SetupRegistrationChecksResults *results;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ results = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ *skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step;
+ *skip_qcdm_hdr_step = results->skip_qcdm_hdr_step;
+ *skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step;
+ *skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step;
+ *skip_detailed_registration_state = results->skip_detailed_registration_state;
+ return TRUE;
+}
+
+static void
+speri_check_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ SetupRegistrationChecksContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error)
+ g_error_free (error);
+ else
+ /* We DO have SPERI */
+ ctx->self->priv->has_speri = TRUE;
+
+ /* All done */
+ ctx->self->priv->checked_sprint_support = TRUE;
+ setup_registration_checks_context_complete_and_free (ctx);
+}
+
+static void
+spservice_check_ready (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ SetupRegistrationChecksContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (error) {
+ g_error_free (error);
+ ctx->self->priv->checked_sprint_support = TRUE;
+ setup_registration_checks_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* We DO have SPSERVICE, look for SPERI */
+ ctx->has_sprint_commands = TRUE;
+ ctx->self->priv->has_spservice = TRUE;
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "$SPERI?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)speri_check_ready,
+ ctx);
+}
+
+static void
+modem_cdma_setup_registration_checks (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SetupRegistrationChecksContext *ctx;
+
+ ctx = g_new0 (SetupRegistrationChecksContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_cdma_setup_registration_checks);
+
+ /* Check if we have a QCDM port */
+ ctx->has_qcdm_port = !!mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
+
+ /* If we have cached results of Sprint command checking, use them */
+ if (ctx->self->priv->checked_sprint_support) {
+ ctx->has_sprint_commands = ctx->self->priv->has_spservice;
+
+ /* Completes in idle */
+ setup_registration_checks_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Otherwise, launch Sprint command checks. */
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+SPSERVICE?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)spservice_check_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Register in network (CDMA interface) */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ GCancellable *cancellable;
+ GTimer *timer;
+ guint max_registration_time;
+} RegisterInCdmaNetworkContext;
+
+static void
+register_in_cdma_network_context_complete_and_free (RegisterInCdmaNetworkContext *ctx)
+{
+ /* If our cancellable reference is still around, clear it */
+ if (ctx->self->priv->modem_cdma_pending_registration_cancellable ==
+ ctx->cancellable) {
+ g_clear_object (&ctx->self->priv->modem_cdma_pending_registration_cancellable);
+ }
+
+ if (ctx->timer)
+ g_timer_destroy (ctx->timer);
+
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+modem_cdma_register_in_network_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+#undef REG_IS_IDLE
+#define REG_IS_IDLE(state) \
+ (state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
+
+#undef REG_IS_DONE
+#define REG_IS_DONE(state) \
+ (state == MM_MODEM_CDMA_REGISTRATION_STATE_HOME || \
+ state == MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING || \
+ state == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED)
+
+static void run_cdma_registration_checks_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ RegisterInCdmaNetworkContext *ctx);
+
+static gboolean
+run_cdma_registration_checks_again (RegisterInCdmaNetworkContext *ctx)
+{
+ /* Get fresh registration state */
+ mm_iface_modem_cdma_run_registration_checks (
+ MM_IFACE_MODEM_CDMA (ctx->self),
+ (GAsyncReadyCallback)run_cdma_registration_checks_ready,
+ ctx);
+ return FALSE;
+}
+
+static void
+run_cdma_registration_checks_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ RegisterInCdmaNetworkContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_iface_modem_cdma_run_registration_checks_finish (MM_IFACE_MODEM_CDMA (self), res, &error);
+
+ if (error) {
+ mm_dbg ("CDMA registration check failed: '%s'", error->message);
+ mm_iface_modem_cdma_update_cdma1x_registration_state (
+ MM_IFACE_MODEM_CDMA (self),
+ MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+ MM_MODEM_CDMA_SID_UNKNOWN,
+ MM_MODEM_CDMA_NID_UNKNOWN);
+ mm_iface_modem_cdma_update_evdo_registration_state (
+ MM_IFACE_MODEM_CDMA (self),
+ MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ mm_iface_modem_cdma_update_access_technologies (
+ MM_IFACE_MODEM_CDMA (self),
+ MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+
+ g_simple_async_result_take_error (ctx->result, error);
+ register_in_cdma_network_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* If we got registered in at least one CDMA network, end registration checks */
+ if (REG_IS_DONE (self->priv->modem_cdma_cdma1x_registration_state) ||
+ REG_IS_DONE (self->priv->modem_cdma_evdo_registration_state)) {
+ mm_dbg ("Modem is currently registered in a CDMA network "
+ "(CDMA1x: '%s', EV-DO: '%s')",
+ REG_IS_DONE (self->priv->modem_cdma_cdma1x_registration_state) ? "yes" : "no",
+ REG_IS_DONE (self->priv->modem_cdma_evdo_registration_state) ? "yes" : "no");
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ register_in_cdma_network_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Don't spend too much time waiting to get registered */
+ if (g_timer_elapsed (ctx->timer, NULL) > ctx->max_registration_time) {
+ mm_dbg ("CDMA registration check timed out");
+ mm_iface_modem_cdma_update_cdma1x_registration_state (
+ MM_IFACE_MODEM_CDMA (self),
+ MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN,
+ MM_MODEM_CDMA_SID_UNKNOWN,
+ MM_MODEM_CDMA_NID_UNKNOWN);
+ mm_iface_modem_cdma_update_evdo_registration_state (
+ MM_IFACE_MODEM_CDMA (self),
+ MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN);
+ mm_iface_modem_cdma_update_access_technologies (
+ MM_IFACE_MODEM_CDMA (self),
+ MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
+ g_simple_async_result_take_error (
+ ctx->result,
+ mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT));
+ register_in_cdma_network_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Check again in a few seconds. */
+ mm_dbg ("Modem not yet registered in a CDMA network... will recheck soon");
+ g_timeout_add_seconds (3,
+ (GSourceFunc)run_cdma_registration_checks_again,
+ ctx);
+}
+
+static void
+modem_cdma_register_in_network (MMIfaceModemCdma *self,
+ guint max_registration_time,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModem *broadband = MM_BROADBAND_MODEM (self);
+ RegisterInCdmaNetworkContext *ctx;
+
+ /* (Try to) cancel previous registration request */
+ if (broadband->priv->modem_cdma_pending_registration_cancellable) {
+ g_cancellable_cancel (broadband->priv->modem_cdma_pending_registration_cancellable);
+ g_clear_object (&broadband->priv->modem_cdma_pending_registration_cancellable);
+ }
+
+ ctx = g_new0 (RegisterInCdmaNetworkContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->max_registration_time = max_registration_time;
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_cdma_register_in_network);
+ ctx->cancellable = g_cancellable_new ();
+
+ /* Keep an accessible reference to the cancellable, so that we can cancel
+ * previous request when needed */
+ broadband->priv->modem_cdma_pending_registration_cancellable =
+ g_object_ref (ctx->cancellable);
+
+ /* Get fresh registration state */
+ ctx->timer = g_timer_new ();
+ mm_iface_modem_cdma_run_registration_checks (
+ MM_IFACE_MODEM_CDMA (self),
+ (GAsyncReadyCallback)run_cdma_registration_checks_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Load location capabilities (Location interface) */
+
+static MMModemLocationSource
+modem_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return MM_MODEM_LOCATION_SOURCE_NONE;
+
+ return (MMModemLocationSource) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (res)));
+}
+
+static void
+modem_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ modem_location_load_capabilities);
+
+ /* Default location capabilities supported by the generic broadband
+ * implementation are only LAC-CI in 3GPP-enabled modems. And even this,
+ * will only be true if the modem supports CREG/CGREG=2 */
+ if (!mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self)))
+ g_simple_async_result_set_op_res_gpointer (result,
+ GUINT_TO_POINTER (MM_MODEM_LOCATION_SOURCE_NONE),
+ NULL);
+ else
+ g_simple_async_result_set_op_res_gpointer (result,
+ GUINT_TO_POINTER (MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI),
+ NULL);
+
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+static gboolean
+enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ enable_location_gathering);
+
+ /* 3GPP modems need to re-run registration checks when enabling the 3GPP
+ * location source, so that we get up to date LAC/CI location information.
+ * Note that we don't care for when the registration checks get finished.
+ */
+ if (source == MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI &&
+ mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) {
+ /* Reload registration to get LAC/CI */
+ mm_iface_modem_3gpp_run_registration_checks (MM_IFACE_MODEM_3GPP (self), NULL, NULL);
+ /* Reload operator to get MCC/MNC */
+ if (MM_BROADBAND_MODEM (self)->priv->modem_3gpp_registration_state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
+ MM_BROADBAND_MODEM (self)->priv->modem_3gpp_registration_state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
+ mm_iface_modem_3gpp_reload_current_operator (MM_IFACE_MODEM_3GPP (self), NULL, NULL);
+ }
+
+ /* Done we are */
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+
+static const gchar *primary_init_sequence[] = {
+ /* Ensure echo is off */
+ "E0",
+ /* Get word responses */
+ "V1",
+ /* Extended numeric codes */
+ "+CMEE=1",
+ /* Report all call status */
+ "X4",
+ /* Assert DCD when carrier detected */
+ "&C1",
+ NULL
+};
+
+static const gchar *secondary_init_sequence[] = {
+ /* Ensure echo is off */
+ "E0",
+ NULL
+};
+
+static void
+setup_ports (MMBroadbandModem *self)
+{
+ MMAtSerialPort *ports[2];
+ GRegex *regex;
+ GPtrArray *array;
+ gint i, j;
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ if (ports[0])
+ g_object_set (ports[0],
+ MM_AT_SERIAL_PORT_INIT_SEQUENCE, primary_init_sequence,
+ NULL);
+
+ if (ports[1])
+ g_object_set (ports[1],
+ MM_AT_SERIAL_PORT_INIT_SEQUENCE, secondary_init_sequence,
+ NULL);
+
+ /* Cleanup all unsolicited message handlers in all AT ports */
+
+ /* Set up CREG unsolicited message handlers, with NULL callbacks */
+ array = mm_3gpp_creg_regex_get (FALSE);
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ for (j = 0; j < array->len; j++) {
+ mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (ports[i]),
+ (GRegex *)g_ptr_array_index (array, j),
+ NULL,
+ NULL,
+ NULL);
+ }
+ }
+ mm_3gpp_creg_regex_destroy (array);
+
+ /* Set up CIEV unsolicited message handler, with NULL callback */
+ regex = mm_3gpp_ciev_regex_get ();
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (ports[i]),
+ regex,
+ NULL,
+ NULL,
+ NULL);
+ }
+ g_regex_unref (regex);
+
+ /* Set up CMTI unsolicited message handler, with NULL callback */
+ regex = mm_3gpp_cmti_regex_get ();
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (ports[i]),
+ regex,
+ NULL,
+ NULL,
+ NULL);
+ }
+ g_regex_unref (regex);
+
+ /* Set up CUSD unsolicited message handler, with NULL callback */
+ regex = mm_3gpp_cusd_regex_get ();
+ for (i = 0; i < 2; i++) {
+ if (!ports[i])
+ continue;
+
+ mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (ports[i]),
+ regex,
+ NULL,
+ NULL,
+ NULL);
+ }
+ g_regex_unref (regex);
+}
+
+/*****************************************************************************/
+/* Generic ports open/close context */
+
+struct _PortsContext {
+ volatile gint ref_count;
+
+ MMAtSerialPort *primary;
+ gboolean primary_open;
+ MMAtSerialPort *secondary;
+ gboolean secondary_open;
+ MMQcdmSerialPort *qcdm;
+ gboolean qcdm_open;
+};
+
+static PortsContext *
+ports_context_ref (PortsContext *ctx)
+{
+ g_atomic_int_inc (&ctx->ref_count);
+ return ctx;
+}
+
+static void
+ports_context_unref (PortsContext *ctx)
+{
+ if (g_atomic_int_dec_and_test (&ctx->ref_count)) {
+ if (ctx->primary) {
+ if (ctx->primary_open)
+ mm_serial_port_close (MM_SERIAL_PORT (ctx->primary));
+ g_object_unref (ctx->primary);
+ }
+ if (ctx->secondary) {
+ if (ctx->secondary_open)
+ mm_serial_port_close (MM_SERIAL_PORT (ctx->secondary));
+ g_object_unref (ctx->secondary);
+ }
+ if (ctx->qcdm) {
+ if (ctx->qcdm_open)
+ mm_serial_port_close (MM_SERIAL_PORT (ctx->qcdm));
+ g_object_unref (ctx->qcdm);
+ }
+ g_free (ctx);
+ }
+}
+
+/*****************************************************************************/
+/* Initialization started/stopped */
+
+static gboolean
+initialization_stopped (MMBroadbandModem *self,
+ gpointer user_data,
+ GError **error)
+{
+ PortsContext *ctx = (PortsContext *)user_data;
+
+ if (ctx)
+ ports_context_unref (ctx);
+ return TRUE;
+}
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ PortsContext *ports;
+} InitializationStartedContext;
+
+static void
+initialization_started_context_complete_and_free (InitializationStartedContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ ports_context_unref (ctx->ports);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gpointer
+initialization_started_finish (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ gpointer ref;
+
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return NULL;
+
+ ref = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
+ return ref ? ports_context_ref (ref) : NULL;
+}
+
+static gboolean
+open_ports_initialization (MMBroadbandModem *self,
+ PortsContext *ctx,
+ GError **error)
+{
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ if (!ctx->primary) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get primary port");
+ return FALSE;
+ }
+
+ /* Open and send first commands to the primary serial port.
+ * We do keep the primary port open during the whole initialization
+ * sequence. Note that this port is not really passed to the interfaces,
+ * they will get the primary port themselves. */
+ if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->primary), error)) {
+ g_prefix_error (error, "Couldn't open primary port: ");
+ return FALSE;
+ }
+
+ ctx->primary_open = TRUE;
+
+ /* Try to disable echo */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->primary,
+ "E0", 3,
+ FALSE, FALSE, NULL, NULL, NULL);
+ /* Try to get extended errors */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ctx->primary,
+ "+CMEE=1", 3,
+ FALSE, FALSE, NULL, NULL, NULL);
+
+ return TRUE;
+}
+
+static void
+initialization_started (MMBroadbandModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ InitializationStartedContext *ctx;
+
+ ctx = g_new0 (InitializationStartedContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ initialization_started);
+ ctx->ports = g_new0 (PortsContext, 1);
+ ctx->ports->ref_count = 1;
+
+ if (!open_ports_initialization (self, ctx->ports, &error)) {
+ g_prefix_error (&error, "Couldn't open ports during modem initialization: ");
+ g_simple_async_result_take_error (ctx->result, error);
+ } else
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ ports_context_ref (ctx->ports),
+ (GDestroyNotify)ports_context_unref);
+
+ initialization_started_context_complete_and_free (ctx);
+}
+
+/*****************************************************************************/
+/* Disabling stopped */
+
+static gboolean
+disabling_stopped (MMBroadbandModem *self,
+ GError **error)
+{
+ if (self->priv->enabled_ports_ctx) {
+ ports_context_unref (self->priv->enabled_ports_ctx);
+ self->priv->enabled_ports_ctx = NULL;
+ }
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Initializing the modem (during first enabling) */
+
+static gboolean
+enabling_modem_init_finish (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, error);
+}
+
+static void
+enabling_modem_init (MMBroadbandModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Init command. ITU rec v.250 (6.1.1) says:
+ * The DTE should not include additional commands on the same command line
+ * after the Z command because such commands may be ignored.
+ * So run ATZ alone.
+ */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
+ "Z",
+ 6,
+ FALSE,
+ FALSE,
+ NULL, /* cancellable */
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
+/* Enabling started */
+
+typedef struct {
+ MMBroadbandModem *self;
+ GSimpleAsyncResult *result;
+ PortsContext *ports;
+ gboolean modem_init_required;
+} EnablingStartedContext;
+
+static void
+enabling_started_context_complete_and_free (EnablingStartedContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ ports_context_unref (ctx->ports);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_slice_free (EnablingStartedContext, ctx);
+}
+
+static gboolean
+enabling_started_finish (MMBroadbandModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static gboolean
+enabling_after_modem_init_timeout (EnablingStartedContext *ctx)
+{
+ /* Reset init sequence enabled flags and run them explicitly */
+ g_object_set (ctx->ports->primary,
+ MM_AT_SERIAL_PORT_INIT_SEQUENCE_ENABLED, TRUE,
+ NULL);
+ mm_at_serial_port_run_init_sequence (ctx->ports->primary);
+ if (ctx->ports->secondary) {
+ g_object_set (ctx->ports->secondary,
+ MM_AT_SERIAL_PORT_INIT_SEQUENCE_ENABLED, TRUE,
+ NULL);
+ mm_at_serial_port_run_init_sequence (ctx->ports->secondary);
+ }
+
+ /* Store enabled ports context and complete */
+ ctx->self->priv->enabled_ports_ctx = ports_context_ref (ctx->ports);
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ enabling_started_context_complete_and_free (ctx);
+ return FALSE;
+}
+
+static void
+enabling_modem_init_ready (MMBroadbandModem *self,
+ GAsyncResult *res,
+ EnablingStartedContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->enabling_modem_init_finish (self, res, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ enabling_started_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Specify that the modem init was run once */
+ ctx->self->priv->modem_init_run = TRUE;
+
+ /* After the modem init sequence, give a 500ms period for the modem to settle */
+ mm_dbg ("Giving some time to settle the modem...");
+ g_timeout_add (500, (GSourceFunc)enabling_after_modem_init_timeout, ctx);
+}
+
+static void
+enabling_flash_done (MMSerialPort *port,
+ GError *error,
+ EnablingStartedContext *ctx)
+{
+ if (error) {
+ g_prefix_error (&error, "Primary port flashing failed: ");
+ g_simple_async_result_set_from_error (ctx->result, error);
+ enabling_started_context_complete_and_free (ctx);
+ return;
+ }
+
+
+ if (ctx->modem_init_required) {
+ mm_dbg ("Running modem initialization sequence...");
+ MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->enabling_modem_init (ctx->self,
+ (GAsyncReadyCallback)enabling_modem_init_ready,
+ ctx);
+ return;
+ }
+
+ /* Store enabled ports context and complete */
+ ctx->self->priv->enabled_ports_ctx = ports_context_ref (ctx->ports);
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ enabling_started_context_complete_and_free (ctx);
+}
+
+static gboolean
+open_ports_enabling (MMBroadbandModem *self,
+ PortsContext *ctx,
+ gboolean modem_init_required,
+ GError **error)
+{
+ /* Open primary */
+ ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ if (!ctx->primary) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't get primary port");
+ return FALSE;
+ }
+
+ /* If we'll need to run modem initialization, disable port init sequence */
+ if (modem_init_required)
+ g_object_set (ctx->primary,
+ MM_AT_SERIAL_PORT_INIT_SEQUENCE_ENABLED, FALSE,
+ NULL);
+
+
+ if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->primary), error)) {
+ g_prefix_error (error, "Couldn't open primary port: ");
+ return FALSE;
+ }
+
+ ctx->primary_open = TRUE;
+
+ /* Open secondary (optional) */
+ ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ if (ctx->secondary) {
+ /* If we'll need to run modem initialization, disable port init sequence */
+ if (modem_init_required)
+ g_object_set (ctx->secondary,
+ MM_AT_SERIAL_PORT_INIT_SEQUENCE_ENABLED, FALSE,
+ NULL);
+ if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->secondary), error)) {
+ g_prefix_error (error, "Couldn't open secondary port: ");
+ return FALSE;
+ }
+ ctx->secondary_open = TRUE;
+ }
+
+ /* Open qcdm (optional) */
+ ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
+ if (ctx->qcdm) {
+ if (!mm_serial_port_open (MM_SERIAL_PORT (ctx->qcdm), error)) {
+ g_prefix_error (error, "Couldn't open QCDM port: ");
+ return FALSE;
+ }
+ ctx->qcdm_open = TRUE;
+ }
+
+ return TRUE;
+}
+
+static void
+enabling_started (MMBroadbandModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ EnablingStartedContext *ctx;
+
+ ctx = g_slice_new0 (EnablingStartedContext);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ enabling_started);
+ ctx->ports = g_new0 (PortsContext, 1);
+ ctx->ports->ref_count = 1;
+
+ /* Skip modem initialization if the device was hotplugged OR if we already
+ * did it (i.e. don't reinitialize if the modem got disabled and enabled
+ * again) */
+ if (ctx->self->priv->modem_init_run)
+ mm_dbg ("Skipping modem initialization: not first enabling");
+ else if (mm_base_modem_get_hotplugged (MM_BASE_MODEM (ctx->self))) {
+ ctx->self->priv->modem_init_run = TRUE;
+ mm_dbg ("Skipping modem initialization: device hotplugged");
+ } else if (!MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->enabling_modem_init ||
+ !MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->enabling_modem_init_finish)
+ mm_dbg ("Skipping modem initialization: not required");
+ else
+ ctx->modem_init_required = TRUE;
+
+ /* Enabling */
+ if (!open_ports_enabling (self, ctx->ports, ctx->modem_init_required, &error)) {
+ g_prefix_error (&error, "Couldn't open ports during modem enabling: ");
+ g_simple_async_result_take_error (ctx->result, error);
+ enabling_started_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Ports were correctly opened, now flash the primary port */
+ mm_dbg ("Flashing primary AT port before enabling...");
+ mm_serial_port_flash (MM_SERIAL_PORT (ctx->ports->primary),
+ 100,
+ FALSE,
+ (MMSerialFlashFn)enabling_flash_done,
+ ctx);
+}
+
+/*****************************************************************************/
+
+typedef enum {
+ DISABLING_STEP_FIRST,
+ DISABLING_STEP_WAIT_FOR_FINAL_STATE,
+ DISABLING_STEP_DISCONNECT_BEARERS,
+ DISABLING_STEP_IFACE_SIMPLE,
+ DISABLING_STEP_IFACE_FIRMWARE,
+ DISABLING_STEP_IFACE_TIME,
+ DISABLING_STEP_IFACE_MESSAGING,
+ DISABLING_STEP_IFACE_LOCATION,
+ DISABLING_STEP_IFACE_CONTACTS,
+ DISABLING_STEP_IFACE_CDMA,
+ DISABLING_STEP_IFACE_3GPP_USSD,
+ DISABLING_STEP_IFACE_3GPP,
+ DISABLING_STEP_IFACE_MODEM,
+ DISABLING_STEP_LAST,
+} DisablingStep;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+ DisablingStep step;
+ MMModemState previous_state;
+ gboolean disabled;
+} DisablingContext;
+
+static void disabling_step (DisablingContext *ctx);
+
+static void
+disabling_context_complete_and_free (DisablingContext *ctx)
+{
+ GError *error = NULL;
+
+ g_simple_async_result_complete_in_idle (ctx->result);
+
+ if (MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->disabling_stopped &&
+ !MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->disabling_stopped (ctx->self, &error)) {
+ mm_warn ("Error when stopping the disabling sequence: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (ctx->disabled)
+ mm_iface_modem_update_state (MM_IFACE_MODEM (ctx->self),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
+ else if (ctx->previous_state != MM_MODEM_STATE_DISABLED) {
+ /* Fallback to previous state */
+ mm_iface_modem_update_state (MM_IFACE_MODEM (ctx->self),
+ ctx->previous_state,
+ MM_MODEM_STATE_CHANGE_REASON_UNKNOWN);
+ }
+
+ g_object_unref (ctx->result);
+ if (ctx->cancellable)
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+disabling_context_complete_and_free_if_cancelled (DisablingContext *ctx)
+{
+ if (!g_cancellable_is_cancelled (ctx->cancellable))
+ return FALSE;
+
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Disabling cancelled");
+ disabling_context_complete_and_free (ctx);
+ return TRUE;
+}
+
+static gboolean
+disable_finish (MMBaseModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+#undef INTERFACE_DISABLE_READY_FN
+#define INTERFACE_DISABLE_READY_FN(NAME,TYPE,FATAL_ERRORS) \
+ static void \
+ NAME##_disable_ready (MMBroadbandModem *self, \
+ GAsyncResult *result, \
+ DisablingContext *ctx) \
+ { \
+ GError *error = NULL; \
+ \
+ if (!mm_##NAME##_disable_finish (TYPE (self), \
+ result, \
+ &error)) { \
+ if (FATAL_ERRORS) { \
+ g_simple_async_result_take_error (G_SIMPLE_ASYNC_RESULT (ctx->result), error); \
+ disabling_context_complete_and_free (ctx); \
+ return; \
+ } \
+ \
+ mm_dbg ("Couldn't disable interface: '%s'", \
+ error->message); \
+ g_error_free (error); \
+ return; \
+ } \
+ \
+ /* Go on to next step */ \
+ ctx->step++; \
+ disabling_step (ctx); \
+ }
+
+INTERFACE_DISABLE_READY_FN (iface_modem, MM_IFACE_MODEM, TRUE)
+INTERFACE_DISABLE_READY_FN (iface_modem_3gpp, MM_IFACE_MODEM_3GPP, TRUE)
+INTERFACE_DISABLE_READY_FN (iface_modem_3gpp_ussd, MM_IFACE_MODEM_3GPP_USSD, TRUE)
+INTERFACE_DISABLE_READY_FN (iface_modem_cdma, MM_IFACE_MODEM_CDMA, TRUE)
+INTERFACE_DISABLE_READY_FN (iface_modem_location, MM_IFACE_MODEM_LOCATION, FALSE)
+INTERFACE_DISABLE_READY_FN (iface_modem_messaging, MM_IFACE_MODEM_MESSAGING, FALSE)
+INTERFACE_DISABLE_READY_FN (iface_modem_time, MM_IFACE_MODEM_TIME, FALSE)
+
+static void
+bearer_list_disconnect_all_bearers_ready (MMBearerList *list,
+ GAsyncResult *res,
+ DisablingContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_bearer_list_disconnect_all_bearers_finish (list, res, &error)) {
+ g_simple_async_result_take_error (G_SIMPLE_ASYNC_RESULT (ctx->result), error);
+ disabling_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ disabling_step (ctx);
+}
+
+static void
+disabling_wait_for_final_state_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ DisablingContext *ctx)
+{
+ GError *error = NULL;
+
+ ctx->previous_state = mm_iface_modem_wait_for_final_state_finish (self, res, &error);
+ if (error) {
+ g_simple_async_result_take_error (G_SIMPLE_ASYNC_RESULT (ctx->result), error);
+ disabling_context_complete_and_free (ctx);
+ return;
+ }
+
+ switch (ctx->previous_state) {
+ case MM_MODEM_STATE_UNKNOWN:
+ case MM_MODEM_STATE_FAILED:
+ case MM_MODEM_STATE_LOCKED:
+ case MM_MODEM_STATE_DISABLED:
+ /* Just return success, don't relaunch disabling.
+ * Note that we do consider here UNKNOWN and FAILED status on purpose,
+ * as the MMManager will try to disable every modem before removing
+ * it. */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ disabling_context_complete_and_free (ctx);
+ return;
+ default:
+ break;
+ }
+
+ /* We're in a final state now, go on */
+
+ mm_iface_modem_update_state (MM_IFACE_MODEM (ctx->self),
+ MM_MODEM_STATE_DISABLING,
+ MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
+
+ ctx->step++;
+ disabling_step (ctx);
+}
+
+static void
+disabling_step (DisablingContext *ctx)
+{
+ /* Don't run new steps if we're cancelled */
+ if (disabling_context_complete_and_free_if_cancelled (ctx))
+ return;
+
+ switch (ctx->step) {
+ case DISABLING_STEP_FIRST:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_WAIT_FOR_FINAL_STATE:
+ mm_iface_modem_wait_for_final_state (MM_IFACE_MODEM (ctx->self),
+ MM_MODEM_STATE_UNKNOWN, /* just any */
+ (GAsyncReadyCallback)disabling_wait_for_final_state_ready,
+ ctx);
+ return;
+
+ case DISABLING_STEP_DISCONNECT_BEARERS:
+ if (ctx->self->priv->modem_bearer_list) {
+ mm_bearer_list_disconnect_all_bearers (
+ ctx->self->priv->modem_bearer_list,
+ (GAsyncReadyCallback)bearer_list_disconnect_all_bearers_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_SIMPLE:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_FIRMWARE:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_TIME:
+ if (ctx->self->priv->modem_time_dbus_skeleton) {
+ mm_dbg ("Modem has time capabilities, disabling the Time interface...");
+ /* Disabling the Modem Time interface */
+ mm_iface_modem_time_disable (MM_IFACE_MODEM_TIME (ctx->self),
+ (GAsyncReadyCallback)iface_modem_time_disable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_MESSAGING:
+ if (ctx->self->priv->modem_messaging_dbus_skeleton) {
+ mm_dbg ("Modem has messaging capabilities, disabling the Messaging interface...");
+ /* Disabling the Modem Messaging interface */
+ mm_iface_modem_messaging_disable (MM_IFACE_MODEM_MESSAGING (ctx->self),
+ (GAsyncReadyCallback)iface_modem_messaging_disable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_LOCATION:
+ if (ctx->self->priv->modem_location_dbus_skeleton) {
+ mm_dbg ("Modem has location capabilities, disabling the Location interface...");
+ /* Disabling the Modem Location interface */
+ mm_iface_modem_location_disable (MM_IFACE_MODEM_LOCATION (ctx->self),
+ (GAsyncReadyCallback)iface_modem_location_disable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_CONTACTS:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_CDMA:
+ if (ctx->self->priv->modem_cdma_dbus_skeleton) {
+ mm_dbg ("Modem has CDMA capabilities, disabling the Modem CDMA interface...");
+ /* Disabling the Modem CDMA interface */
+ mm_iface_modem_cdma_disable (MM_IFACE_MODEM_CDMA (ctx->self),
+ (GAsyncReadyCallback)iface_modem_cdma_disable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_3GPP_USSD:
+ if (ctx->self->priv->modem_3gpp_ussd_dbus_skeleton) {
+ mm_dbg ("Modem has 3GPP/USSD capabilities, disabling the Modem 3GPP/USSD interface...");
+ /* Disabling the Modem 3GPP USSD interface */
+ mm_iface_modem_3gpp_ussd_disable (MM_IFACE_MODEM_3GPP_USSD (ctx->self),
+ (GAsyncReadyCallback)iface_modem_3gpp_ussd_disable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_3GPP:
+ if (ctx->self->priv->modem_3gpp_dbus_skeleton) {
+ mm_dbg ("Modem has 3GPP capabilities, disabling the Modem 3GPP interface...");
+ /* Disabling the Modem 3GPP interface */
+ mm_iface_modem_3gpp_disable (MM_IFACE_MODEM_3GPP (ctx->self),
+ (GAsyncReadyCallback)iface_modem_3gpp_disable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_IFACE_MODEM:
+ /* This skeleton may be NULL when mm_base_modem_disable() gets called at
+ * the same time as modem object disposal. */
+ if (ctx->self->priv->modem_dbus_skeleton) {
+ /* Disabling the Modem interface */
+ mm_iface_modem_disable (MM_IFACE_MODEM (ctx->self),
+ (GAsyncReadyCallback)iface_modem_disable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case DISABLING_STEP_LAST:
+ ctx->disabled = TRUE;
+ /* All disabled without errors! */
+ g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (ctx->result), TRUE);
+ disabling_context_complete_and_free (ctx);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+disable (MMBaseModem *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DisablingContext *ctx;
+
+ ctx = g_new0 (DisablingContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, disable);
+ ctx->cancellable = (cancellable ? g_object_ref (cancellable) : NULL);
+ ctx->step = DISABLING_STEP_FIRST;
+
+ disabling_step (ctx);
+}
+
+/*****************************************************************************/
+
+typedef enum {
+ ENABLING_STEP_FIRST,
+ ENABLING_STEP_WAIT_FOR_FINAL_STATE,
+ ENABLING_STEP_STARTED,
+ ENABLING_STEP_IFACE_MODEM,
+ ENABLING_STEP_IFACE_3GPP,
+ ENABLING_STEP_IFACE_3GPP_USSD,
+ ENABLING_STEP_IFACE_CDMA,
+ ENABLING_STEP_IFACE_CONTACTS,
+ ENABLING_STEP_IFACE_LOCATION,
+ ENABLING_STEP_IFACE_MESSAGING,
+ ENABLING_STEP_IFACE_TIME,
+ ENABLING_STEP_IFACE_FIRMWARE,
+ ENABLING_STEP_IFACE_SIMPLE,
+ ENABLING_STEP_LAST,
+} EnablingStep;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+ EnablingStep step;
+ MMModemState previous_state;
+ gboolean enabled;
+} EnablingContext;
+
+static void enabling_step (EnablingContext *ctx);
+
+static void
+enabling_context_complete_and_free (EnablingContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+
+ if (ctx->enabled)
+ mm_iface_modem_update_state (MM_IFACE_MODEM (ctx->self),
+ MM_MODEM_STATE_ENABLED,
+ MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
+ else if (ctx->previous_state != MM_MODEM_STATE_ENABLED) {
+ /* Fallback to previous state */
+ mm_iface_modem_update_state (MM_IFACE_MODEM (ctx->self),
+ ctx->previous_state,
+ MM_MODEM_STATE_CHANGE_REASON_UNKNOWN);
+ }
+
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+enabling_context_complete_and_free_if_cancelled (EnablingContext *ctx)
+{
+ if (!g_cancellable_is_cancelled (ctx->cancellable))
+ return FALSE;
+
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Enabling cancelled");
+ enabling_context_complete_and_free (ctx);
+ return TRUE;
+}
+
+static gboolean
+enable_finish (MMBaseModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ return TRUE;
+}
+
+#undef INTERFACE_ENABLE_READY_FN
+#define INTERFACE_ENABLE_READY_FN(NAME,TYPE,FATAL_ERRORS) \
+ static void \
+ NAME##_enable_ready (MMBroadbandModem *self, \
+ GAsyncResult *result, \
+ EnablingContext *ctx) \
+ { \
+ GError *error = NULL; \
+ \
+ if (!mm_##NAME##_enable_finish (TYPE (self), \
+ result, \
+ &error)) { \
+ if (FATAL_ERRORS) { \
+ g_simple_async_result_take_error (G_SIMPLE_ASYNC_RESULT (ctx->result), error); \
+ enabling_context_complete_and_free (ctx); \
+ return; \
+ } \
+ \
+ mm_dbg ("Couldn't enable interface: '%s'", \
+ error->message); \
+ g_error_free (error); \
+ } \
+ \
+ /* Go on to next step */ \
+ ctx->step++; \
+ enabling_step (ctx); \
+ }
+
+INTERFACE_ENABLE_READY_FN (iface_modem, MM_IFACE_MODEM, TRUE)
+INTERFACE_ENABLE_READY_FN (iface_modem_3gpp, MM_IFACE_MODEM_3GPP, TRUE)
+INTERFACE_ENABLE_READY_FN (iface_modem_3gpp_ussd, MM_IFACE_MODEM_3GPP_USSD, TRUE)
+INTERFACE_ENABLE_READY_FN (iface_modem_cdma, MM_IFACE_MODEM_CDMA, TRUE)
+INTERFACE_ENABLE_READY_FN (iface_modem_location, MM_IFACE_MODEM_LOCATION, FALSE)
+INTERFACE_ENABLE_READY_FN (iface_modem_messaging, MM_IFACE_MODEM_MESSAGING, FALSE)
+INTERFACE_ENABLE_READY_FN (iface_modem_time, MM_IFACE_MODEM_TIME, FALSE)
+
+static void
+enabling_started_ready (MMBroadbandModem *self,
+ GAsyncResult *result,
+ EnablingContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_started_finish (self, result, &error)) {
+ g_simple_async_result_take_error (G_SIMPLE_ASYNC_RESULT (ctx->result), error);
+ enabling_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ enabling_step (ctx);
+}
+
+static void
+enabling_wait_for_final_state_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ EnablingContext *ctx)
+{
+ GError *error = NULL;
+
+ ctx->previous_state = mm_iface_modem_wait_for_final_state_finish (self, res, &error);
+ if (error) {
+ g_simple_async_result_take_error (G_SIMPLE_ASYNC_RESULT (ctx->result), error);
+ enabling_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (ctx->previous_state >= MM_MODEM_STATE_ENABLED) {
+ /* Just return success, don't relaunch enabling */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ enabling_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* We're in a final state now, go on */
+
+ mm_iface_modem_update_state (MM_IFACE_MODEM (ctx->self),
+ MM_MODEM_STATE_ENABLING,
+ MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
+
+ ctx->step++;
+ enabling_step (ctx);
+}
+
+static void
+enabling_step (EnablingContext *ctx)
+{
+ /* Don't run new steps if we're cancelled */
+ if (enabling_context_complete_and_free_if_cancelled (ctx))
+ return;
+
+ switch (ctx->step) {
+ case ENABLING_STEP_FIRST:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_WAIT_FOR_FINAL_STATE:
+ mm_iface_modem_wait_for_final_state (MM_IFACE_MODEM (ctx->self),
+ MM_MODEM_STATE_UNKNOWN, /* just any */
+ (GAsyncReadyCallback)enabling_wait_for_final_state_ready,
+ ctx);
+ return;
+
+ case ENABLING_STEP_STARTED:
+ if (MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->enabling_started &&
+ MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->enabling_started_finish) {
+ MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->enabling_started (ctx->self,
+ (GAsyncReadyCallback)enabling_started_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_MODEM:
+ g_assert (ctx->self->priv->modem_dbus_skeleton != NULL);
+ /* Enabling the Modem interface */
+ mm_iface_modem_enable (MM_IFACE_MODEM (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_enable_ready,
+ ctx);
+ return;
+
+ case ENABLING_STEP_IFACE_3GPP:
+ if (ctx->self->priv->modem_3gpp_dbus_skeleton) {
+ mm_dbg ("Modem has 3GPP capabilities, enabling the Modem 3GPP interface...");
+ /* Enabling the Modem 3GPP interface */
+ mm_iface_modem_3gpp_enable (MM_IFACE_MODEM_3GPP (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_3gpp_enable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_3GPP_USSD:
+ if (ctx->self->priv->modem_3gpp_ussd_dbus_skeleton) {
+ mm_dbg ("Modem has 3GPP/USSD capabilities, enabling the Modem 3GPP/USSD interface...");
+ mm_iface_modem_3gpp_ussd_enable (MM_IFACE_MODEM_3GPP_USSD (ctx->self),
+ (GAsyncReadyCallback)iface_modem_3gpp_ussd_enable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_CDMA:
+ if (ctx->self->priv->modem_cdma_dbus_skeleton) {
+ mm_dbg ("Modem has CDMA capabilities, enabling the Modem CDMA interface...");
+ /* Enabling the Modem CDMA interface */
+ mm_iface_modem_cdma_enable (MM_IFACE_MODEM_CDMA (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_cdma_enable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_CONTACTS:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_LOCATION:
+ if (ctx->self->priv->modem_location_dbus_skeleton) {
+ mm_dbg ("Modem has location capabilities, enabling the Location interface...");
+ /* Enabling the Modem Location interface */
+ mm_iface_modem_location_enable (MM_IFACE_MODEM_LOCATION (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_location_enable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_MESSAGING:
+ if (ctx->self->priv->modem_messaging_dbus_skeleton) {
+ mm_dbg ("Modem has messaging capabilities, enabling the Messaging interface...");
+ /* Enabling the Modem Messaging interface */
+ mm_iface_modem_messaging_enable (MM_IFACE_MODEM_MESSAGING (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_messaging_enable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_TIME:
+ if (ctx->self->priv->modem_time_dbus_skeleton) {
+ mm_dbg ("Modem has time capabilities, enabling the Time interface...");
+ /* Enabling the Modem Time interface */
+ mm_iface_modem_time_enable (MM_IFACE_MODEM_TIME (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_time_enable_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_FIRMWARE:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_IFACE_SIMPLE:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case ENABLING_STEP_LAST:
+ ctx->enabled = TRUE;
+ /* All enabled without errors! */
+ g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (ctx->result), TRUE);
+ enabling_context_complete_and_free (ctx);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+enable (MMBaseModem *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, enable);
+
+ /* Check state before launching modem enabling */
+ switch (MM_BROADBAND_MODEM (self)->priv->modem_state) {
+ case MM_MODEM_STATE_UNKNOWN:
+ case MM_MODEM_STATE_FAILED:
+ /* We should never have a UNKNOWN|FAILED->ENABLED transition */
+ g_assert_not_reached ();
+ break;
+
+ case MM_MODEM_STATE_INITIALIZING:
+ g_simple_async_result_set_error (result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot enable modem: "
+ "device not fully initialized yet");
+ break;
+
+ case MM_MODEM_STATE_LOCKED:
+ g_simple_async_result_set_error (result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot enable modem: device locked");
+ break;
+
+ case MM_MODEM_STATE_DISABLED: {
+ EnablingContext *ctx;
+
+ ctx = g_new0 (EnablingContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->result = result;
+ ctx->cancellable = g_object_ref (cancellable);
+ ctx->step = ENABLING_STEP_FIRST;
+ enabling_step (ctx);
+ return;
+ }
+
+ case MM_MODEM_STATE_DISABLING:
+ g_simple_async_result_set_error (result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot enable modem: "
+ "currently being disabled");
+ break;
+
+ case MM_MODEM_STATE_ENABLING:
+ g_simple_async_result_set_error (result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot enable modem: "
+ "already being enabled");
+ break;
+
+ case MM_MODEM_STATE_ENABLED:
+ case MM_MODEM_STATE_SEARCHING:
+ case MM_MODEM_STATE_REGISTERED:
+ case MM_MODEM_STATE_DISCONNECTING:
+ case MM_MODEM_STATE_CONNECTING:
+ case MM_MODEM_STATE_CONNECTED:
+ /* Just return success, don't relaunch enabling */
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ break;
+ }
+
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+
+typedef enum {
+ INITIALIZE_STEP_FIRST,
+ INITIALIZE_STEP_SETUP_PORTS,
+ INITIALIZE_STEP_STARTED,
+ INITIALIZE_STEP_SETUP_SIMPLE_STATUS,
+ INITIALIZE_STEP_IFACE_MODEM,
+ INITIALIZE_STEP_IFACE_3GPP,
+ INITIALIZE_STEP_IFACE_3GPP_USSD,
+ INITIALIZE_STEP_IFACE_CDMA,
+ INITIALIZE_STEP_IFACE_CONTACTS,
+ INITIALIZE_STEP_IFACE_LOCATION,
+ INITIALIZE_STEP_IFACE_MESSAGING,
+ INITIALIZE_STEP_IFACE_TIME,
+ INITIALIZE_STEP_IFACE_FIRMWARE,
+ INITIALIZE_STEP_IFACE_SIMPLE,
+ INITIALIZE_STEP_LAST,
+} InitializeStep;
+
+typedef struct {
+ MMBroadbandModem *self;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *result;
+ InitializeStep step;
+ gpointer ports_ctx;
+} InitializeContext;
+
+static void initialize_step (InitializeContext *ctx);
+
+static void
+initialize_context_complete_and_free (InitializeContext *ctx)
+{
+ GError *error = NULL;
+
+ g_simple_async_result_complete_in_idle (ctx->result);
+
+ if (ctx->ports_ctx &&
+ MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->initialization_stopped &&
+ !MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->initialization_stopped (ctx->self, ctx->ports_ctx, &error)) {
+ mm_warn ("Error when stopping the initialization sequence: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+initialize_context_complete_and_free_if_cancelled (InitializeContext *ctx)
+{
+ if (!g_cancellable_is_cancelled (ctx->cancellable))
+ return FALSE;
+
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Initialization cancelled");
+ initialize_context_complete_and_free (ctx);
+ return TRUE;
+}
+
+static gboolean
+initialize_finish (MMBaseModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+initialization_started_ready (MMBroadbandModem *self,
+ GAsyncResult *result,
+ InitializeContext *ctx)
+{
+ GError *error = NULL;
+ gpointer ports_ctx;
+
+ /* May return NULL without error */
+ ports_ctx = MM_BROADBAND_MODEM_GET_CLASS (self)->initialization_started_finish (self, result, &error);
+ if (error) {
+ mm_warn ("Couldn't start initialization: %s", error->message);
+ g_error_free (error);
+
+ /* There is no Modem interface yet, so just update the variable directly */
+ ctx->self->priv->modem_state = MM_MODEM_STATE_FAILED;
+
+ /* Just jump to the last step */
+ ctx->step = INITIALIZE_STEP_LAST;
+ initialize_step (ctx);
+ return;
+ }
+
+ /* Keep the ctx for later use when stopping initialization */
+ ctx->ports_ctx = ports_ctx;
+
+ /* Go on to next step */
+ ctx->step++;
+ initialize_step (ctx);
+}
+
+static void
+iface_modem_initialize_ready (MMBroadbandModem *self,
+ GAsyncResult *result,
+ InitializeContext *ctx)
+{
+ GError *error = NULL;
+
+ /* If the modem interface fails to get initialized, we will move the modem
+ * to a FAILED state. Note that in this case we still export the interface. */
+ if (!mm_iface_modem_initialize_finish (MM_IFACE_MODEM (self), result, &error)) {
+ MMModemStateFailedReason failed_reason = MM_MODEM_STATE_FAILED_REASON_UNKNOWN;
+
+ /* Report the new FAILED state */
+ mm_warn ("Modem couldn't be initialized: %s", error->message);
+
+ if (g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED))
+ failed_reason = MM_MODEM_STATE_FAILED_REASON_SIM_MISSING;
+ else if (g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE) ||
+ g_error_matches (error,
+ MM_MOBILE_EQUIPMENT_ERROR,
+ MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG))
+ failed_reason = MM_MODEM_STATE_FAILED_REASON_SIM_MISSING;
+
+ g_error_free (error);
+
+ mm_iface_modem_update_failed_state (MM_IFACE_MODEM (self), failed_reason);
+
+ /* Jump to the firmware step. We allow firmware switching even in failed
+ * state */
+ ctx->step = INITIALIZE_STEP_IFACE_FIRMWARE;
+ initialize_step (ctx);
+ return;
+ }
+
+ /* bind simple properties */
+ mm_iface_modem_bind_simple_status (MM_IFACE_MODEM (self),
+ self->priv->modem_simple_status);
+
+ /* If we find ourselves in a LOCKED state, we shouldn't keep on
+ * the initialization sequence. Instead, we will re-initialize once
+ * we are unlocked. */
+ if (ctx->self->priv->modem_state == MM_MODEM_STATE_LOCKED) {
+ /* Jump to the Firmware interface. We do allow modems to export
+ * both the Firmware and Simple interfaces when locked. */
+ ctx->step = INITIALIZE_STEP_IFACE_FIRMWARE;
+ initialize_step (ctx);
+ return;
+ }
+
+ /* Go on to next step */
+ ctx->step++;
+ initialize_step (ctx);
+}
+
+#undef INTERFACE_INIT_READY_FN
+#define INTERFACE_INIT_READY_FN(NAME,TYPE,FATAL_ERRORS) \
+ static void \
+ NAME##_initialize_ready (MMBroadbandModem *self, \
+ GAsyncResult *result, \
+ InitializeContext *ctx) \
+ { \
+ GError *error = NULL; \
+ \
+ if (!mm_##NAME##_initialize_finish (TYPE (self), result, &error)) { \
+ if (FATAL_ERRORS) { \
+ mm_warn ("Couldn't initialize interface: '%s'", \
+ error->message); \
+ g_error_free (error); \
+ \
+ /* Report the new FAILED state */ \
+ mm_iface_modem_update_failed_state (MM_IFACE_MODEM (self), \
+ MM_MODEM_STATE_FAILED_REASON_UNKNOWN); \
+ \
+ /* Just jump to the last step */ \
+ ctx->step = INITIALIZE_STEP_LAST; \
+ initialize_step (ctx); \
+ return; \
+ } \
+ \
+ mm_dbg ("Couldn't initialize interface: '%s'", \
+ error->message); \
+ /* Just shutdown this interface */ \
+ mm_##NAME##_shutdown (TYPE (self)); \
+ g_error_free (error); \
+ } else { \
+ /* bind simple properties */ \
+ mm_##NAME##_bind_simple_status (TYPE (self), self->priv->modem_simple_status); \
+ } \
+ \
+ /* Go on to next step */ \
+ ctx->step++; \
+ initialize_step (ctx); \
+ }
+
+INTERFACE_INIT_READY_FN (iface_modem_3gpp, MM_IFACE_MODEM_3GPP, TRUE)
+INTERFACE_INIT_READY_FN (iface_modem_3gpp_ussd, MM_IFACE_MODEM_3GPP_USSD, FALSE)
+INTERFACE_INIT_READY_FN (iface_modem_cdma, MM_IFACE_MODEM_CDMA, TRUE)
+INTERFACE_INIT_READY_FN (iface_modem_location, MM_IFACE_MODEM_LOCATION, FALSE)
+INTERFACE_INIT_READY_FN (iface_modem_messaging, MM_IFACE_MODEM_MESSAGING, FALSE)
+INTERFACE_INIT_READY_FN (iface_modem_time, MM_IFACE_MODEM_TIME, FALSE)
+INTERFACE_INIT_READY_FN (iface_modem_firmware, MM_IFACE_MODEM_FIRMWARE, FALSE)
+
+static void
+initialize_step (InitializeContext *ctx)
+{
+ /* Don't run new steps if we're cancelled */
+ if (initialize_context_complete_and_free_if_cancelled (ctx))
+ return;
+
+ switch (ctx->step) {
+ case INITIALIZE_STEP_FIRST:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_SETUP_PORTS:
+ if (MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->setup_ports)
+ MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->setup_ports (ctx->self);
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_STARTED:
+ if (MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->initialization_started &&
+ MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->initialization_started_finish) {
+ MM_BROADBAND_MODEM_GET_CLASS (ctx->self)->initialization_started (ctx->self,
+ (GAsyncReadyCallback)initialization_started_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_SETUP_SIMPLE_STATUS:
+ /* Simple status must be created before any interface initialization,
+ * so that interfaces add and bind the properties they want to export.
+ */
+ if (!ctx->self->priv->modem_simple_status)
+ ctx->self->priv->modem_simple_status = mm_simple_status_new ();
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_IFACE_MODEM:
+ /* Initialize the Modem interface */
+ mm_iface_modem_initialize (MM_IFACE_MODEM (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_initialize_ready,
+ ctx);
+ return;
+
+ case INITIALIZE_STEP_IFACE_3GPP:
+ if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self))) {
+ /* Initialize the 3GPP interface */
+ mm_iface_modem_3gpp_initialize (MM_IFACE_MODEM_3GPP (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_3gpp_initialize_ready,
+ ctx);
+ return;
+ }
+
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_IFACE_3GPP_USSD:
+ if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self))) {
+ /* Initialize the 3GPP/USSD interface */
+ mm_iface_modem_3gpp_ussd_initialize (MM_IFACE_MODEM_3GPP_USSD (ctx->self),
+ (GAsyncReadyCallback)iface_modem_3gpp_ussd_initialize_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_IFACE_CDMA:
+ if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->self))) {
+ /* Initialize the CDMA interface */
+ mm_iface_modem_cdma_initialize (MM_IFACE_MODEM_CDMA (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_cdma_initialize_ready,
+ ctx);
+ return;
+ }
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_IFACE_CONTACTS:
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_IFACE_LOCATION:
+ /* Initialize the Location interface */
+ mm_iface_modem_location_initialize (MM_IFACE_MODEM_LOCATION (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_location_initialize_ready,
+ ctx);
+ return;
+
+ case INITIALIZE_STEP_IFACE_MESSAGING:
+ /* Initialize the Messaging interface */
+ mm_iface_modem_messaging_initialize (MM_IFACE_MODEM_MESSAGING (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_messaging_initialize_ready,
+ ctx);
+ return;
+
+ case INITIALIZE_STEP_IFACE_TIME:
+ /* Initialize the Time interface */
+ mm_iface_modem_time_initialize (MM_IFACE_MODEM_TIME (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_time_initialize_ready,
+ ctx);
+ return;
+
+ case INITIALIZE_STEP_IFACE_FIRMWARE:
+ /* Initialize the Firmware interface */
+ mm_iface_modem_firmware_initialize (MM_IFACE_MODEM_FIRMWARE (ctx->self),
+ ctx->cancellable,
+ (GAsyncReadyCallback)iface_modem_firmware_initialize_ready,
+ ctx);
+ return;
+
+ case INITIALIZE_STEP_IFACE_SIMPLE:
+ if (ctx->self->priv->modem_state != MM_MODEM_STATE_FAILED)
+ mm_iface_modem_simple_initialize (MM_IFACE_MODEM_SIMPLE (ctx->self));
+ /* Fall down to next step */
+ ctx->step++;
+
+ case INITIALIZE_STEP_LAST:
+ if (ctx->self->priv->modem_state == MM_MODEM_STATE_FAILED) {
+
+
+ if (!ctx->self->priv->modem_dbus_skeleton) {
+ /* Error setting up ports. Abort without even exporting the
+ * Modem interface */
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_ABORTED,
+ "Modem is unusable, "
+ "cannot fully initialize");
+ } else {
+ /* Fatal SIM failure :-( */
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Modem is unusable, "
+ "cannot fully initialize");
+ /* Ensure we only leave the Modem interface around */
+ mm_iface_modem_3gpp_shutdown (MM_IFACE_MODEM_3GPP (ctx->self));
+ mm_iface_modem_3gpp_ussd_shutdown (MM_IFACE_MODEM_3GPP_USSD (ctx->self));
+ mm_iface_modem_cdma_shutdown (MM_IFACE_MODEM_CDMA (ctx->self));
+ mm_iface_modem_location_shutdown (MM_IFACE_MODEM_LOCATION (ctx->self));
+ mm_iface_modem_messaging_shutdown (MM_IFACE_MODEM_MESSAGING (ctx->self));
+ mm_iface_modem_time_shutdown (MM_IFACE_MODEM_TIME (ctx->self));
+ mm_iface_modem_simple_shutdown (MM_IFACE_MODEM_SIMPLE (ctx->self));
+ }
+ initialize_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (ctx->self->priv->modem_state == MM_MODEM_STATE_LOCKED) {
+ /* We're locked :-/ */
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Modem is currently locked, "
+ "cannot fully initialize");
+ initialize_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* All initialized without errors!
+ * Set as disabled (a.k.a. initialized) */
+ mm_iface_modem_update_state (MM_IFACE_MODEM (ctx->self),
+ MM_MODEM_STATE_DISABLED,
+ MM_MODEM_STATE_CHANGE_REASON_UNKNOWN);
+
+ g_simple_async_result_set_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (ctx->result), TRUE);
+ initialize_context_complete_and_free (ctx);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+initialize (MMBaseModem *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, initialize);
+
+ /* Check state before launching modem initialization */
+ switch (MM_BROADBAND_MODEM (self)->priv->modem_state) {
+ case MM_MODEM_STATE_FAILED:
+ /* NOTE: this will only happen if we ever support hot-plugging SIMs */
+ g_simple_async_result_set_error (result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot initialize modem: "
+ "device is unusable");
+ break;
+
+ case MM_MODEM_STATE_UNKNOWN:
+ case MM_MODEM_STATE_LOCKED: {
+ InitializeContext *ctx;
+
+ ctx = g_new0 (InitializeContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->cancellable = g_object_ref (cancellable);
+ ctx->result = result;
+ ctx->step = INITIALIZE_STEP_FIRST;
+
+ /* Set as being initialized, even if we were locked before */
+ mm_iface_modem_update_state (MM_IFACE_MODEM (self),
+ MM_MODEM_STATE_INITIALIZING,
+ MM_MODEM_STATE_CHANGE_REASON_UNKNOWN);
+
+ initialize_step (ctx);
+ return;
+ }
+
+ case MM_MODEM_STATE_INITIALIZING:
+ g_simple_async_result_set_error (result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_WRONG_STATE,
+ "Cannot initialize modem: "
+ "already being initialized");
+ break;
+
+ case MM_MODEM_STATE_DISABLED:
+ case MM_MODEM_STATE_DISABLING:
+ case MM_MODEM_STATE_ENABLING:
+ case MM_MODEM_STATE_ENABLED:
+ case MM_MODEM_STATE_SEARCHING:
+ case MM_MODEM_STATE_REGISTERED:
+ case MM_MODEM_STATE_DISCONNECTING:
+ case MM_MODEM_STATE_CONNECTING:
+ case MM_MODEM_STATE_CONNECTED:
+ /* Just return success, don't relaunch initialization */
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ break;
+ }
+
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+}
+
+/*****************************************************************************/
+
+gchar *
+mm_broadband_modem_take_and_convert_to_utf8 (MMBroadbandModem *self,
+ gchar *str)
+{
+ /* should only be used AFTER current charset is set */
+ if (self->priv->modem_current_charset == MM_MODEM_CHARSET_UNKNOWN)
+ return str;
+
+ return mm_charset_take_and_convert_to_utf8 (str,
+ self->priv->modem_current_charset);
+}
+
+gchar *
+mm_broadband_modem_take_and_convert_to_current_charset (MMBroadbandModem *self,
+ gchar *str)
+{
+ /* should only be used AFTER current charset is set */
+ if (self->priv->modem_current_charset == MM_MODEM_CHARSET_UNKNOWN)
+ return str;
+
+ return mm_utf8_take_and_convert_to_charset (str, self->priv->modem_current_charset);
+}
+
+gchar *
+mm_broadband_modem_create_device_identifier (MMBroadbandModem *self,
+ const gchar *ati,
+ const gchar *ati1)
+{
+ return (mm_create_device_identifier (
+ mm_base_modem_get_vendor_id (MM_BASE_MODEM (self)),
+ mm_base_modem_get_product_id (MM_BASE_MODEM (self)),
+ ati,
+ ati1,
+ mm_gdbus_modem_get_equipment_identifier (
+ MM_GDBUS_MODEM (MM_BROADBAND_MODEM (self)->priv->modem_dbus_skeleton)),
+ mm_gdbus_modem_get_revision (
+ MM_GDBUS_MODEM (MM_BROADBAND_MODEM (self)->priv->modem_dbus_skeleton)),
+ mm_gdbus_modem_get_model (
+ MM_GDBUS_MODEM (MM_BROADBAND_MODEM (self)->priv->modem_dbus_skeleton)),
+ mm_gdbus_modem_get_manufacturer (
+ MM_GDBUS_MODEM (MM_BROADBAND_MODEM (self)->priv->modem_dbus_skeleton))));
+}
+
+/*****************************************************************************/
+
+MMBroadbandModem *
+mm_broadband_modem_new (const gchar *device,
+ const gchar **drivers,
+ const gchar *plugin,
+ guint16 vendor_id,
+ guint16 product_id)
+{
+ return g_object_new (MM_TYPE_BROADBAND_MODEM,
+ MM_BASE_MODEM_DEVICE, device,
+ MM_BASE_MODEM_DRIVERS, drivers,
+ MM_BASE_MODEM_PLUGIN, plugin,
+ MM_BASE_MODEM_VENDOR_ID, vendor_id,
+ MM_BASE_MODEM_PRODUCT_ID, product_id,
+ NULL);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (object);
+
+ switch (prop_id) {
+ case PROP_MODEM_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_dbus_skeleton);
+ self->priv->modem_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_3GPP_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_3gpp_dbus_skeleton);
+ self->priv->modem_3gpp_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_3GPP_USSD_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_3gpp_ussd_dbus_skeleton);
+ self->priv->modem_3gpp_ussd_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_CDMA_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_cdma_dbus_skeleton);
+ self->priv->modem_cdma_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_SIMPLE_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_simple_dbus_skeleton);
+ self->priv->modem_simple_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_LOCATION_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_location_dbus_skeleton);
+ self->priv->modem_location_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_MESSAGING_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_messaging_dbus_skeleton);
+ self->priv->modem_messaging_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_TIME_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_time_dbus_skeleton);
+ self->priv->modem_time_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_FIRMWARE_DBUS_SKELETON:
+ g_clear_object (&self->priv->modem_firmware_dbus_skeleton);
+ self->priv->modem_firmware_dbus_skeleton = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_SIM:
+ g_clear_object (&self->priv->modem_sim);
+ self->priv->modem_sim = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_BEARER_LIST:
+ g_clear_object (&self->priv->modem_bearer_list);
+ self->priv->modem_bearer_list = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_STATE:
+ self->priv->modem_state = g_value_get_enum (value);
+ break;
+ case PROP_MODEM_3GPP_REGISTRATION_STATE:
+ self->priv->modem_3gpp_registration_state = g_value_get_enum (value);
+ break;
+ case PROP_MODEM_3GPP_CS_NETWORK_SUPPORTED:
+ self->priv->modem_3gpp_cs_network_supported = g_value_get_boolean (value);
+ break;
+ case PROP_MODEM_3GPP_PS_NETWORK_SUPPORTED:
+ self->priv->modem_3gpp_ps_network_supported = g_value_get_boolean (value);
+ break;
+ case PROP_MODEM_3GPP_EPS_NETWORK_SUPPORTED:
+ self->priv->modem_3gpp_eps_network_supported = g_value_get_boolean (value);
+ break;
+ case PROP_MODEM_3GPP_IGNORED_FACILITY_LOCKS:
+ self->priv->modem_3gpp_ignored_facility_locks = g_value_get_flags (value);
+ break;
+ case PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE:
+ self->priv->modem_cdma_cdma1x_registration_state = g_value_get_enum (value);
+ break;
+ case PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE:
+ self->priv->modem_cdma_evdo_registration_state = g_value_get_enum (value);
+ break;
+ case PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED:
+ self->priv->modem_cdma_cdma1x_network_supported = g_value_get_boolean (value);
+ break;
+ case PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED:
+ self->priv->modem_cdma_evdo_network_supported = g_value_get_boolean (value);
+ break;
+ case PROP_MODEM_MESSAGING_SMS_LIST:
+ g_clear_object (&self->priv->modem_messaging_sms_list);
+ self->priv->modem_messaging_sms_list = g_value_dup_object (value);
+ break;
+ case PROP_MODEM_MESSAGING_SMS_PDU_MODE:
+ self->priv->modem_messaging_sms_pdu_mode = g_value_get_boolean (value);
+ break;
+ case PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE:
+ self->priv->modem_messaging_sms_default_storage = g_value_get_enum (value);
+ break;
+ case PROP_MODEM_SIMPLE_STATUS:
+ g_clear_object (&self->priv->modem_simple_status);
+ self->priv->modem_simple_status = g_value_dup_object (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)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (object);
+
+ switch (prop_id) {
+ case PROP_MODEM_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_dbus_skeleton);
+ break;
+ case PROP_MODEM_3GPP_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_3gpp_dbus_skeleton);
+ break;
+ case PROP_MODEM_3GPP_USSD_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_3gpp_ussd_dbus_skeleton);
+ break;
+ case PROP_MODEM_CDMA_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_cdma_dbus_skeleton);
+ break;
+ case PROP_MODEM_SIMPLE_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_simple_dbus_skeleton);
+ break;
+ case PROP_MODEM_LOCATION_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_location_dbus_skeleton);
+ break;
+ case PROP_MODEM_MESSAGING_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_messaging_dbus_skeleton);
+ break;
+ case PROP_MODEM_TIME_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_time_dbus_skeleton);
+ break;
+ case PROP_MODEM_FIRMWARE_DBUS_SKELETON:
+ g_value_set_object (value, self->priv->modem_firmware_dbus_skeleton);
+ break;
+ case PROP_MODEM_SIM:
+ g_value_set_object (value, self->priv->modem_sim);
+ break;
+ case PROP_MODEM_BEARER_LIST:
+ g_value_set_object (value, self->priv->modem_bearer_list);
+ break;
+ case PROP_MODEM_STATE:
+ g_value_set_enum (value, self->priv->modem_state);
+ break;
+ case PROP_MODEM_3GPP_REGISTRATION_STATE:
+ g_value_set_enum (value, self->priv->modem_3gpp_registration_state);
+ break;
+ case PROP_MODEM_3GPP_CS_NETWORK_SUPPORTED:
+ g_value_set_boolean (value, self->priv->modem_3gpp_cs_network_supported);
+ break;
+ case PROP_MODEM_3GPP_PS_NETWORK_SUPPORTED:
+ g_value_set_boolean (value, self->priv->modem_3gpp_ps_network_supported);
+ break;
+ case PROP_MODEM_3GPP_EPS_NETWORK_SUPPORTED:
+ g_value_set_boolean (value, self->priv->modem_3gpp_eps_network_supported);
+ break;
+ case PROP_MODEM_3GPP_IGNORED_FACILITY_LOCKS:
+ g_value_set_flags (value, self->priv->modem_3gpp_ignored_facility_locks);
+ break;
+ case PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE:
+ g_value_set_enum (value, self->priv->modem_cdma_cdma1x_registration_state);
+ break;
+ case PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE:
+ g_value_set_enum (value, self->priv->modem_cdma_evdo_registration_state);
+ break;
+ case PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED:
+ g_value_set_boolean (value, self->priv->modem_cdma_cdma1x_network_supported);
+ break;
+ case PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED:
+ g_value_set_boolean (value, self->priv->modem_cdma_evdo_network_supported);
+ break;
+ case PROP_MODEM_MESSAGING_SMS_LIST:
+ g_value_set_object (value, self->priv->modem_messaging_sms_list);
+ break;
+ case PROP_MODEM_MESSAGING_SMS_PDU_MODE:
+ g_value_set_boolean (value, self->priv->modem_messaging_sms_pdu_mode);
+ break;
+ case PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE:
+ g_value_set_enum (value, self->priv->modem_messaging_sms_default_storage);
+ break;
+ case PROP_MODEM_SIMPLE_STATUS:
+ g_value_set_object (value, self->priv->modem_simple_status);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_broadband_modem_init (MMBroadbandModem *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BROADBAND_MODEM,
+ MMBroadbandModemPrivate);
+ self->priv->modem_state = MM_MODEM_STATE_UNKNOWN;
+ self->priv->modem_3gpp_registration_regex = mm_3gpp_creg_regex_get (TRUE);
+ self->priv->modem_current_charset = MM_MODEM_CHARSET_UNKNOWN;
+ self->priv->modem_3gpp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
+ self->priv->modem_3gpp_cs_network_supported = TRUE;
+ self->priv->modem_3gpp_ps_network_supported = TRUE;
+ self->priv->modem_3gpp_eps_network_supported = FALSE;
+ self->priv->modem_3gpp_ignored_facility_locks = MM_MODEM_3GPP_FACILITY_NONE;
+ self->priv->modem_cdma_cdma1x_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ self->priv->modem_cdma_evdo_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ self->priv->modem_cdma_cdma1x_network_supported = TRUE;
+ self->priv->modem_cdma_evdo_network_supported = TRUE;
+ self->priv->modem_messaging_sms_default_storage = MM_SMS_STORAGE_UNKNOWN;
+ self->priv->current_sms_mem1_storage = MM_SMS_STORAGE_UNKNOWN;
+ self->priv->current_sms_mem2_storage = MM_SMS_STORAGE_UNKNOWN;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (object);
+
+ if (self->priv->enabled_ports_ctx)
+ ports_context_unref (self->priv->enabled_ports_ctx);
+
+ if (self->priv->modem_3gpp_registration_regex)
+ mm_3gpp_creg_regex_destroy (self->priv->modem_3gpp_registration_regex);
+
+ G_OBJECT_CLASS (mm_broadband_modem_parent_class)->finalize (object);
+}
+
+static void
+dispose (GObject *object)
+{
+ MMBroadbandModem *self = MM_BROADBAND_MODEM (object);
+
+ if (self->priv->modem_dbus_skeleton) {
+ mm_iface_modem_shutdown (MM_IFACE_MODEM (object));
+ g_clear_object (&self->priv->modem_dbus_skeleton);
+ }
+
+ if (self->priv->modem_3gpp_dbus_skeleton) {
+ mm_iface_modem_3gpp_shutdown (MM_IFACE_MODEM_3GPP (object));
+ g_clear_object (&self->priv->modem_3gpp_dbus_skeleton);
+ }
+
+ if (self->priv->modem_3gpp_ussd_dbus_skeleton) {
+ mm_iface_modem_3gpp_ussd_shutdown (MM_IFACE_MODEM_3GPP_USSD (object));
+ g_clear_object (&self->priv->modem_3gpp_ussd_dbus_skeleton);
+ }
+
+ if (self->priv->modem_cdma_dbus_skeleton) {
+ mm_iface_modem_cdma_shutdown (MM_IFACE_MODEM_CDMA (object));
+ g_clear_object (&self->priv->modem_cdma_dbus_skeleton);
+ }
+
+ if (self->priv->modem_location_dbus_skeleton) {
+ mm_iface_modem_location_shutdown (MM_IFACE_MODEM_LOCATION (object));
+ g_clear_object (&self->priv->modem_location_dbus_skeleton);
+ }
+
+ if (self->priv->modem_messaging_dbus_skeleton) {
+ mm_iface_modem_messaging_shutdown (MM_IFACE_MODEM_MESSAGING (object));
+ g_clear_object (&self->priv->modem_messaging_dbus_skeleton);
+ }
+
+ if (self->priv->modem_time_dbus_skeleton) {
+ mm_iface_modem_time_shutdown (MM_IFACE_MODEM_TIME (object));
+ g_clear_object (&self->priv->modem_time_dbus_skeleton);
+ }
+
+ if (self->priv->modem_simple_dbus_skeleton) {
+ mm_iface_modem_simple_shutdown (MM_IFACE_MODEM_SIMPLE (object));
+ g_clear_object (&self->priv->modem_simple_dbus_skeleton);
+ }
+
+ g_clear_object (&self->priv->modem_sim);
+ g_clear_object (&self->priv->modem_bearer_list);
+ g_clear_object (&self->priv->modem_messaging_sms_list);
+ g_clear_object (&self->priv->modem_simple_status);
+
+ G_OBJECT_CLASS (mm_broadband_modem_parent_class)->dispose (object);
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+ /* Initialization steps */
+ iface->load_current_capabilities = modem_load_current_capabilities;
+ iface->load_current_capabilities_finish = modem_load_current_capabilities_finish;
+ iface->load_manufacturer = modem_load_manufacturer;
+ iface->load_manufacturer_finish = modem_load_manufacturer_finish;
+ iface->load_model = modem_load_model;
+ iface->load_model_finish = modem_load_model_finish;
+ iface->load_revision = modem_load_revision;
+ iface->load_revision_finish = modem_load_revision_finish;
+ iface->load_equipment_identifier = modem_load_equipment_identifier;
+ iface->load_equipment_identifier_finish = modem_load_equipment_identifier_finish;
+ iface->load_device_identifier = modem_load_device_identifier;
+ iface->load_device_identifier_finish = modem_load_device_identifier_finish;
+ iface->load_own_numbers = modem_load_own_numbers;
+ iface->load_own_numbers_finish = modem_load_own_numbers_finish;
+ iface->load_unlock_required = modem_load_unlock_required;
+ iface->load_unlock_required_finish = modem_load_unlock_required_finish;
+ iface->create_sim = modem_create_sim;
+ iface->create_sim_finish = modem_create_sim_finish;
+ iface->load_supported_modes = modem_load_supported_modes;
+ iface->load_supported_modes_finish = modem_load_supported_modes_finish;
+ iface->load_power_state = load_power_state;
+ iface->load_power_state_finish = load_power_state_finish;
+ iface->load_supported_ip_families = modem_load_supported_ip_families;
+ iface->load_supported_ip_families_finish = modem_load_supported_ip_families_finish;
+
+ /* Enabling steps */
+ iface->modem_power_up = modem_power_up;
+ iface->modem_power_up_finish = modem_power_up_finish;
+ iface->setup_flow_control = modem_setup_flow_control;
+ iface->setup_flow_control_finish = modem_setup_flow_control_finish;
+ iface->load_supported_charsets = modem_load_supported_charsets;
+ iface->load_supported_charsets_finish = modem_load_supported_charsets_finish;
+ iface->setup_charset = modem_setup_charset;
+ iface->setup_charset_finish = modem_setup_charset_finish;
+
+ /* Additional actions */
+ iface->load_signal_quality = modem_load_signal_quality;
+ iface->load_signal_quality_finish = modem_load_signal_quality_finish;
+ iface->create_bearer = modem_create_bearer;
+ iface->create_bearer_finish = modem_create_bearer_finish;
+ iface->command = modem_command;
+ iface->command_finish = modem_command_finish;
+
+ iface->load_access_technologies = modem_load_access_technologies;
+ iface->load_access_technologies_finish = modem_load_access_technologies_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+ /* Initialization steps */
+ iface->load_imei = modem_3gpp_load_imei;
+ iface->load_imei_finish = modem_3gpp_load_imei_finish;
+ iface->load_enabled_facility_locks = modem_3gpp_load_enabled_facility_locks;
+ iface->load_enabled_facility_locks_finish = modem_3gpp_load_enabled_facility_locks_finish;
+
+ /* Enabling steps */
+ iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
+ iface->setup_unsolicited_registration_events = modem_3gpp_setup_unsolicited_registration_events;
+ iface->setup_unsolicited_registration_events_finish = modem_3gpp_setup_unsolicited_registration_events_finish;
+ iface->enable_unsolicited_registration_events = modem_3gpp_enable_unsolicited_registration_events;
+ iface->enable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish;
+
+ /* Disabling steps */
+ iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
+ iface->disable_unsolicited_registration_events = modem_3gpp_disable_unsolicited_registration_events;
+ iface->disable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish;
+ iface->cleanup_unsolicited_registration_events = modem_3gpp_cleanup_unsolicited_registration_events;
+ iface->cleanup_unsolicited_registration_events_finish = modem_3gpp_cleanup_unsolicited_registration_events_finish;
+
+ /* Additional actions */
+ iface->load_operator_code = modem_3gpp_load_operator_code;
+ iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish;
+ iface->load_operator_name = modem_3gpp_load_operator_name;
+ iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish;
+ iface->run_registration_checks = modem_3gpp_run_registration_checks;
+ iface->run_registration_checks_finish = modem_3gpp_run_registration_checks_finish;
+ iface->register_in_network = modem_3gpp_register_in_network;
+ iface->register_in_network_finish = modem_3gpp_register_in_network_finish;
+ iface->scan_networks = modem_3gpp_scan_networks;
+ iface->scan_networks_finish = modem_3gpp_scan_networks_finish;
+}
+
+static void
+iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface)
+{
+ /* Initialization steps */
+ iface->check_support = modem_3gpp_ussd_check_support;
+ iface->check_support_finish = modem_3gpp_ussd_check_support_finish;
+
+ /* Enabling steps */
+ iface->setup_unsolicited_result_codes = modem_3gpp_ussd_setup_unsolicited_result_codes;
+ iface->setup_unsolicited_result_codes_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_result_codes_finish;
+ iface->enable_unsolicited_result_codes = modem_3gpp_ussd_enable_unsolicited_result_codes;
+ iface->enable_unsolicited_result_codes_finish = modem_3gpp_ussd_enable_disable_unsolicited_result_codes_finish;
+
+ /* Disabling steps */
+ iface->cleanup_unsolicited_result_codes_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_result_codes_finish;
+ iface->cleanup_unsolicited_result_codes = modem_3gpp_ussd_cleanup_unsolicited_result_codes;
+ iface->disable_unsolicited_result_codes = modem_3gpp_ussd_disable_unsolicited_result_codes;
+ iface->disable_unsolicited_result_codes_finish = modem_3gpp_ussd_enable_disable_unsolicited_result_codes_finish;
+
+ /* Additional actions */
+ iface->encode = modem_3gpp_ussd_encode;
+ iface->decode = modem_3gpp_ussd_decode;
+ iface->send = modem_3gpp_ussd_send;
+ iface->send_finish = modem_3gpp_ussd_send_finish;
+ iface->cancel = modem_3gpp_ussd_cancel;
+ iface->cancel_finish = modem_3gpp_ussd_cancel_finish;
+}
+
+static void
+iface_modem_cdma_init (MMIfaceModemCdma *iface)
+{
+ /* Initialization steps */
+ iface->load_esn = modem_cdma_load_esn;
+ iface->load_esn_finish = modem_cdma_load_esn_finish;
+ iface->load_meid = modem_cdma_load_meid;
+ iface->load_meid_finish = modem_cdma_load_meid_finish;
+
+ /* Registration check steps */
+ iface->setup_registration_checks = modem_cdma_setup_registration_checks;
+ iface->setup_registration_checks_finish = modem_cdma_setup_registration_checks_finish;
+ iface->get_call_manager_state = modem_cdma_get_call_manager_state;
+ iface->get_call_manager_state_finish = modem_cdma_get_call_manager_state_finish;
+ iface->get_hdr_state = modem_cdma_get_hdr_state;
+ iface->get_hdr_state_finish = modem_cdma_get_hdr_state_finish;
+ iface->get_service_status = modem_cdma_get_service_status;
+ iface->get_service_status_finish = modem_cdma_get_service_status_finish;
+ iface->get_cdma1x_serving_system = modem_cdma_get_cdma1x_serving_system;
+ iface->get_cdma1x_serving_system_finish = modem_cdma_get_cdma1x_serving_system_finish;
+ iface->get_detailed_registration_state = modem_cdma_get_detailed_registration_state;
+ iface->get_detailed_registration_state_finish = modem_cdma_get_detailed_registration_state_finish;
+
+ /* Additional actions */
+ iface->register_in_network = modem_cdma_register_in_network;
+ iface->register_in_network_finish = modem_cdma_register_in_network_finish;
+}
+
+static void
+iface_modem_simple_init (MMIfaceModemSimple *iface)
+{
+}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface->load_capabilities = modem_location_load_capabilities;
+ iface->load_capabilities_finish = modem_location_load_capabilities_finish;
+ iface->enable_location_gathering = enable_location_gathering;
+ iface->enable_location_gathering_finish = enable_location_gathering_finish;
+}
+
+static void
+iface_modem_messaging_init (MMIfaceModemMessaging *iface)
+{
+ iface->check_support = modem_messaging_check_support;
+ iface->check_support_finish = modem_messaging_check_support_finish;
+ iface->load_supported_storages = modem_messaging_load_supported_storages;
+ iface->load_supported_storages_finish = modem_messaging_load_supported_storages_finish;
+ iface->set_default_storage = modem_messaging_set_default_storage;
+ iface->set_default_storage_finish = modem_messaging_set_default_storage_finish;
+ iface->setup_sms_format = modem_messaging_setup_sms_format;
+ iface->setup_sms_format_finish = modem_messaging_setup_sms_format_finish;
+ iface->load_initial_sms_parts = modem_messaging_load_initial_sms_parts;
+ iface->load_initial_sms_parts_finish = modem_messaging_load_initial_sms_parts_finish;
+ iface->setup_unsolicited_events = modem_messaging_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_messaging_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_messaging_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_messaging_enable_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_messaging_cleanup_unsolicited_events;
+ iface->cleanup_unsolicited_events_finish = modem_messaging_setup_cleanup_unsolicited_events_finish;
+ iface->create_sms = modem_messaging_create_sms;
+}
+
+static void
+iface_modem_time_init (MMIfaceModemTime *iface)
+{
+}
+
+static void
+iface_modem_firmware_init (MMIfaceModemFirmware *iface)
+{
+}
+
+static void
+mm_broadband_modem_class_init (MMBroadbandModemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ MMBaseModemClass *base_modem_class = MM_BASE_MODEM_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBroadbandModemPrivate));
+
+ /* Virtual methods */
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+ object_class->finalize = finalize;
+
+ base_modem_class->initialize = initialize;
+ base_modem_class->initialize_finish = initialize_finish;
+ base_modem_class->enable = enable;
+ base_modem_class->enable_finish = enable_finish;
+ base_modem_class->disable = disable;
+ base_modem_class->disable_finish = disable_finish;
+
+ klass->setup_ports = setup_ports;
+ klass->initialization_started = initialization_started;
+ klass->initialization_started_finish = initialization_started_finish;
+ klass->initialization_stopped = initialization_stopped;
+ klass->enabling_started = enabling_started;
+ klass->enabling_started_finish = enabling_started_finish;
+ klass->enabling_modem_init = enabling_modem_init;
+ klass->enabling_modem_init_finish = enabling_modem_init_finish;
+ klass->disabling_stopped = disabling_stopped;
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_DBUS_SKELETON,
+ MM_IFACE_MODEM_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_3GPP_DBUS_SKELETON,
+ MM_IFACE_MODEM_3GPP_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_3GPP_USSD_DBUS_SKELETON,
+ MM_IFACE_MODEM_3GPP_USSD_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_CDMA_DBUS_SKELETON,
+ MM_IFACE_MODEM_CDMA_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_SIMPLE_DBUS_SKELETON,
+ MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_LOCATION_DBUS_SKELETON,
+ MM_IFACE_MODEM_LOCATION_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_MESSAGING_DBUS_SKELETON,
+ MM_IFACE_MODEM_MESSAGING_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_TIME_DBUS_SKELETON,
+ MM_IFACE_MODEM_TIME_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_FIRMWARE_DBUS_SKELETON,
+ MM_IFACE_MODEM_FIRMWARE_DBUS_SKELETON);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_SIM,
+ MM_IFACE_MODEM_SIM);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_BEARER_LIST,
+ MM_IFACE_MODEM_BEARER_LIST);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_STATE,
+ MM_IFACE_MODEM_STATE);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_3GPP_REGISTRATION_STATE,
+ MM_IFACE_MODEM_3GPP_REGISTRATION_STATE);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_3GPP_CS_NETWORK_SUPPORTED,
+ MM_IFACE_MODEM_3GPP_CS_NETWORK_SUPPORTED);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_3GPP_PS_NETWORK_SUPPORTED,
+ MM_IFACE_MODEM_3GPP_PS_NETWORK_SUPPORTED);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_3GPP_EPS_NETWORK_SUPPORTED,
+ MM_IFACE_MODEM_3GPP_EPS_NETWORK_SUPPORTED);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_3GPP_IGNORED_FACILITY_LOCKS,
+ MM_IFACE_MODEM_3GPP_IGNORED_FACILITY_LOCKS);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE,
+ MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE,
+ MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED,
+ MM_IFACE_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED,
+ MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_MESSAGING_SMS_LIST,
+ MM_IFACE_MODEM_MESSAGING_SMS_LIST);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_MESSAGING_SMS_PDU_MODE,
+ MM_IFACE_MODEM_MESSAGING_SMS_PDU_MODE);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE,
+ MM_IFACE_MODEM_MESSAGING_SMS_DEFAULT_STORAGE);
+
+ g_object_class_override_property (object_class,
+ PROP_MODEM_SIMPLE_STATUS,
+ MM_IFACE_MODEM_SIMPLE_STATUS);
+}