blob: 3d7f09f4846c6d3aae3e919b13df21344224fa90 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2012 Red Hat, Inc.
* Copyright (C) 2011 - 2012 Google, Inc.
* Copyright (C) 2015 - Marco Bascetta <marco.bascetta@sadel.it>
*/
#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-voice.h"
#include "mm-iface-modem-time.h"
#include "mm-iface-modem-firmware.h"
#include "mm-iface-modem-signal.h"
#include "mm-iface-modem-oma.h"
#include "mm-broadband-bearer.h"
#include "mm-bearer-list.h"
#include "mm-sms-list.h"
#include "mm-sms-part-3gpp.h"
#include "mm-call-list.h"
#include "mm-base-sim.h"
#include "mm-log.h"
#include "mm-modem-helpers.h"
#include "mm-error-helpers.h"
#include "mm-port-serial-qcdm.h"
#include "libqcdm/src/errors.h"
#include "libqcdm/src/commands.h"
#include "libqcdm/src/logs.h"
#include "libqcdm/src/log-items.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_voice_init (MMIfaceModemVoice *iface);
static void iface_modem_time_init (MMIfaceModemTime *iface);
static void iface_modem_signal_init (MMIfaceModemSignal *iface);
static void iface_modem_oma_init (MMIfaceModemOma *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_VOICE, iface_modem_voice_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_OMA, iface_modem_oma_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_VOICE_DBUS_SKELETON,
PROP_MODEM_TIME_DBUS_SKELETON,
PROP_MODEM_SIGNAL_DBUS_SKELETON,
PROP_MODEM_OMA_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_VOICE_CALL_LIST,
PROP_MODEM_SIMPLE_STATUS,
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
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;
PortsContext *sim_hot_swap_ports_ctx;
gboolean modem_init_run;
gboolean sim_hot_swap_supported;
/*<--- Modem interface --->*/
/* Properties */
GObject *modem_dbus_skeleton;
MMBaseSim *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;
gint evdo_pilot_rssi;
/*<--- 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 Voice interface --->*/
/* Properties */
GObject *modem_voice_dbus_skeleton;
MMCallList *modem_voice_call_list;
/*<--- Modem Time interface --->*/
/* Properties */
GObject *modem_time_dbus_skeleton;
/*<--- Modem Signal interface --->*/
/* Properties */
GObject *modem_signal_dbus_skeleton;
/*<--- Modem OMA interface --->*/
/* Properties */
GObject *modem_oma_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 MMBaseBearer *
modem_create_bearer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
MMBaseBearer *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_base_bearer_get_path (bearer));
return g_object_ref (bearer);
}
static void
broadband_bearer_new_ready (GObject *source,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
MMBaseBearer *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 interface) */
static MMBaseSim *
modem_create_sim_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return mm_base_sim_new_finish (res, error);
}
static void
modem_create_sim (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* New generic SIM */
mm_base_sim_new (MM_BASE_MODEM (self),
NULL, /* cancellable */
callback,
user_data);
}
/*****************************************************************************/
/* Capabilities loading (Modem interface) */
typedef struct {
MMBroadbandModem *self;
GSimpleAsyncResult *result;
MMModemCapability caps;
MMPortSerialQcdm *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_port_serial_close (MM_PORT_SERIAL (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 (MMPortSerialQcdm *port,
GAsyncResult *res,
LoadCapabilitiesContext *ctx)
{
QcdmResult *result;
gint err = QCDM_SUCCESS;
uint8_t pref = 0;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
/* Fall back to AT checking */
mm_dbg ("Failed to load NV ModePref: %s", error->message);
g_error_free (error);
goto at_caps;
}
/* Parse the response */
result = qcdm_cmd_nv_get_mode_pref_result ((const gchar *)response->data,
response->len,
&err);
g_byte_array_unref (response);
if (!result) {
mm_dbg ("Failed to parse NV ModePref result: %d", err);
g_byte_array_unref (response);
goto at_caps;
}
err = qcdm_result_get_u8 (result, QCDM_CMD_NV_GET_MODE_PREF_ITEM_MODE_PREF, &pref);
qcdm_result_unref (result);
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_port_serial_open (MM_PORT_SERIAL (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_port_serial_qcdm_command (ctx->qcdm_port,
cmd,
3,
NULL,
(GAsyncReadyCallback)mode_pref_qcdm_ready,
ctx);
g_byte_array_unref (cmd);
}
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;
MMPortSerialQcdm *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_port_serial_close (MM_PORT_SERIAL (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 (MMPortSerialQcdm *port,
GAsyncResult *res,
OwnNumbersContext *ctx)
{
QcdmResult *result;
gint err = QCDM_SUCCESS;
const char *numbers[2] = { NULL, NULL };
GByteArray *response;
GError *error = NULL;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_simple_async_result_take_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);
g_byte_array_unref (response);
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_port_serial_qcdm_command (ctx->qcdm,
mdn,
3,
NULL,
(GAsyncReadyCallback)mdn_qcdm_ready,
ctx);
g_byte_array_unref (mdn);
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_port_serial_open (MM_PORT_SERIAL (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) */
static void
qcdm_evdo_pilot_sets_log_handle (MMPortSerialQcdm *port,
GByteArray *log_buffer,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (user_data);
QcdmResult *result;
uint32_t num_active = 0;
uint32_t pilot_pn = 0;
uint32_t pilot_energy = 0;
int32_t rssi_dbm = 0;
result = qcdm_log_item_evdo_pilot_sets_v2_new ((const char *) log_buffer->data,
log_buffer->len,
NULL);
if (!result)
return;
if (!qcdm_log_item_evdo_pilot_sets_v2_get_num (result,
QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE,
&num_active)) {
qcdm_result_unref (result);
return;
}
if (num_active > 0 &&
qcdm_log_item_evdo_pilot_sets_v2_get_pilot (result,
QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE,
0,
&pilot_pn,
&pilot_energy,
&rssi_dbm)) {
mm_dbg ("EVDO active pilot RSSI: %ddBm", rssi_dbm);
self->priv->evdo_pilot_rssi = rssi_dbm;
}
qcdm_result_unref (result);
}
typedef struct {
MMBroadbandModem *self;
GSimpleAsyncResult *result;
MMPortSerial *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 guint
signal_quality_evdo_pilot_sets (MMBroadbandModem *self)
{
gint dbm;
if (self->priv->modem_cdma_evdo_registration_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
return 0;
if (self->priv->evdo_pilot_rssi >= 0)
return 0;
dbm = CLAMP (self->priv->evdo_pilot_rssi, -113, -51);
return 100 - ((dbm + 51) * 100 / (-113 + 51));
}
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)) {
if (quality == 99) {
/* 99 can mean unknown, no service, etc. But the modem may
* also only report CDMA 1x quality in CSQ, so try EVDO via
* QCDM log messages too.
*/
quality = signal_quality_evdo_pilot_sets (self);
} 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_PORT_SERIAL_AT (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_PORT_SERIAL_AT (ctx->port),
"+CIND?",
3,
FALSE,
FALSE, /* raw */
NULL, /* cancellable */
(GAsyncReadyCallback)signal_quality_cind_ready,
ctx);
}
static void
signal_quality_qcdm_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
SignalQualityContext *ctx)
{
QcdmResult *result;
guint32 num = 0, quality = 0, i;
gfloat best_db = -28;
gint err = QCDM_SUCCESS;
GByteArray *response;
GError *error = NULL;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_simple_async_result_take_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);
g_byte_array_unref (response);
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;
guint quality;
/* If EVDO is active try that signal strength first */
quality = signal_quality_evdo_pilot_sets (ctx->self);
if (quality > 0) {
g_simple_async_result_set_op_res_gpointer (ctx->result,
GUINT_TO_POINTER (quality),
NULL);
signal_quality_context_complete_and_free (ctx);
return;
}
/* 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_port_serial_qcdm_command (MM_PORT_SERIAL_QCDM (ctx->port),
pilot_sets,
3,
NULL,
(GAsyncReadyCallback)signal_quality_qcdm_ready,
ctx);
g_byte_array_unref (pilot_sets);
}
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 = (MMPortSerial *)mm_base_modem_get_best_at_port (MM_BASE_MODEM (self), &error);
if (ctx->port) {
if (MM_BROADBAND_MODEM (self)->priv->modem_cind_supported &&
CIND_INDICATOR_IS_VALID (MM_BROADBAND_MODEM (self)->priv->modem_cind_indicator_signal_quality))
signal_quality_cind (ctx);
else
signal_quality_csq (ctx);
return;
}
/* If no best AT port available (all connected), try with QCDM ports */
ctx->port = (MMPortSerial *)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;
MMPortSerialQcdm *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 (MMPortSerialQcdm *port,
GAsyncResult *res,
AccessTechContext *ctx)
{
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint8 l1;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
access_tech_context_complete_and_free (ctx, error, FALSE);
return;
}
/* Parse the response */
result = qcdm_cmd_wcdma_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
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 (MMPortSerialQcdm *port,
GAsyncResult *res,
AccessTechContext *ctx)
{
GByteArray *cmd;
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint8 opmode = 0;
guint8 sysmode = 0;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
access_tech_context_complete_and_free (ctx, error, FALSE);
return;
}
/* Parse the response */
result = qcdm_cmd_gsm_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
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_port_serial_qcdm_command (port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_wcdma_ready,
ctx);
g_byte_array_unref (cmd);
}
static void
access_tech_qcdm_hdr_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
AccessTechContext *ctx)
{
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint8 session = 0;
guint8 almp = 0;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
access_tech_context_complete_and_free (ctx, error, FALSE);
return;
}
/* Parse the response */
result = qcdm_cmd_hdr_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
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 (MMPortSerialQcdm *port,
GAsyncResult *res,
AccessTechContext *ctx)
{
GByteArray *cmd;
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint32 hybrid;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
access_tech_context_complete_and_free (ctx, error, FALSE);
return;
}
/* Parse the response */
result = qcdm_cmd_cm_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
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_port_serial_qcdm_command (port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_hdr_ready,
ctx);
g_byte_array_unref (cmd);
}
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_port_serial_qcdm_command (ctx->port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_gsm_ready,
ctx);
g_byte_array_unref (cmd);
return;
}
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_port_serial_qcdm_command (ctx->port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_cdma_ready,
ctx);
g_byte_array_unref (cmd);
return;
}
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 (MMPortSerialAt *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)
{
MMPortSerialAt *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_port_serial_at_add_unsolicited_msg_handler (
ports[i],
ciev_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) 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)
{
MMPortSerialAt *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;
MMModemPowerState state;
GError *error = NULL;
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (!result || !mm_3gpp_parse_cfun_query_generic_response (result, &state, &error))
g_simple_async_result_take_error (simple, error);
else
g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (state), NULL);
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 = 1u << 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 = NULL;
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!result)
return NULL;
if (!mm_3gpp_parse_cops_read_response (result,
NULL, /* mode */
NULL, /* format */
&operator_code,
NULL, /* act */
error))
return NULL;
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 = NULL;
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!result)
return NULL;
if (!mm_3gpp_parse_cops_read_response (result,
NULL, /* mode */
NULL, /* format */
&operator_name,
NULL, /* act */
error))
return NULL;
mm_3gpp_normalize_operator_name (&operator_name, 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);
}
/*****************************************************************************/
/* Subscription State Loading (3GPP interface) */
static MMModem3gppSubscriptionState
modem_3gpp_load_subscription_state_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
return MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNKNOWN;
return (MMModem3gppSubscriptionState) GPOINTER_TO_UINT (
g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
}
static void
modem_3gpp_load_subscription_state (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_3gpp_load_subscription_state);
g_simple_async_result_set_op_res_gpointer (
result,
GUINT_TO_POINTER (MM_MODEM_3GPP_SUBSCRIPTION_STATE_UNKNOWN),
NULL);
g_simple_async_result_complete_in_idle (result);
g_object_unref (result);
}
/*****************************************************************************/
/* 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 (MMPortSerialAt *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;
MMPortSerialAt *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_port_serial_at_add_unsolicited_msg_handler (
MM_PORT_SERIAL_AT (ports[i]),
(GRegex *) g_ptr_array_index (array, j),
(MMPortSerialAtUnsolicitedMsgFn)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;
MMPortSerialAt *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_port_serial_at_add_unsolicited_msg_handler (
MM_PORT_SERIAL_AT (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=?",
300,
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;
MMPortSerialAt *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
* precedence over PS errors and PS errors take precedence 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 (MMPortSerialAt *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;
MMPortSerialAt *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_port_serial_at_add_unsolicited_msg_handler (
ports[i],
cusd_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) 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);
}
/*****************************************************************************/
/* Init current SMS storages (Messaging interface) */
static gboolean
modem_messaging_init_current_storages_finish (MMIfaceModemMessaging *_self,
GAsyncResult *res,
GError **error)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
cpms_query_ready (MMBroadbandModem *self,
GAsyncResult *res,
GSimpleAsyncResult *simple)
{
const gchar *response;
GError *error = NULL;
MMSmsStorage mem1;
MMSmsStorage mem2;
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;
}
/* Parse reply */
if (!mm_3gpp_parse_cpms_query_response (response,
&mem1,
&mem2,
&error)) {
g_simple_async_result_take_error (simple, error);
} else {
self->priv->current_sms_mem1_storage = mem1;
self->priv->current_sms_mem2_storage = mem2;
mm_dbg ("Current storages initialized:");
mm_dbg (" mem1 (list/read/delete) storages: '%s'",
mm_common_build_sms_storages_string (&mem1, 1));
mm_dbg (" mem2 (write/send) storages: '%s'",
mm_common_build_sms_storages_string (&mem2, 1));
}
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
modem_messaging_init_current_storages (MMIfaceModemMessaging *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_messaging_init_current_storages);
/* Check support storages */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CPMS?",
3,
TRUE,
(GAsyncReadyCallback)cpms_query_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);
if (mem1 == MM_SMS_STORAGE_UNKNOWN) {
/* Some modems may not support empty string parameters. Then if mem1 is
* UNKNOWN, we send again the already locked mem1 value in place of an
* empty string. This way we also avoid to confuse the environment of
* other async operation that have potentially locked mem1 previoulsy.
* */
mem1_str = g_ascii_strup (mm_sms_storage_get_string (self->priv->current_sms_mem1_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 *mem1_str;
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;
/* We provide the current sms storage for mem1 if not UNKNOWN */
mem1_str = g_ascii_strup (mm_sms_storage_get_string (self->priv->current_sms_mem1_storage), -1);
mem_str = g_ascii_strup (mm_sms_storage_get_string (storage), -1);
cmd = g_strdup_printf ("+CPMS=\"%s\",\"%s\",\"%s\"",
mem1_str ? mem1_str : "",
mem_str,
mem_str);
mm_base_modem_at_command (MM_BASE_MODEM (self),
cmd,
3,
FALSE,
(GAsyncReadyCallback)cpms_set_ready,
result);
g_free (mem1_str);
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;
MM3gppPduInfo *info;
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;
}
info = mm_3gpp_parse_cmgr_read_response (response, ctx->idx, &error);
if (!info) {
mm_warn ("Couldn't parse 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_3gpp_new_from_pdu (info->index, info->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 */
mm_3gpp_pdu_info_free (info);
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 (MMPortSerialAt *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 (MMPortSerialAt *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_3gpp_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;
MMPortSerialAt *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));
/* Add messaging unsolicited events handler for port primary and secondary */
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_port_serial_at_add_unsolicited_msg_handler (
ports[i],
cmti_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) cmti_received : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
cds_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) 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)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
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_secondary_ready (MMBaseModem *self,
GAsyncResult *res,
GSimpleAsyncResult *final_result)
{
GError *inner_error = NULL;
MMPortSerialAt *secondary;
secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
/* Since the secondary is not required, we don't propagate the error anywhere */
mm_base_modem_at_sequence_full_finish (MM_BASE_MODEM (self), res, NULL, &inner_error);
if (inner_error) {
mm_dbg ("(%s) Unable to enable messaging unsolicited events on modem secondary: %s",
mm_port_get_device (MM_PORT (secondary)),
inner_error->message);
g_error_free (inner_error);
}
mm_dbg ("(%s) Messaging unsolicited events enabled on secondary",
mm_port_get_device (MM_PORT (secondary)));
g_simple_async_result_complete (final_result);
g_object_unref (final_result);
}
static void
modem_messaging_enable_unsolicited_events_primary_ready (MMBaseModem *self,
GAsyncResult *res,
GSimpleAsyncResult *final_result)
{
GError *inner_error = NULL;
MMPortSerialAt *primary;
MMPortSerialAt *secondary;
primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
mm_base_modem_at_sequence_full_finish (MM_BASE_MODEM (self), res, NULL, &inner_error);
if (inner_error) {
g_simple_async_result_take_error (final_result, inner_error);
g_simple_async_result_complete (final_result);
g_object_unref (final_result);
return;
}
mm_dbg ("(%s) Messaging unsolicited events enabled on primary",
mm_port_get_device (MM_PORT (primary)));
/* Try to enable unsolicited events for secondary port */
if (secondary) {
mm_dbg ("(%s) Enabling messaging unsolicited events on secondary port",
mm_port_get_device (MM_PORT (secondary)));
mm_base_modem_at_sequence_full (
MM_BASE_MODEM (self),
secondary,
cnmi_sequence,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
NULL,
(GAsyncReadyCallback)modem_messaging_enable_unsolicited_events_secondary_ready,
final_result);
return;
}
g_simple_async_result_complete (final_result);
g_object_unref (final_result);
}
static void
modem_messaging_enable_unsolicited_events (MMIfaceModemMessaging *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
MMPortSerialAt *primary;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_messaging_enable_unsolicited_events);
primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
/* Enable unsolicited events for primary port */
mm_dbg ("(%s) Enabling messaging unsolicited events on primary port",
mm_port_get_device (MM_PORT (primary)));
mm_base_modem_at_sequence_full (
MM_BASE_MODEM (self),
primary,
cnmi_sequence,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
NULL,
(GAsyncReadyCallback)modem_messaging_enable_unsolicited_events_primary_ready,
result);
}
/*****************************************************************************/
/* 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_match_info_free (match_info);
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_3gpp_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 MMBaseSms *
modem_messaging_create_sms (MMIfaceModemMessaging *self)
{
return mm_base_sms_new (MM_BASE_MODEM (self));
}
/*****************************************************************************/
/* Check if Voice supported (Voice interface) */
static gboolean
modem_voice_check_support_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
ath_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;
}
/* ATH command is supported; assume we have full voice capabilities */
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
g_simple_async_result_complete (simple);
g_object_unref (simple);
}
static void
modem_voice_check_support (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_voice_check_support);
/* We assume that all modems have voice capabilities, but ... */
/* Check ATH support */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"H",
3,
TRUE,
(GAsyncReadyCallback)ath_format_check_ready,
result);
}
/*****************************************************************************/
/* Setup/cleanup voice related unsolicited events (Voice interface) */
static gboolean
modem_voice_setup_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
GAsyncResult *res,
GError **error)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
ring_received (MMPortSerialAt *port,
GMatchInfo *info,
MMBroadbandModem *self)
{
mm_dbg ("Ringing");
mm_iface_modem_voice_create_incoming_call (MM_IFACE_MODEM_VOICE (self));
}
static void
cring_received (MMPortSerialAt *port,
GMatchInfo *info,
MMBroadbandModem *self)
{
/* The match info gives us in which storage the index applies */
gchar *str;
/* We could have "VOICE" or "DATA". Now consider only "VOICE" */
str = mm_get_string_unquoted_from_match_info (info, 1);
mm_dbg ("Ringing (%s)", str);
g_free (str);
mm_iface_modem_voice_create_incoming_call (MM_IFACE_MODEM_VOICE (self));
}
static void
clip_received (MMPortSerialAt *port,
GMatchInfo *info,
MMBroadbandModem *self)
{
/* The match info gives us in which storage the index applies */
gchar *str;
str = mm_get_string_unquoted_from_match_info (info, 1);
if (str) {
guint validity = 0;
guint type = 0;
mm_get_uint_from_match_info (info, 2, &type);
mm_get_uint_from_match_info (info, 3, &validity);
mm_dbg ("Caller ID received: number '%s', type '%d', validity '%d'", str, type, validity);
mm_iface_modem_voice_update_incoming_call_number (MM_IFACE_MODEM_VOICE (self), str, type, validity);
g_free (str);
}
}
static void
set_voice_unsolicited_events_handlers (MMIfaceModemVoice *self,
gboolean enable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *result;
MMPortSerialAt *ports[2];
GRegex *cring_regex;
GRegex *ring_regex;
GRegex *clip_regex;
guint i;
result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
set_voice_unsolicited_events_handlers);
cring_regex = mm_voice_cring_regex_get ();
ring_regex = mm_voice_ring_regex_get ();
clip_regex = mm_voice_clip_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 voice unsolicited events handlers",
mm_port_get_device (MM_PORT (ports[i])),
enable ? "Setting" : "Removing");
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
cring_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) cring_received : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
ring_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) ring_received : NULL,
enable ? self : NULL,
NULL);
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
clip_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) clip_received : NULL,
enable ? self : NULL,
NULL);
}
g_regex_unref (clip_regex);
g_regex_unref (cring_regex);
g_regex_unref (ring_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_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
set_voice_unsolicited_events_handlers (self, TRUE, callback, user_data);
}
static void
modem_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
set_voice_unsolicited_events_handlers (self, FALSE, callback, user_data);
}
/*****************************************************************************/
/* Enable unsolicited events (CALL indications) (Voice interface) */
static gboolean
modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *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
ring_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 TRUE;
}
*result = NULL;
return FALSE;
}
static const MMBaseModemAtCommand ring_sequence[] = {
/* Show caller number on RING. */
{ "+CLIP=1", 3, FALSE, ring_response_processor },
/* Show difference between data call and voice call */
{ "+CRC=1", 3, FALSE, ring_response_processor },
{ NULL }
};
static void
modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
ring_sequence,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
callback,
user_data);
}
/*****************************************************************************/
/* Create CALL (Voice interface) */
static MMBaseCall *
modem_voice_create_call (MMIfaceModemVoice *self)
{
return mm_base_call_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);
}
/*****************************************************************************/
/* Setup/Cleanup unsolicited events (CDMA interface) */
typedef struct {
MMBroadbandModem *self;
gboolean setup;
GSimpleAsyncResult *result;
MMPortSerialQcdm *qcdm;
} CdmaUnsolicitedEventsContext;
static void
cdma_unsolicited_events_context_complete_and_free (CdmaUnsolicitedEventsContext *ctx,
gboolean close_port,
GError *error)
{
if (error)
g_simple_async_result_take_error (ctx->result, error);
else
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
g_simple_async_result_complete_in_idle (ctx->result);
g_clear_object (&ctx->result);
g_clear_object (&ctx->self);
if (ctx->qcdm && close_port)
mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
g_clear_object (&ctx->qcdm);
g_free (ctx);
}
static void
logcmd_qcdm_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
CdmaUnsolicitedEventsContext *ctx)
{
QcdmResult *result;
gint err = QCDM_SUCCESS;
GByteArray *response;
GError *error = NULL;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
cdma_unsolicited_events_context_complete_and_free (ctx, TRUE, error);
return;
}
/* Parse the response */
result = qcdm_cmd_log_config_set_mask_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
if (!result) {
error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse Log Config Set Mask command result: %d",
err);
cdma_unsolicited_events_context_complete_and_free (ctx, TRUE, error);
return;
}
mm_port_serial_qcdm_add_unsolicited_msg_handler (port,
DM_LOG_ITEM_EVDO_PILOT_SETS_V2,
ctx->setup ? qcdm_evdo_pilot_sets_log_handle : NULL,
ctx->self,
NULL);
qcdm_result_unref (result);
/* Balance the mm_port_seral_open() from modem_cdma_setup_cleanup_unsolicited_events().
* We want to close it in either case:
* (a) we're cleaning up and setup opened the port
* (b) if it was unexpectedly closed before cleanup and thus cleanup opened it
*
* Setup should leave the port open to allow log messages to be received
* and sent to handlers.
*/
cdma_unsolicited_events_context_complete_and_free (ctx, ctx->setup ? FALSE : TRUE, NULL);
}
static void
modem_cdma_setup_cleanup_unsolicited_events (MMBroadbandModem *self,
gboolean setup,
GAsyncReadyCallback callback,
gpointer user_data)
{
CdmaUnsolicitedEventsContext *ctx;
GByteArray *logcmd;
uint16_t log_items[] = { DM_LOG_ITEM_EVDO_PILOT_SETS_V2, 0 };
GError *error = NULL;
ctx = g_new0 (CdmaUnsolicitedEventsContext, 1);
ctx->self = g_object_ref (self);
ctx->setup = TRUE;
ctx->result = g_simple_async_result_new (G_OBJECT (self),
callback,
user_data,
modem_cdma_setup_cleanup_unsolicited_events);
ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
if (!ctx->qcdm) {
cdma_unsolicited_events_context_complete_and_free (ctx, FALSE, NULL);
return;
}
/* Setup must open the QCDM port and keep it open to receive unsolicited
* events. Cleanup expects the port to already be opened from setup, but
* if not we still want to open it and try to disable log messages.
*/
if (setup || !mm_port_serial_is_open (MM_PORT_SERIAL (ctx->qcdm))) {
if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm), &error)) {
cdma_unsolicited_events_context_complete_and_free (ctx, FALSE, error);
return;
}
}
logcmd = g_byte_array_sized_new (512);
logcmd->len = qcdm_cmd_log_config_set_mask_new ((char *) logcmd->data,
512,
0x01, /* Equipment ID */
setup ? log_items : NULL);
assert (logcmd->len);
mm_port_serial_qcdm_command (ctx->qcdm,
logcmd,
5,
NULL,
(GAsyncReadyCallback)logcmd_qcdm_ready,
ctx);
g_byte_array_unref (logcmd);
}
static gboolean
modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self,
GAsyncResult *res,
GError **error)
{
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
}
static void
modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
modem_cdma_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM (self),
TRUE,
callback,
user_data);
}
static void
modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
modem_cdma_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM (self),
FALSE,
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;
MMPortSerialQcdm *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 (MMPortSerialQcdm *port,
GAsyncResult *res,
HdrStateContext *ctx)
{
QcdmResult *result;
HdrStateResults *results;
gint err = QCDM_SUCCESS;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
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);
g_byte_array_unref (response);
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)
{
MMPortSerialQcdm *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_port_serial_qcdm_command (ctx->qcdm,
hdrstate,
3,
NULL,
(GAsyncReadyCallback)hdr_subsys_state_info_ready,
ctx);
g_byte_array_unref (hdrstate);
}
/*****************************************************************************/
/* Call Manager state check (CDMA interface) */
typedef struct {
guint system_mode;
guint operating_mode;
} CallManagerStateResults;
typedef struct {
MMBroadbandModem *self;
GSimpleAsyncResult *result;
MMPortSerialQcdm *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 (MMPortSerialQcdm *port,
GAsyncResult *res,
CallManagerStateContext *ctx)
{
QcdmResult *result;
CallManagerStateResults *results;
gint err = QCDM_SUCCESS;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_simple_async_result_take_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);
g_byte_array_unref (response);
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)
{
MMPortSerialQcdm *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_port_serial_qcdm_command (ctx->qcdm,
cmstate,
3,
NULL,
(GAsyncReadyCallback)cm_subsys_state_info_ready,
ctx);
g_byte_array_unref (cmstate);
}
/*****************************************************************************/
/* Serving System check (CDMA interface) */
typedef struct {
guint sid;
guint nid;
guint class;
guint band;
} Cdma1xServingSystemResults;
typedef struct {
MMBroadbandModem *self;
GSimpleAsyncResult *result;
MMPortSerialQcdm *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 (MMPortSerialQcdm *port,
GAsyncResult *res,
Cdma1xServingSystemContext *ctx)
{
Cdma1xServingSystemResults *results;
QcdmResult *result = NULL;
guint32 sid = MM_MODEM_CDMA_SID_UNKNOWN;
guint32 nid = MM_MODEM_CDMA_NID_UNKNOWN;
guint32 rxstate = 0;
gint err = QCDM_SUCCESS;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
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);
if (error)
g_error_free (error);
if (response)
g_byte_array_unref (response);
return;
}
g_byte_array_unref (response);
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_port_serial_qcdm_command (ctx->qcdm,
cdma_status,
3,
NULL,
(GAsyncReadyCallback)qcdm_cdma_status_ready,
ctx);
g_byte_array_unref (cdma_status);
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;
MMPortSerialAt *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)
{
MMPortSerialAt *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 G_SOURCE_REMOVE;
}
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 registration information 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_registration_info (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);
}
/*****************************************************************************/
/* Load network time (Time interface) */
static gchar *
modem_time_load_network_time_finish (MMIfaceModemTime *self,
GAsyncResult *res,
GError **error)
{
const gchar *response;
gchar *result = NULL;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response)
return NULL;
if (!mm_parse_cclk_response (response, &result, NULL, error))
return NULL;
return result;
}
static void
modem_time_load_network_time (MMIfaceModemTime *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CCLK?",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Load network timezone (Time interface) */
static MMNetworkTimezone *
modem_time_load_network_timezone_finish (MMIfaceModemTime *self,
GAsyncResult *res,
GError **error)
{
const gchar *response;
MMNetworkTimezone *tz = NULL;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response)
return NULL;
if (!mm_parse_cclk_response (response, NULL, &tz, error))
return NULL;
return tz;
}
static void
modem_time_load_network_timezone (MMIfaceModemTime *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CCLK?",
3,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Check support (Time interface) */
static const MMBaseModemAtCommand time_check_sequence[] = {
{ "+CTZU=1", 3, TRUE, mm_base_modem_response_processor_no_result_continue },
{ "+CCLK?", 3, TRUE, mm_base_modem_response_processor_string },
{ NULL }
};
static gboolean
modem_time_check_support_finish (MMIfaceModemTime *self,
GAsyncResult *res,
GError **error)
{
return !!mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
}
static void
modem_time_check_support (MMIfaceModemTime *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_sequence (MM_BASE_MODEM (self),
time_check_sequence,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
callback,
user_data);
}
/*****************************************************************************/
/* Check support (Signal interface) */
static gboolean
modem_signal_check_support_finish (MMIfaceModemSignal *self,
GAsyncResult *res,
GError **error)
{
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
}
static void
modem_signal_check_support (MMIfaceModemSignal *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CESQ=?",
3,
TRUE,
callback,
user_data);
}
/*****************************************************************************/
/* Load extended signal information (Signal interface) */
static gboolean
modem_signal_load_values_finish (MMIfaceModemSignal *self,
GAsyncResult *res,
MMSignal **cdma,
MMSignal **evdo,
MMSignal **gsm,
MMSignal **umts,
MMSignal **lte,
GError **error)
{
const gchar *response;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
if (!response || !mm_3gpp_cesq_response_to_signal_info (response, gsm, umts, lte, error))
return FALSE;
/* No 3GPP2 support */
if (cdma)
*cdma = NULL;
if (evdo)
*evdo = NULL;
return TRUE;
}
static void
modem_signal_load_values (MMIfaceModemSignal *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CESQ",
3,
TRUE,
callback,
user_data);
}
/*****************************************************************************/
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)
{
MMPortSerialAt *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_PORT_SERIAL_AT_INIT_SEQUENCE, primary_init_sequence,
NULL);
if (ports[1])
g_object_set (ports[1],
MM_PORT_SERIAL_AT_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_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (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_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (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_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (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_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]),
regex,
NULL,
NULL,
NULL);
}
g_regex_unref (regex);
}
/*****************************************************************************/
/* Generic ports open/close context */
struct _PortsContext {
volatile gint ref_count;
MMPortSerialAt *primary;
gboolean primary_open;
MMPortSerialAt *secondary;
gboolean secondary_open;
MMPortSerialQcdm *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_port_serial_close (MM_PORT_SERIAL (ctx->primary));
g_object_unref (ctx->primary);
}
if (ctx->secondary) {
if (ctx->secondary_open)
mm_port_serial_close (MM_PORT_SERIAL (ctx->secondary));
g_object_unref (ctx->secondary);
}
if (ctx->qcdm) {
if (ctx->qcdm_open)
mm_port_serial_close (MM_PORT_SERIAL (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_port_serial_open (MM_PORT_SERIAL (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;
}
if (self->priv->sim_hot_swap_ports_ctx) {
ports_context_unref (self->priv->sim_hot_swap_ports_ctx);
self->priv->sim_hot_swap_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_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, TRUE,
NULL);
mm_port_serial_at_run_init_sequence (ctx->ports->primary);
if (ctx->ports->secondary) {
g_object_set (ctx->ports->secondary,
MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, TRUE,
NULL);
mm_port_serial_at_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 G_SOURCE_REMOVE;
}
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 (MMPortSerial *port,
GAsyncResult *res,
EnablingStartedContext *ctx)
{
GError *error = NULL;
if (!mm_port_serial_flash_finish (port, res, &error)) {
g_prefix_error (&error, "Primary port flashing failed: ");
g_simple_async_result_take_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_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE,
NULL);
if (!mm_port_serial_open (MM_PORT_SERIAL (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_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, FALSE,
NULL);
if (!mm_port_serial_open (MM_PORT_SERIAL (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_port_serial_open (MM_PORT_SERIAL (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_port_serial_flash (MM_PORT_SERIAL (ctx->ports->primary),
100,
FALSE,
(GAsyncReadyCallback)enabling_flash_done,
ctx);
}
/*****************************************************************************/
/* First registration checks */
static void
modem_3gpp_run_registration_checks_ready (MMIfaceModem3gpp *self,
GAsyncResult *res)
{
GError *error = NULL;
if (!mm_iface_modem_3gpp_run_registration_checks_finish (self, res, &error)) {
mm_warn ("Initial 3GPP registration check failed: %s", error->message);
g_error_free (error);
return;
}
mm_dbg ("Initial 3GPP registration checks finished");
}
static void
modem_cdma_run_registration_checks_ready (MMIfaceModemCdma *self,
GAsyncResult *res)
{
GError *error = NULL;
if (!mm_iface_modem_cdma_run_registration_checks_finish (self, res, &error)) {
mm_warn ("Initial CDMA registration check failed: %s", error->message);
g_error_free (error);
return;
}
mm_dbg ("Initial CDMA registration checks finished");
}
static gboolean
schedule_initial_registration_checks_cb (MMBroadbandModem *self)
{
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self)))
mm_iface_modem_3gpp_run_registration_checks (MM_IFACE_MODEM_3GPP (self),
(GAsyncReadyCallback) modem_3gpp_run_registration_checks_ready,
NULL);
if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self)))
mm_iface_modem_cdma_run_registration_checks (MM_IFACE_MODEM_CDMA (self),
(GAsyncReadyCallback) modem_cdma_run_registration_checks_ready,
NULL);
/* We got a full reference, so balance it out here */
g_object_unref (self);
return G_SOURCE_REMOVE;
}
static void
schedule_initial_registration_checks (MMBroadbandModem *self)
{
g_idle_add ((GSourceFunc) schedule_initial_registration_checks_cb, g_object_ref (self));
}
/*****************************************************************************/
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_SIGNAL,
DISABLING_STEP_IFACE_OMA,
DISABLING_STEP_IFACE_TIME,
DISABLING_STEP_IFACE_MESSAGING,
DISABLING_STEP_IFACE_VOICE,
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 (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_voice, MM_IFACE_MODEM_VOICE, FALSE)
INTERFACE_DISABLE_READY_FN (iface_modem_signal, MM_IFACE_MODEM_SIGNAL, FALSE)
INTERFACE_DISABLE_READY_FN (iface_modem_time, MM_IFACE_MODEM_TIME, FALSE)
INTERFACE_DISABLE_READY_FN (iface_modem_oma, MM_IFACE_MODEM_OMA, 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 (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 (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_SIGNAL:
if (ctx->self->priv->modem_signal_dbus_skeleton) {
mm_dbg ("Modem has extended signal reporting capabilities, disabling the Signal interface...");
/* Disabling the Modem Signal interface */
mm_iface_modem_signal_disable (MM_IFACE_MODEM_SIGNAL (ctx->self),
(GAsyncReadyCallback)iface_modem_signal_disable_ready,
ctx);
return;
}
/* Fall down to next step */
ctx->step++;
case DISABLING_STEP_IFACE_OMA:
if (ctx->self->priv->modem_oma_dbus_skeleton) {
mm_dbg ("Modem has OMA capabilities, disabling the OMA interface...");
/* Disabling the Modem Oma interface */
mm_iface_modem_oma_disable (MM_IFACE_MODEM_OMA (ctx->self),
(GAsyncReadyCallback)iface_modem_oma_disable_ready,
ctx);
return;
}
/* 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_VOICE:
if (ctx->self->priv->modem_voice_dbus_skeleton) {
mm_dbg ("Modem has voice capabilities, disabling the Voice interface...");
/* Disabling the Modem Voice interface */
mm_iface_modem_voice_disable (MM_IFACE_MODEM_VOICE (ctx->self),
(GAsyncReadyCallback)iface_modem_voice_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 (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_VOICE,
ENABLING_STEP_IFACE_TIME,
ENABLING_STEP_IFACE_SIGNAL,
ENABLING_STEP_IFACE_OMA,
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 (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_voice, MM_IFACE_MODEM_VOICE, FALSE)
INTERFACE_ENABLE_READY_FN (iface_modem_signal, MM_IFACE_MODEM_SIGNAL, FALSE)
INTERFACE_ENABLE_READY_FN (iface_modem_time, MM_IFACE_MODEM_TIME, FALSE)
INTERFACE_ENABLE_READY_FN (iface_modem_oma, MM_IFACE_MODEM_OMA, 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 (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 (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_VOICE:
if (ctx->self->priv->modem_voice_dbus_skeleton) {
mm_dbg ("Modem has voice capabilities, enabling the Voice interface...");
/* Enabling the Modem Voice interface */
mm_iface_modem_voice_enable (MM_IFACE_MODEM_VOICE (ctx->self),
ctx->cancellable,
(GAsyncReadyCallback)iface_modem_voice_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_SIGNAL:
if (ctx->self->priv->modem_signal_dbus_skeleton) {
mm_dbg ("Modem has extended signal reporting capabilities, enabling the Signal interface...");
/* Enabling the Modem Signal interface */
mm_iface_modem_signal_enable (MM_IFACE_MODEM_SIGNAL (ctx->self),
ctx->cancellable,
(GAsyncReadyCallback)iface_modem_signal_enable_ready,
ctx);
return;
}
/* Fall down to next step */
ctx->step++;
case ENABLING_STEP_IFACE_OMA:
if (ctx->self->priv->modem_oma_dbus_skeleton) {
mm_dbg ("Modem has OMA capabilities, enabling the OMA interface...");
/* Enabling the Modem Oma interface */
mm_iface_modem_oma_enable (MM_IFACE_MODEM_OMA (ctx->self),
ctx->cancellable,
(GAsyncReadyCallback)iface_modem_oma_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;
/* Once all interfaces have been enabled, trigger registration checks in
* 3GPP and CDMA modems. We have to do this at this point so that e.g.
* location interface gets proper registration related info reported.
*
* We do this in an idle so that we don't mess up the logs at this point
* with the new requests being triggered.
*/
schedule_initial_registration_checks (ctx->self);
/* All enabled without errors! */
g_simple_async_result_set_op_res_gboolean (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:
/* We should never have a UNKNOWN->ENABLED transition */
g_assert_not_reached ();
break;
case MM_MODEM_STATE_FAILED:
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_IN_PROGRESS,
"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_VOICE,
INITIALIZE_STEP_IFACE_TIME,
INITIALIZE_STEP_IFACE_SIGNAL,
INITIALIZE_STEP_IFACE_OMA,
INITIALIZE_STEP_IFACE_FIRMWARE,
INITIALIZE_STEP_SIM_HOT_SWAP,
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_voice, MM_IFACE_MODEM_VOICE, FALSE)
INTERFACE_INIT_READY_FN (iface_modem_time, MM_IFACE_MODEM_TIME, FALSE)
INTERFACE_INIT_READY_FN (iface_modem_signal, MM_IFACE_MODEM_SIGNAL, FALSE)
INTERFACE_INIT_READY_FN (iface_modem_oma, MM_IFACE_MODEM_OMA, 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_VOICE:
/* Initialize the Voice interface */
mm_iface_modem_voice_initialize (MM_IFACE_MODEM_VOICE (ctx->self),
ctx->cancellable,
(GAsyncReadyCallback)iface_modem_voice_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_SIGNAL:
/* Initialize the Signal interface */
mm_iface_modem_signal_initialize (MM_IFACE_MODEM_SIGNAL (ctx->self),
ctx->cancellable,
(GAsyncReadyCallback)iface_modem_signal_initialize_ready,
ctx);
return;
case INITIALIZE_STEP_IFACE_OMA:
/* Initialize the Oma interface */
mm_iface_modem_oma_initialize (MM_IFACE_MODEM_OMA (ctx->self),
ctx->cancellable,
(GAsyncReadyCallback)iface_modem_oma_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_SIM_HOT_SWAP:
{
gboolean is_sim_hot_swap_supported = FALSE;
g_object_get (ctx->self,
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, &is_sim_hot_swap_supported,
NULL);
if (is_sim_hot_swap_supported) {
PortsContext *ports;
GError *error = NULL;
mm_dbg ("Creating PortsContext for SIM hot swap.");
ports = g_new0 (PortsContext, 1);
ports->ref_count = 1;
if (!open_ports_enabling (ctx->self, ports, FALSE, &error)) {
mm_warn ("Couldn't open ports during Modem SIM hot swap enabling: %s", error? error->message : "unknown reason");
g_error_free (error);
} else
ctx->self->priv->sim_hot_swap_ports_ctx = ports_context_ref (ports);
ports_context_unref (ports);
}
}
/* Fall down to next step */
ctx->step++;
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, firmware, or modem failure :-( */
gboolean is_sim_hot_swap_supported = FALSE;
MMModemStateFailedReason reason =
mm_gdbus_modem_get_state_failed_reason (
(MmGdbusModem*)ctx->self->priv->modem_dbus_skeleton);
g_object_get (ctx->self,
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED,
&is_sim_hot_swap_supported,
NULL);
if (reason == MM_MODEM_STATE_FAILED_REASON_SIM_MISSING &&
is_sim_hot_swap_supported &&
ctx->self->priv->sim_hot_swap_ports_ctx) {
mm_info ("SIM is missing, but the modem supports SIM hot swap. Waiting for SIM...");
g_simple_async_result_set_error (ctx->result,
MM_CORE_ERROR,
MM_CORE_ERROR_WRONG_STATE,
"Modem is unusable due to SIM missing, "
"cannot fully initialize, "
"waiting for SIM insertion.");
} else {
mm_dbg ("SIM is missing and Modem does not support SIM Hot Swap");
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, OMA, and Firmware interfaces
* around. A failure could be caused by firmware issues, which
* a firmware update, switch, or provisioning could fix.
*/
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_voice_shutdown (MM_IFACE_MODEM_VOICE (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 (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_IN_PROGRESS,
"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);
}
MMModemCharset
mm_broadband_modem_get_current_charset (MMBroadbandModem *self)
{
return 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))));
}
/*****************************************************************************/
static void
after_hotswap_event_disable_ready (MMBaseModem *self,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
mm_base_modem_disable_finish (self, res, &error);
if (error) {
mm_err ("Disable modem error: %s", error->message);
g_error_free (error);
} else {
mm_base_modem_set_valid (MM_BASE_MODEM (self), FALSE);
}
}
void
mm_broadband_modem_update_sim_hot_swap_detected (MMBroadbandModem *self)
{
if (self->priv->sim_hot_swap_ports_ctx) {
mm_dbg ("Releasing SIM hot swap ports context");
ports_context_unref (self->priv->sim_hot_swap_ports_ctx);
self->priv->sim_hot_swap_ports_ctx = NULL;
}
mm_base_modem_set_reprobe (MM_BASE_MODEM (self), TRUE);
mm_base_modem_disable (MM_BASE_MODEM (self),
(GAsyncReadyCallback) after_hotswap_event_disable_ready,
NULL);
}
/*****************************************************************************/
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_VOICE_DBUS_SKELETON:
g_clear_object (&self->priv->modem_voice_dbus_skeleton);
self->priv->modem_voice_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_SIGNAL_DBUS_SKELETON:
g_clear_object (&self->priv->modem_signal_dbus_skeleton);
self->priv->modem_signal_dbus_skeleton = g_value_dup_object (value);
break;
case PROP_MODEM_OMA_DBUS_SKELETON:
g_clear_object (&self->priv->modem_oma_dbus_skeleton);
self->priv->modem_oma_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_VOICE_CALL_LIST:
g_clear_object (&self->priv->modem_voice_call_list);
self->priv->modem_voice_call_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;
case PROP_MODEM_SIM_HOT_SWAP_SUPPORTED:
self->priv->sim_hot_swap_supported = g_value_get_boolean (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_VOICE_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_voice_dbus_skeleton);
break;
case PROP_MODEM_TIME_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_time_dbus_skeleton);
break;
case PROP_MODEM_SIGNAL_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_signal_dbus_skeleton);
break;
case PROP_MODEM_OMA_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_oma_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_VOICE_CALL_LIST:
g_value_set_object (value, self->priv->modem_voice_call_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;
case PROP_MODEM_SIM_HOT_SWAP_SUPPORTED:
g_value_set_boolean (value, self->priv->sim_hot_swap_supported);
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;
self->priv->sim_hot_swap_supported = FALSE;
}
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_voice_dbus_skeleton) {
mm_iface_modem_voice_shutdown (MM_IFACE_MODEM_VOICE (object));
g_clear_object (&self->priv->modem_voice_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_voice_call_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->load_subscription_state = modem_3gpp_load_subscription_state;
iface->load_subscription_state_finish = modem_3gpp_load_subscription_state_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_unsolicited_events = modem_cdma_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
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;
iface->init_current_storages = modem_messaging_init_current_storages;
iface->init_current_storages_finish = modem_messaging_init_current_storages_finish;
}
static void
iface_modem_voice_init (MMIfaceModemVoice *iface)
{
iface->check_support = modem_voice_check_support;
iface->check_support_finish = modem_voice_check_support_finish;
iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_voice_setup_cleanup_unsolicited_events_finish;
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_voice_setup_cleanup_unsolicited_events_finish;
iface->create_call = modem_voice_create_call;
}
static void
iface_modem_time_init (MMIfaceModemTime *iface)
{
iface->check_support = modem_time_check_support;
iface->check_support_finish = modem_time_check_support_finish;
iface->load_network_time = modem_time_load_network_time;
iface->load_network_time_finish = modem_time_load_network_time_finish;
iface->load_network_timezone = modem_time_load_network_timezone;
iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
}
static void
iface_modem_signal_init (MMIfaceModemSignal *iface)
{
iface->check_support = modem_signal_check_support;
iface->check_support_finish = modem_signal_check_support_finish;
iface->load_values = modem_signal_load_values;
iface->load_values_finish = modem_signal_load_values_finish;
}
static void
iface_modem_oma_init (MMIfaceModemOma *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_VOICE_DBUS_SKELETON,
MM_IFACE_MODEM_VOICE_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_SIGNAL_DBUS_SKELETON,
MM_IFACE_MODEM_SIGNAL_DBUS_SKELETON);
g_object_class_override_property (object_class,
PROP_MODEM_OMA_DBUS_SKELETON,
MM_IFACE_MODEM_OMA_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_VOICE_CALL_LIST,
MM_IFACE_MODEM_VOICE_CALL_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);
g_object_class_override_property (object_class,
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED);
}