The BroadMobi BM818 has inconsitent line endings and the in-call URC regex can miss events, particularly NO CARRIER, because the regex doesn't match. To fix this we add an optional carriage return to the regex. This will match for the BM818's output.
12697 lines
444 KiB
C
12697 lines
444 KiB
C
/* -*- 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>
|
|
* Copyright (C) 2019 Purism SPC
|
|
*/
|
|
|
|
#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"
|
|
#include "mm-helper-enums-types.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_3GPP_INITIAL_EPS_BEARER,
|
|
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_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS,
|
|
PROP_MODEM_VOICE_CALL_LIST,
|
|
PROP_MODEM_SIMPLE_STATUS,
|
|
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
|
|
PROP_MODEM_SIM_HOT_SWAP_CONFIGURED,
|
|
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
|
|
PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
|
|
PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
|
|
PROP_MODEM_CARRIER_CONFIG_MAPPING,
|
|
PROP_FLOW_CONTROL,
|
|
PROP_INDICATORS_DISABLED,
|
|
PROP_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[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;
|
|
PortsContext *in_call_ports_ctx;
|
|
gboolean modem_init_run;
|
|
gboolean sim_hot_swap_supported;
|
|
gboolean sim_hot_swap_configured;
|
|
gboolean periodic_signal_check_disabled;
|
|
gboolean periodic_access_tech_check_disabled;
|
|
|
|
/*<--- Modem interface --->*/
|
|
/* Properties */
|
|
GObject *modem_dbus_skeleton;
|
|
MMBaseSim *modem_sim;
|
|
MMBearerList *modem_bearer_list;
|
|
MMModemState modem_state;
|
|
gchar *carrier_config_mapping;
|
|
/* Implementation helpers */
|
|
MMModemCharset modem_current_charset;
|
|
gboolean modem_cind_disabled;
|
|
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;
|
|
MM3gppCmerMode modem_cmer_enable_mode;
|
|
MM3gppCmerMode modem_cmer_disable_mode;
|
|
MM3gppCmerInd modem_cmer_ind;
|
|
gboolean modem_cgerep_support_checked;
|
|
gboolean modem_cgerep_supported;
|
|
MMFlowControl flow_control;
|
|
|
|
/*<--- 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;
|
|
MMBaseBearer *modem_3gpp_initial_eps_bearer;
|
|
|
|
/*<--- Modem 3GPP USSD interface --->*/
|
|
/* Properties */
|
|
GObject *modem_3gpp_ussd_dbus_skeleton;
|
|
/* Implementation helpers */
|
|
gboolean use_unencoded_ussd;
|
|
GTask *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;
|
|
gboolean modem_location_allow_gps_unmanaged_always;
|
|
|
|
/*<--- 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;
|
|
gboolean periodic_call_list_check_disabled;
|
|
gboolean clcc_supported;
|
|
|
|
/*<--- 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;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ports_context_open (MMBroadbandModem *self,
|
|
PortsContext *ctx,
|
|
gboolean disable_at_init_sequence,
|
|
gboolean with_at_secondary,
|
|
gboolean with_qcdm,
|
|
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 (disable_at_init_sequence)
|
|
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) */
|
|
if (with_at_secondary) {
|
|
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 (disable_at_init_sequence)
|
|
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) */
|
|
if (with_qcdm) {
|
|
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 PortsContext *
|
|
ports_context_new (void)
|
|
{
|
|
PortsContext *ctx;
|
|
|
|
ctx = g_new0 (PortsContext, 1);
|
|
ctx->ref_count = 1;
|
|
return ctx;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
broadband_bearer_new_ready (GObject *source,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBaseBearer *bearer = NULL;
|
|
GError *error = NULL;
|
|
|
|
bearer = mm_broadband_bearer_new_finish (res, &error);
|
|
if (!bearer)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_pointer (task, bearer, g_object_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_create_bearer (MMIfaceModem *self,
|
|
MMBearerProperties *props,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* We just create a MMBroadbandBearer */
|
|
mm_dbg ("Creating Broadband bearer in broadband modem");
|
|
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
|
|
props,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_new_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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 {
|
|
MMModemCapability caps;
|
|
MMPortSerialQcdm *qcdm_port;
|
|
} LoadCapabilitiesContext;
|
|
|
|
static void
|
|
load_capabilities_context_free (LoadCapabilitiesContext *ctx)
|
|
{
|
|
if (ctx->qcdm_port) {
|
|
mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm_port));
|
|
g_object_unref (ctx->qcdm_port);
|
|
}
|
|
g_slice_free (LoadCapabilitiesContext, ctx);
|
|
}
|
|
|
|
static MMModemCapability
|
|
modem_load_current_capabilities_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_CAPABILITY_NONE;
|
|
}
|
|
return (MMModemCapability)value;
|
|
}
|
|
|
|
static void
|
|
current_capabilities_ws46_test_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadCapabilitiesContext *ctx;
|
|
const gchar *response;
|
|
GArray *modes;
|
|
guint i;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* Completely ignore errors in AT+WS46=? */
|
|
response = mm_base_modem_at_command_finish (self, res, NULL);
|
|
if (!response)
|
|
goto out;
|
|
|
|
modes = mm_3gpp_parse_ws46_test_response (response, NULL);
|
|
if (!modes)
|
|
goto out;
|
|
|
|
/* Add LTE caps if any of the reported modes supports 4G */
|
|
for (i = 0; i < modes->len; i++) {
|
|
if (g_array_index (modes, MMModemMode, i) & MM_MODEM_MODE_4G) {
|
|
ctx->caps |= MM_MODEM_CAPABILITY_LTE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* The +CGSM capability is saying that the modem is a 3GPP modem, but that
|
|
* doesn't necessarily mean it's a GSM/UMTS modem, it could be a LTE-only
|
|
* device. We did add the GSM_UMTS capability when +CGSM was found, so now
|
|
* we'll check if the device only reports 4G-only mode, and remove the
|
|
* capability if so.
|
|
*
|
|
* Note that we don't change the default +CGSM -> GSM/UMTS logic, we just
|
|
* fix it up.
|
|
*/
|
|
if ((modes->len == 1) && (g_array_index (modes, MMModemMode, 0) == MM_MODEM_MODE_4G)) {
|
|
g_assert (ctx->caps & MM_MODEM_CAPABILITY_LTE);
|
|
ctx->caps &= ~MM_MODEM_CAPABILITY_GSM_UMTS;
|
|
}
|
|
|
|
g_array_unref (modes);
|
|
|
|
out:
|
|
g_task_return_int (task, ctx->caps);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
typedef struct {
|
|
const 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) {
|
|
if (error &&
|
|
(g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) ||
|
|
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_BUSY) ||
|
|
g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG))) {
|
|
/* At least, it's a GSM modem */
|
|
*result = g_variant_new_uint32 (MM_MODEM_CAPABILITY_GSM_UMTS);
|
|
return TRUE;
|
|
}
|
|
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,
|
|
GTask *task)
|
|
{
|
|
LoadCapabilitiesContext *ctx;
|
|
GError *error = NULL;
|
|
GVariant *result;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
result = mm_base_modem_at_sequence_finish (self, res, NULL, &error);
|
|
if (!result) {
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"%s",
|
|
"Failed to determine modem capabilities.");
|
|
g_object_unref (task);
|
|
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 (
|
|
self,
|
|
"+WS46=?",
|
|
3,
|
|
TRUE, /* allow caching, it's a test command */
|
|
(GAsyncReadyCallback)current_capabilities_ws46_test_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, just set the already retrieved capabilities */
|
|
g_task_return_int (task, ctx->caps);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_current_capabilities_at (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
/* Launch sequence, we will expect a "u" GVariant */
|
|
mm_base_modem_at_sequence (
|
|
MM_BASE_MODEM (self),
|
|
capabilities,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
(GAsyncReadyCallback)capabilities_sequence_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
mode_pref_qcdm_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadCapabilitiesContext *ctx;
|
|
QcdmResult *result;
|
|
gint err = QCDM_SUCCESS;
|
|
uint8_t pref = 0;
|
|
GError *error = NULL;
|
|
GByteArray *response;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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_task_return_int (task, ctx->caps);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
at_caps:
|
|
load_current_capabilities_at (task);
|
|
}
|
|
|
|
static void
|
|
load_current_capabilities_qcdm (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
LoadCapabilitiesContext *ctx;
|
|
GByteArray *cmd;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
ctx->qcdm_port = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (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 (task);
|
|
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,
|
|
task);
|
|
g_byte_array_unref (cmd);
|
|
}
|
|
|
|
static void
|
|
modem_load_current_capabilities (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
LoadCapabilitiesContext *ctx;
|
|
GTask *task;
|
|
|
|
mm_dbg ("loading current capabilities...");
|
|
|
|
ctx = g_slice_new0 (LoadCapabilitiesContext);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_capabilities_context_free);
|
|
|
|
if (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self)))
|
|
load_current_capabilities_qcdm (task);
|
|
else
|
|
load_current_capabilities_at (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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_clear_pointer (&equip_id, g_free);
|
|
|
|
if (imei)
|
|
equip_id = g_strdup (imei);
|
|
else if (meid)
|
|
equip_id = g_strdup (meid);
|
|
else if (esn)
|
|
equip_id = g_strdup (esn);
|
|
else
|
|
g_assert_not_reached ();
|
|
|
|
g_free (esn);
|
|
g_free (meid);
|
|
g_free (imei);
|
|
} 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 {
|
|
MMPortSerialQcdm *qcdm;
|
|
} OwnNumbersContext;
|
|
|
|
static void
|
|
own_numbers_context_free (OwnNumbersContext *ctx)
|
|
{
|
|
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)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
mdn_qcdm_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse NV MDN command result: %d",
|
|
err);
|
|
g_object_unref (task);
|
|
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_task_return_pointer (task,
|
|
g_strdupv ((gchar **) numbers),
|
|
(GDestroyNotify)g_strfreev);
|
|
} else {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"%s",
|
|
"MDN from NV memory appears invalid");
|
|
}
|
|
} else {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"%s",
|
|
"Failed retrieve MDN");
|
|
}
|
|
g_object_unref (task);
|
|
|
|
qcdm_result_unref (result);
|
|
}
|
|
|
|
static void
|
|
modem_load_own_numbers_done (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
OwnNumbersContext *ctx;
|
|
const gchar *result;
|
|
GError *error = NULL;
|
|
GStrv numbers;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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,
|
|
task);
|
|
g_byte_array_unref (mdn);
|
|
return;
|
|
}
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
numbers = mm_3gpp_parse_cnum_exec_response (result);
|
|
g_task_return_pointer (task, numbers, (GDestroyNotify)g_strfreev);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_load_own_numbers (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
OwnNumbersContext *ctx;
|
|
GError *error = NULL;
|
|
GTask *task;
|
|
|
|
ctx = g_new0 (OwnNumbersContext, 1);
|
|
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;
|
|
}
|
|
}
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)own_numbers_context_free);
|
|
|
|
mm_dbg ("loading own numbers...");
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CNUM",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)modem_load_own_numbers_done,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_LOCK_UNKNOWN;
|
|
}
|
|
return (MMModemLock)value;
|
|
}
|
|
|
|
static void
|
|
cpin_query_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_int (task, lock);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_load_unlock_required (MMIfaceModem *self,
|
|
gboolean last_attempt,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* 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_task_return_int (task, MM_MODEM_LOCK_NONE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_dbg ("checking if unlock required...");
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CPIN?",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)cpin_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Supported modes loading (Modem interface) */
|
|
|
|
typedef struct {
|
|
MMUnlockRetries *retries;
|
|
guint i;
|
|
} LoadUnlockRetriesContext;
|
|
|
|
typedef struct {
|
|
MMModemLock lock;
|
|
const gchar *command;
|
|
} UnlockRetriesMap;
|
|
|
|
static const UnlockRetriesMap unlock_retries_map [] = {
|
|
{ MM_MODEM_LOCK_SIM_PIN, "+CSIM=10,\"0020000100\"" },
|
|
{ MM_MODEM_LOCK_SIM_PUK, "+CSIM=10,\"002C000100\"" },
|
|
{ MM_MODEM_LOCK_SIM_PIN2, "+CSIM=10,\"0020008100\"" },
|
|
{ MM_MODEM_LOCK_SIM_PUK2, "+CSIM=10,\"002C008100\"" },
|
|
};
|
|
|
|
static void
|
|
load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx)
|
|
{
|
|
g_object_unref (ctx->retries);
|
|
g_slice_free (LoadUnlockRetriesContext, ctx);
|
|
}
|
|
|
|
static MMUnlockRetries *
|
|
load_unlock_retries_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void load_unlock_retries_context_step (GTask *task);
|
|
|
|
static void
|
|
csim_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadUnlockRetriesContext *ctx;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
gint val;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response) {
|
|
mm_dbg ("Couldn't load retry count for lock '%s': %s",
|
|
mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock),
|
|
error->message);
|
|
goto next;
|
|
}
|
|
|
|
val = mm_parse_csim_response (response, &error);
|
|
if (val < 0) {
|
|
mm_dbg ("Couldn't parse retry count value for lock '%s': %s",
|
|
mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock),
|
|
error->message);
|
|
goto next;
|
|
}
|
|
|
|
mm_unlock_retries_set (ctx->retries, unlock_retries_map[ctx->i].lock, val);
|
|
|
|
next:
|
|
g_clear_error (&error);
|
|
|
|
/* Go to next lock value */
|
|
ctx->i++;
|
|
load_unlock_retries_context_step (task);
|
|
}
|
|
|
|
static void
|
|
load_unlock_retries_context_step (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
LoadUnlockRetriesContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->i == G_N_ELEMENTS (unlock_retries_map)) {
|
|
g_task_return_pointer (task, g_object_ref (ctx->retries), g_object_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
unlock_retries_map[ctx->i].command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)csim_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_unlock_retries (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
LoadUnlockRetriesContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (LoadUnlockRetriesContext);
|
|
ctx->retries = mm_unlock_retries_new ();
|
|
ctx->i = 0;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free);
|
|
|
|
load_unlock_retries_context_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Supported modes loading (Modem interface) */
|
|
|
|
typedef struct {
|
|
MMModemMode mode;
|
|
gboolean run_cnti;
|
|
gboolean run_ws46;
|
|
gboolean run_gcap;
|
|
} LoadSupportedModesContext;
|
|
|
|
static GArray *
|
|
modem_load_supported_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
GArray *modes;
|
|
MMModemModeCombination mode;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return NULL;
|
|
}
|
|
|
|
/* Build a mask with all supported modes */
|
|
modes = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
|
|
mode.allowed = (MMModemMode) value;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
g_array_append_val (modes, mode);
|
|
|
|
return modes;
|
|
}
|
|
|
|
static void load_supported_modes_step (GTask *task);
|
|
|
|
static void
|
|
supported_modes_gcap_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
LoadSupportedModesContext *ctx;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mm_base_modem_at_command_finish (_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 (!self->priv->modem_cdma_evdo_network_supported) {
|
|
self->priv->modem_cdma_evdo_network_supported = TRUE;
|
|
g_object_notify (G_OBJECT (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 (!self->priv->modem_cdma_cdma1x_network_supported) {
|
|
self->priv->modem_cdma_cdma1x_network_supported = TRUE;
|
|
g_object_notify (G_OBJECT (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 (self->priv->modem_cdma_cdma1x_network_supported) {
|
|
mm_dbg ("Assuming device allows (CDMA) 2G network mode");
|
|
ctx->mode |= MM_MODEM_MODE_2G;
|
|
}
|
|
if (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 (task);
|
|
}
|
|
|
|
static void
|
|
supported_modes_ws46_test_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadSupportedModesContext *ctx;
|
|
const gchar *response;
|
|
GArray *modes;
|
|
GError *error = NULL;
|
|
guint i;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
mm_dbg ("Generic query of supported 3GPP networks with WS46=? failed: '%s'", error->message);
|
|
g_error_free (error);
|
|
goto out;
|
|
}
|
|
|
|
modes = mm_3gpp_parse_ws46_test_response (response, &error);
|
|
if (!modes) {
|
|
mm_dbg ("Parsing WS46=? response failed: '%s'", error->message);
|
|
g_error_free (error);
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < modes->len; i++) {
|
|
MMModemMode mode;
|
|
gchar *str;
|
|
|
|
mode = g_array_index (modes, MMModemMode, i);
|
|
|
|
ctx->mode |= mode;
|
|
|
|
str = mm_modem_mode_build_string_from_mask (mode);
|
|
mm_dbg ("Device allows (3GPP) mode combination: %s", str);
|
|
g_free (str);
|
|
}
|
|
|
|
g_array_unref (modes);
|
|
|
|
out:
|
|
/* Now keep on with the loading, we may need CDMA-specific checks */
|
|
ctx->run_ws46 = FALSE;
|
|
load_supported_modes_step (task);
|
|
}
|
|
|
|
static void
|
|
supported_modes_cnti_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadSupportedModesContext *ctx;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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 (task);
|
|
}
|
|
|
|
static void
|
|
load_supported_modes_step (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
LoadSupportedModesContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->run_cnti) {
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"*CNTI=2",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)supported_modes_cnti_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (ctx->run_ws46) {
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+WS46=?",
|
|
3,
|
|
TRUE, /* allow caching, it's a test command */
|
|
(GAsyncReadyCallback)supported_modes_ws46_test_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (ctx->run_gcap) {
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+GCAP",
|
|
3,
|
|
TRUE, /* allow caching */
|
|
(GAsyncReadyCallback)supported_modes_gcap_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* All done.
|
|
* If no mode found, error */
|
|
if (ctx->mode == MM_MODEM_MODE_NONE)
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't retrieve supported modes");
|
|
else
|
|
g_task_return_int (task, ctx->mode);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_load_supported_modes (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
LoadSupportedModesContext *ctx;
|
|
GTask *task;
|
|
|
|
mm_dbg ("loading supported modes...");
|
|
ctx = g_new0 (LoadSupportedModesContext, 1);
|
|
ctx->mode = MM_MODEM_MODE_NONE;
|
|
|
|
if (mm_iface_modem_is_3gpp (self)) {
|
|
/* Run +WS46=? and *CNTI=2 */
|
|
ctx->run_ws46 = TRUE;
|
|
ctx->run_cnti = TRUE;
|
|
}
|
|
|
|
if (mm_iface_modem_is_cdma (self)) {
|
|
/* Run +GCAP in order to know if the modem is CDMA1x only or CDMA1x/EV-DO */
|
|
ctx->run_gcap = TRUE;
|
|
}
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
load_supported_modes_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Supported IP families loading (Modem interface) */
|
|
|
|
static MMBearerIpFamily
|
|
modem_load_supported_ip_families_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_BEARER_IP_FAMILY_NONE;
|
|
}
|
|
return (MMBearerIpFamily)value;
|
|
}
|
|
|
|
static void
|
|
supported_ip_families_cgdcont_test_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
else
|
|
g_task_return_int (task, mask);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_load_supported_ip_families (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
mm_dbg ("loading supported IP families...");
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (mm_iface_modem_is_cdma_only (self)) {
|
|
g_task_return_int (task, MM_BEARER_IP_FAMILY_IPV4);
|
|
g_object_unref (task);
|
|
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,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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 {
|
|
MMPortSerial *at_port;
|
|
MMPortSerial *qcdm_port;
|
|
} SignalQualityContext;
|
|
|
|
static void
|
|
signal_quality_context_free (SignalQualityContext *ctx)
|
|
{
|
|
g_clear_object (&ctx->at_port);
|
|
if (ctx->qcdm_port) {
|
|
mm_port_serial_close (ctx->qcdm_port);
|
|
g_object_unref (ctx->qcdm_port);
|
|
}
|
|
g_free (ctx);
|
|
}
|
|
|
|
static guint
|
|
modem_load_signal_quality_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), error);
|
|
return value < 0 ? 0 : value;
|
|
}
|
|
|
|
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,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_int (task, quality);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse signal quality results");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/* 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, FALSE, response_processor_string_ignore_at_errors },
|
|
{ "+CSQ?", 3, FALSE, response_processor_string_ignore_at_errors },
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
signal_quality_csq (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
SignalQualityContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
mm_base_modem_at_sequence_full (
|
|
MM_BASE_MODEM (self),
|
|
MM_PORT_SERIAL_AT (ctx->at_port),
|
|
signal_quality_csq_sequence,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)signal_quality_csq_ready,
|
|
task);
|
|
}
|
|
|
|
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,
|
|
GTask *task)
|
|
{
|
|
SignalQualityContext *ctx;
|
|
GError *error = NULL;
|
|
const gchar *result;
|
|
GByteArray *indicators;
|
|
guint quality = 0;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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->at_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->at_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_task_return_int (task, quality);
|
|
g_object_unref (task);
|
|
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 (task);
|
|
}
|
|
|
|
static void
|
|
signal_quality_cind (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
SignalQualityContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
MM_PORT_SERIAL_AT (ctx->at_port),
|
|
"+CIND?",
|
|
5,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)signal_quality_cind_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
signal_quality_qcdm_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse pilot sets command result: %d",
|
|
err);
|
|
g_object_unref (task);
|
|
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_task_return_int (task, quality);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
signal_quality_qcdm (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
SignalQualityContext *ctx;
|
|
GByteArray *pilot_sets;
|
|
guint quality;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* If EVDO is active try that signal strength first */
|
|
quality = signal_quality_evdo_pilot_sets (self);
|
|
if (quality > 0) {
|
|
g_task_return_int (task, quality);
|
|
g_object_unref (task);
|
|
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->qcdm_port),
|
|
pilot_sets,
|
|
3,
|
|
NULL,
|
|
(GAsyncReadyCallback)signal_quality_qcdm_ready,
|
|
task);
|
|
g_byte_array_unref (pilot_sets);
|
|
}
|
|
|
|
static void
|
|
modem_load_signal_quality (MMIfaceModem *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
SignalQualityContext *ctx;
|
|
GError *error = NULL;
|
|
GTask *task;
|
|
|
|
mm_dbg ("loading signal quality...");
|
|
ctx = g_new0 (SignalQualityContext, 1);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)signal_quality_context_free);
|
|
|
|
/* Check whether we can get a non-connected AT port */
|
|
ctx->at_port = (MMPortSerial *)mm_base_modem_get_best_at_port (MM_BASE_MODEM (self), &error);
|
|
if (ctx->at_port) {
|
|
if (!self->priv->modem_cind_disabled &&
|
|
self->priv->modem_cind_supported &&
|
|
CIND_INDICATOR_IS_VALID (self->priv->modem_cind_indicator_signal_quality))
|
|
signal_quality_cind (task);
|
|
else
|
|
signal_quality_csq (task);
|
|
return;
|
|
}
|
|
|
|
/* If no best AT port available (all connected), try with QCDM ports */
|
|
ctx->qcdm_port = (MMPortSerial *)mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
|
|
if (ctx->qcdm_port) {
|
|
g_clear_error (&error);
|
|
|
|
/* Need to open QCDM port as it may be closed/blocked */
|
|
if (mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm_port), &error)) {
|
|
g_object_ref (ctx->qcdm_port);
|
|
signal_quality_qcdm (task);
|
|
return;
|
|
}
|
|
|
|
ctx->qcdm_port = NULL;
|
|
mm_dbg ("Couldn't open QCDM port: %s", error->message);
|
|
}
|
|
|
|
/* Return the error we got when getting best AT port */
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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;
|
|
|
|
tech = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!tech)
|
|
return FALSE;
|
|
|
|
*access_technologies = tech->access_technologies;
|
|
*mask = tech->mask;
|
|
g_free (tech);
|
|
return TRUE;
|
|
}
|
|
|
|
typedef struct {
|
|
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_free (AccessTechContext *ctx)
|
|
{
|
|
if (ctx->port) {
|
|
mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
|
|
g_object_unref (ctx->port);
|
|
}
|
|
g_free (ctx);
|
|
}
|
|
|
|
static AccessTechAndMask *
|
|
access_tech_and_mask_new (AccessTechContext *ctx)
|
|
{
|
|
AccessTechAndMask *tech;
|
|
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
guint mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
|
|
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_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_WCDMA:
|
|
if (ctx->wcdma_open) {
|
|
/* Assume UMTS; can't yet determine UMTS/HSxPA/HSPA+ with QCDM */
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
|
|
}
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
tech = g_new0 (AccessTechAndMask, 1);
|
|
tech->access_technologies = act;
|
|
tech->mask = mask;
|
|
return tech;
|
|
}
|
|
|
|
static void
|
|
access_tech_qcdm_wcdma_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* 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 ||
|
|
l1 == QCDM_WCDMA_L1_STATE_PCH_SLEEP)
|
|
ctx->wcdma_open = TRUE;
|
|
}
|
|
|
|
g_task_return_pointer (task, access_tech_and_mask_new (ctx), g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
access_tech_qcdm_gsm_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse GSM subsys command result: %d",
|
|
err);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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,
|
|
task);
|
|
g_byte_array_unref (cmd);
|
|
}
|
|
|
|
static void
|
|
access_tech_qcdm_hdr_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
g_task_return_pointer (task, access_tech_and_mask_new (ctx), g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
access_tech_qcdm_cdma_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse CM subsys command result: %d",
|
|
err);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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,
|
|
task);
|
|
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;
|
|
GTask *task;
|
|
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->port = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)access_tech_context_free);
|
|
|
|
if (ctx->port) {
|
|
/* Need to open QCDM port as it may be closed/blocked */
|
|
if (mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error)) {
|
|
g_object_ref (ctx->port);
|
|
|
|
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,
|
|
task);
|
|
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,
|
|
task);
|
|
g_byte_array_unref (cmd);
|
|
return;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
ctx->port = NULL;
|
|
mm_dbg ("Couldn't open QCDM port: %s", error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
/* Fall back if we don't have a QCDM port or it couldn't be opened */
|
|
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);
|
|
g_task_return_pointer (task, access_tech_and_mask_new (ctx), g_free);
|
|
} else {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot get 3GPP access technology without a QCDM port");
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup/Cleanup unsolicited events (3GPP interface) */
|
|
|
|
static gboolean
|
|
modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
bearer_report_disconnected (MMBaseBearer *bearer,
|
|
gpointer user_data)
|
|
{
|
|
guint cid;
|
|
|
|
cid = GPOINTER_TO_UINT (user_data);
|
|
|
|
/* If we're told to disconnect a single context and this is not the
|
|
* bearer associated to that context, ignore operation */
|
|
if (cid > 0 &&
|
|
MM_IS_BROADBAND_BEARER (bearer) &&
|
|
mm_broadband_bearer_get_3gpp_cid (MM_BROADBAND_BEARER (bearer)) != cid)
|
|
return;
|
|
|
|
/* If already disconnected, ignore operation */
|
|
if (mm_base_bearer_get_status (bearer) == MM_BEARER_STATUS_DISCONNECTED)
|
|
return;
|
|
|
|
mm_info ("Bearer %s: explicitly disconnected", mm_base_bearer_get_path (bearer));
|
|
mm_base_bearer_report_connection_status (bearer, MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
|
|
}
|
|
|
|
static void
|
|
bearer_list_report_disconnections (MMBroadbandModem *self,
|
|
guint cid)
|
|
{
|
|
MMBearerList *list = NULL;
|
|
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_BEARER_LIST, &list,
|
|
NULL);
|
|
|
|
/* If empty bearer list, nothing else to do */
|
|
if (!list)
|
|
return;
|
|
|
|
mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_report_disconnected, GUINT_TO_POINTER (cid));
|
|
g_object_unref (list);
|
|
}
|
|
|
|
static void
|
|
cgev_process_detach (MMBroadbandModem *self,
|
|
MM3gppCgev type)
|
|
{
|
|
if (type == MM_3GPP_CGEV_NW_DETACH) {
|
|
mm_info ("network forced PS detach: all contexts have been deactivated");
|
|
bearer_list_report_disconnections (self, 0);
|
|
return;
|
|
}
|
|
|
|
if (type == MM_3GPP_CGEV_ME_DETACH) {
|
|
mm_info ("mobile equipment forced PS detach: all contexts have been deactivated");
|
|
bearer_list_report_disconnections (self, 0);
|
|
return;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
cgev_process_primary (MMBroadbandModem *self,
|
|
MM3gppCgev type,
|
|
const gchar *str)
|
|
{
|
|
GError *error = NULL;
|
|
guint cid = 0;
|
|
|
|
if (!mm_3gpp_parse_cgev_indication_primary (str, type, &cid, &error)) {
|
|
mm_warn ("couldn't parse cid info from +CGEV indication '%s': %s", str, error->message);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case MM_3GPP_CGEV_NW_ACT_PRIMARY:
|
|
mm_info ("network request to activate context (cid %u)", cid);
|
|
break;
|
|
case MM_3GPP_CGEV_ME_ACT_PRIMARY:
|
|
mm_info ("mobile equipment request to activate context (cid %u)", cid);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_DEACT_PRIMARY:
|
|
mm_info ("network request to deactivate context (cid %u)", cid);
|
|
bearer_list_report_disconnections (self, cid);
|
|
break;
|
|
case MM_3GPP_CGEV_ME_DEACT_PRIMARY:
|
|
mm_info ("mobile equipment request to deactivate context (cid %u)", cid);
|
|
bearer_list_report_disconnections (self, cid);
|
|
break;
|
|
case MM_3GPP_CGEV_UNKNOWN:
|
|
case MM_3GPP_CGEV_NW_DETACH:
|
|
case MM_3GPP_CGEV_ME_DETACH:
|
|
case MM_3GPP_CGEV_NW_CLASS:
|
|
case MM_3GPP_CGEV_ME_CLASS:
|
|
case MM_3GPP_CGEV_NW_ACT_SECONDARY:
|
|
case MM_3GPP_CGEV_ME_ACT_SECONDARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_SECONDARY:
|
|
case MM_3GPP_CGEV_ME_DEACT_SECONDARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_PDP:
|
|
case MM_3GPP_CGEV_ME_DEACT_PDP:
|
|
case MM_3GPP_CGEV_NW_MODIFY:
|
|
case MM_3GPP_CGEV_ME_MODIFY:
|
|
case MM_3GPP_CGEV_REJECT:
|
|
case MM_3GPP_CGEV_NW_REACT:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
cgev_process_secondary (MMBroadbandModem *self,
|
|
MM3gppCgev type,
|
|
const gchar *str)
|
|
{
|
|
GError *error = NULL;
|
|
guint p_cid = 0;
|
|
guint cid = 0;
|
|
|
|
if (!mm_3gpp_parse_cgev_indication_secondary (str, type, &p_cid, &cid, NULL, &error)) {
|
|
mm_warn ("couldn't parse p_cid/cid info from +CGEV indication '%s': %s", str, error->message);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case MM_3GPP_CGEV_NW_ACT_SECONDARY:
|
|
mm_info ("network request to activate secondary context (cid %u, primary cid %u)", cid, p_cid);
|
|
break;
|
|
case MM_3GPP_CGEV_ME_ACT_SECONDARY:
|
|
mm_info ("mobile equipment request to activate secondary context (cid %u, primary cid %u)", cid, p_cid);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_DEACT_SECONDARY:
|
|
mm_info ("network request to deactivate secondary context (cid %u, primary cid %u)", cid, p_cid);
|
|
bearer_list_report_disconnections (self, cid);
|
|
break;
|
|
case MM_3GPP_CGEV_ME_DEACT_SECONDARY:
|
|
mm_info ("mobile equipment request to deactivate secondary context (cid %u, primary cid %u)", cid, p_cid);
|
|
bearer_list_report_disconnections (self, cid);
|
|
break;
|
|
case MM_3GPP_CGEV_UNKNOWN:
|
|
case MM_3GPP_CGEV_NW_DETACH:
|
|
case MM_3GPP_CGEV_ME_DETACH:
|
|
case MM_3GPP_CGEV_NW_CLASS:
|
|
case MM_3GPP_CGEV_ME_CLASS:
|
|
case MM_3GPP_CGEV_NW_ACT_PRIMARY:
|
|
case MM_3GPP_CGEV_ME_ACT_PRIMARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_PRIMARY:
|
|
case MM_3GPP_CGEV_ME_DEACT_PRIMARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_PDP:
|
|
case MM_3GPP_CGEV_ME_DEACT_PDP:
|
|
case MM_3GPP_CGEV_NW_MODIFY:
|
|
case MM_3GPP_CGEV_ME_MODIFY:
|
|
case MM_3GPP_CGEV_REJECT:
|
|
case MM_3GPP_CGEV_NW_REACT:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
cgev_process_pdp (MMBroadbandModem *self,
|
|
MM3gppCgev type,
|
|
const gchar *str)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *pdp_type = NULL;
|
|
gchar *pdp_addr = NULL;
|
|
guint cid = 0;
|
|
|
|
if (!mm_3gpp_parse_cgev_indication_pdp (str, type, &pdp_type, &pdp_addr, &cid, &error)) {
|
|
mm_warn ("couldn't parse PDP info from +CGEV indication '%s': %s", str, error->message);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case MM_3GPP_CGEV_REJECT:
|
|
mm_info ("network request to activate context (type %s, address %s) has been automatically rejected", pdp_type, pdp_addr);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_REACT:
|
|
/* NOTE: we don't currently notify about automatic reconnections like this one */
|
|
if (cid)
|
|
mm_info ("network request to reactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
|
|
else
|
|
mm_info ("network request to reactivate context (type %s, address %s, cid unknown)", pdp_type, pdp_addr);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_DEACT_PDP:
|
|
if (cid) {
|
|
mm_info ("network request to deactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
|
|
bearer_list_report_disconnections (self, cid);
|
|
} else
|
|
mm_info ("network request to deactivate context (type %s, address %s, cid unknown)", pdp_type, pdp_addr);
|
|
break;
|
|
case MM_3GPP_CGEV_ME_DEACT_PDP:
|
|
if (cid) {
|
|
mm_info ("mobile equipment request to deactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
|
|
bearer_list_report_disconnections (self, cid);
|
|
} else
|
|
mm_info ("mobile equipment request to deactivate context (type %s, address %s, cid unknown)", pdp_type, pdp_addr);
|
|
break;
|
|
case MM_3GPP_CGEV_UNKNOWN:
|
|
case MM_3GPP_CGEV_NW_DETACH:
|
|
case MM_3GPP_CGEV_ME_DETACH:
|
|
case MM_3GPP_CGEV_NW_CLASS:
|
|
case MM_3GPP_CGEV_ME_CLASS:
|
|
case MM_3GPP_CGEV_NW_ACT_PRIMARY:
|
|
case MM_3GPP_CGEV_ME_ACT_PRIMARY:
|
|
case MM_3GPP_CGEV_NW_ACT_SECONDARY:
|
|
case MM_3GPP_CGEV_ME_ACT_SECONDARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_PRIMARY:
|
|
case MM_3GPP_CGEV_ME_DEACT_PRIMARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_SECONDARY:
|
|
case MM_3GPP_CGEV_ME_DEACT_SECONDARY:
|
|
case MM_3GPP_CGEV_NW_MODIFY:
|
|
case MM_3GPP_CGEV_ME_MODIFY:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
g_free (pdp_addr);
|
|
g_free (pdp_type);
|
|
}
|
|
|
|
static void
|
|
cgev_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
gchar *str;
|
|
MM3gppCgev type;
|
|
|
|
str = mm_get_string_unquoted_from_match_info (info, 1);
|
|
if (!str)
|
|
return;
|
|
|
|
type = mm_3gpp_parse_cgev_indication_action (str);
|
|
|
|
switch (type) {
|
|
case MM_3GPP_CGEV_NW_DETACH:
|
|
case MM_3GPP_CGEV_ME_DETACH:
|
|
cgev_process_detach (self, type);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_ACT_PRIMARY:
|
|
case MM_3GPP_CGEV_ME_ACT_PRIMARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_PRIMARY:
|
|
case MM_3GPP_CGEV_ME_DEACT_PRIMARY:
|
|
cgev_process_primary (self, type, str);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_ACT_SECONDARY:
|
|
case MM_3GPP_CGEV_ME_ACT_SECONDARY:
|
|
case MM_3GPP_CGEV_NW_DEACT_SECONDARY:
|
|
case MM_3GPP_CGEV_ME_DEACT_SECONDARY:
|
|
cgev_process_secondary (self, type, str);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_DEACT_PDP:
|
|
case MM_3GPP_CGEV_ME_DEACT_PDP:
|
|
case MM_3GPP_CGEV_REJECT:
|
|
case MM_3GPP_CGEV_NW_REACT:
|
|
cgev_process_pdp (self, type, str);
|
|
break;
|
|
case MM_3GPP_CGEV_NW_CLASS:
|
|
case MM_3GPP_CGEV_ME_CLASS:
|
|
case MM_3GPP_CGEV_NW_MODIFY:
|
|
case MM_3GPP_CGEV_ME_MODIFY:
|
|
/* ignore */
|
|
break;
|
|
case MM_3GPP_CGEV_UNKNOWN:
|
|
default:
|
|
mm_dbg ("unhandled +CGEV indication: %s", str);
|
|
break;
|
|
}
|
|
|
|
g_free (str);
|
|
}
|
|
|
|
static void
|
|
set_cgev_unsolicited_events_handlers (MMBroadbandModem *self,
|
|
gboolean enable)
|
|
{
|
|
MMPortSerialAt *ports[2];
|
|
GRegex *cgev_regex;
|
|
guint i;
|
|
|
|
cgev_regex = mm_3gpp_cgev_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 CGEV event handler */
|
|
mm_dbg ("(%s) %s 3GPP +CGEV unsolicited events handlers",
|
|
mm_port_get_device (MM_PORT (ports[i])),
|
|
enable ? "Setting" : "Removing");
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ports[i],
|
|
cgev_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn) cgev_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
}
|
|
|
|
g_regex_unref (cgev_regex);
|
|
}
|
|
|
|
static void
|
|
ciev_signal_received (MMBroadbandModem *self,
|
|
GMatchInfo *match_info)
|
|
{
|
|
guint quality;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 2, &quality)) {
|
|
mm_dbg ("Couldn't parse signal quality value from +CIEV");
|
|
return;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
static void
|
|
ciev_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
guint ind;
|
|
gchar *item;
|
|
|
|
item = mm_get_string_unquoted_from_match_info (match_info, 1);
|
|
if (!item)
|
|
return;
|
|
|
|
/* numeric index? */
|
|
if (mm_get_uint_from_str (item, &ind)) {
|
|
if (ind == self->priv->modem_cind_indicator_signal_quality)
|
|
ciev_signal_received (self, match_info);
|
|
}
|
|
/* string index? */
|
|
else {
|
|
if (g_str_equal (item, "signal"))
|
|
ciev_signal_received (self, match_info);
|
|
}
|
|
|
|
g_free (item);
|
|
}
|
|
|
|
static void
|
|
set_ciev_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 +CIEV 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
|
|
support_checked_setup_unsolicited_events (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
if (self->priv->modem_cind_supported)
|
|
set_ciev_unsolicited_events_handlers (self, TRUE);
|
|
|
|
if (self->priv->modem_cgerep_supported)
|
|
set_cgev_unsolicited_events_handlers (self, TRUE);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void check_and_setup_3gpp_urc_support (GTask *task);
|
|
|
|
static void
|
|
cgerep_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
const gchar *result;
|
|
|
|
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (!result) {
|
|
mm_dbg ("+CGEREP check failed, marking packet domain event reporting as unsupported: '%s'", error->message);
|
|
g_error_free (error);
|
|
goto out;
|
|
}
|
|
|
|
mm_dbg ("Modem supports packet domain event reporting");
|
|
self->priv->modem_cgerep_supported = TRUE;
|
|
|
|
out:
|
|
/* go on with remaining checks */
|
|
check_and_setup_3gpp_urc_support (task);
|
|
}
|
|
|
|
static void
|
|
cmer_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MM3gppCmerMode supported_modes = MM_3GPP_CMER_MODE_NONE;
|
|
MM3gppCmerInd supported_inds = MM_3GPP_CMER_IND_NONE;
|
|
GError *error = NULL;
|
|
const gchar *result;
|
|
gchar *aux;
|
|
|
|
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error || !mm_3gpp_parse_cmer_test_response (result, &supported_modes, &supported_inds, &error)) {
|
|
mm_dbg ("+CMER check failed, marking indications as unsupported: '%s'", error->message);
|
|
g_error_free (error);
|
|
goto out;
|
|
}
|
|
|
|
aux = mm_3gpp_cmer_mode_build_string_from_mask (supported_modes);
|
|
mm_dbg ("Supported +CMER modes: %s", aux);
|
|
g_free (aux);
|
|
|
|
aux = mm_3gpp_cmer_ind_build_string_from_mask (supported_inds);
|
|
mm_dbg ("Supported +CMER indication settings: %s", aux);
|
|
g_free (aux);
|
|
|
|
/* Flag +CMER supported values */
|
|
|
|
if (supported_modes & MM_3GPP_CMER_MODE_FORWARD_URCS)
|
|
self->priv->modem_cmer_enable_mode = MM_3GPP_CMER_MODE_FORWARD_URCS;
|
|
else if (supported_modes & MM_3GPP_CMER_MODE_BUFFER_URCS_IF_LINK_RESERVED)
|
|
self->priv->modem_cmer_enable_mode = MM_3GPP_CMER_MODE_BUFFER_URCS_IF_LINK_RESERVED;
|
|
else if (supported_modes & MM_3GPP_CMER_MODE_DISCARD_URCS_IF_LINK_RESERVED)
|
|
self->priv->modem_cmer_enable_mode = MM_3GPP_CMER_MODE_DISCARD_URCS_IF_LINK_RESERVED;
|
|
|
|
aux = mm_3gpp_cmer_mode_build_string_from_mask (self->priv->modem_cmer_enable_mode);
|
|
mm_dbg ("+CMER enable mode: %s", aux);
|
|
g_free (aux);
|
|
|
|
if (supported_modes & MM_3GPP_CMER_MODE_DISCARD_URCS)
|
|
self->priv->modem_cmer_disable_mode = MM_3GPP_CMER_MODE_DISCARD_URCS;
|
|
|
|
aux = mm_3gpp_cmer_mode_build_string_from_mask (self->priv->modem_cmer_disable_mode);
|
|
mm_dbg ("+CMER disable mode: %s", aux);
|
|
g_free (aux);
|
|
|
|
if (supported_inds & MM_3GPP_CMER_IND_ENABLE_NOT_CAUSED_BY_CIND)
|
|
self->priv->modem_cmer_ind = MM_3GPP_CMER_IND_ENABLE_NOT_CAUSED_BY_CIND;
|
|
else if (supported_inds & MM_3GPP_CMER_IND_ENABLE_ALL)
|
|
self->priv->modem_cmer_ind = MM_3GPP_CMER_IND_ENABLE_ALL;
|
|
|
|
aux = mm_3gpp_cmer_ind_build_string_from_mask (self->priv->modem_cmer_ind);
|
|
mm_dbg ("+CMER indication setting: %s", aux);
|
|
g_free (aux);
|
|
|
|
out:
|
|
/* go on with remaining checks */
|
|
check_and_setup_3gpp_urc_support (task);
|
|
}
|
|
|
|
static void
|
|
cind_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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 ("+CIND check failed, marking indications as unsupported: '%s'", error->message);
|
|
g_error_free (error);
|
|
/* go on with remaining checks */
|
|
check_and_setup_3gpp_urc_support (task);
|
|
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);
|
|
|
|
/* Check +CMER required format */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CMER=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cmer_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
check_and_setup_3gpp_urc_support (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
/* Check support for +CIEV indications, managed with +CIND/+CMER */
|
|
if (!self->priv->modem_cind_disabled && !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,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Check support for +CGEV indications, managed with +CGEREP */
|
|
if (!self->priv->modem_cgerep_support_checked) {
|
|
mm_dbg ("Checking packet domain event reporting...");
|
|
self->priv->modem_cgerep_support_checked = TRUE;
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CGEREP=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cgerep_format_check_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
support_checked_setup_unsolicited_events (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
check_and_setup_3gpp_urc_support (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (!self->priv->modem_cind_disabled && self->priv->modem_cind_support_checked && self->priv->modem_cind_supported)
|
|
set_ciev_unsolicited_events_handlers (self, FALSE);
|
|
|
|
if (self->priv->modem_cgerep_supported)
|
|
set_cgev_unsolicited_events_handlers (self, FALSE);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enabling/disabling unsolicited events (3GPP interface) */
|
|
|
|
typedef struct {
|
|
gboolean enable;
|
|
MMPortSerialAt *primary;
|
|
MMPortSerialAt *secondary;
|
|
gchar *cmer_command;
|
|
gboolean cmer_primary_done;
|
|
gboolean cmer_secondary_done;
|
|
gchar *cgerep_command;
|
|
gboolean cgerep_primary_done;
|
|
gboolean cgerep_secondary_done;
|
|
} UnsolicitedEventsContext;
|
|
|
|
static void
|
|
unsolicited_events_context_free (UnsolicitedEventsContext *ctx)
|
|
{
|
|
if (ctx->secondary)
|
|
g_object_unref (ctx->secondary);
|
|
if (ctx->primary)
|
|
g_object_unref (ctx->primary);
|
|
g_free (ctx->cgerep_command);
|
|
g_free (ctx->cmer_command);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
modem_3gpp_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void run_unsolicited_events_setup (GTask *task);
|
|
|
|
static void
|
|
unsolicited_events_setup_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
UnsolicitedEventsContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
|
|
mm_dbg ("Couldn't %s event reporting: '%s'",
|
|
ctx->enable ? "enable" : "disable",
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
/* Continue on next port/command */
|
|
run_unsolicited_events_setup (task);
|
|
}
|
|
|
|
static void
|
|
run_unsolicited_events_setup (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
UnsolicitedEventsContext *ctx;
|
|
MMPortSerialAt *port = NULL;
|
|
const gchar *command = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* CMER on primary port */
|
|
if (!ctx->cmer_primary_done && ctx->cmer_command && ctx->primary && !self->priv->modem_cind_disabled) {
|
|
mm_dbg ("%s +CIND event reporting in primary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->cmer_primary_done = TRUE;
|
|
command = ctx->cmer_command;
|
|
port = ctx->primary;
|
|
}
|
|
/* CMER on secondary port */
|
|
else if (!ctx->cmer_secondary_done && ctx->cmer_command && ctx->secondary && !self->priv->modem_cind_disabled) {
|
|
mm_dbg ("%s +CIND event reporting in secondary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->cmer_secondary_done = TRUE;
|
|
command = ctx->cmer_command;
|
|
port = ctx->secondary;
|
|
}
|
|
/* CGEREP on primary port */
|
|
else if (!ctx->cgerep_primary_done && ctx->cgerep_command && ctx->primary) {
|
|
mm_dbg ("%s +CGEV event reporting in primary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->cgerep_primary_done = TRUE;
|
|
command = ctx->cgerep_command;
|
|
port = ctx->primary;
|
|
}
|
|
/* CGEREP on secondary port */
|
|
else if (!ctx->cgerep_secondary_done && ctx->cgerep_command && ctx->secondary) {
|
|
mm_dbg ("%s +CGEV event reporting in secondary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->cgerep_secondary_done = TRUE;
|
|
port = ctx->secondary;
|
|
command = ctx->cgerep_command;
|
|
}
|
|
|
|
/* Enable unsolicited events in given port */
|
|
if (port && command) {
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
port,
|
|
command,
|
|
3,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)unsolicited_events_setup_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Fully done now */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
GTask *task;
|
|
UnsolicitedEventsContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_new0 (UnsolicitedEventsContext, 1);
|
|
ctx->enable = TRUE;
|
|
ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
|
|
ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)unsolicited_events_context_free);
|
|
|
|
if (self->priv->modem_cind_support_checked && self->priv->modem_cind_supported)
|
|
ctx->cmer_command = mm_3gpp_build_cmer_set_request (self->priv->modem_cmer_enable_mode, self->priv->modem_cmer_ind);
|
|
|
|
if (self->priv->modem_cgerep_support_checked && self->priv->modem_cgerep_supported)
|
|
ctx->cgerep_command = g_strdup ("+CGEREP=2");
|
|
|
|
run_unsolicited_events_setup (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
GTask *task;
|
|
UnsolicitedEventsContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_new0 (UnsolicitedEventsContext, 1);
|
|
ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
|
|
ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)unsolicited_events_context_free);
|
|
|
|
if (self->priv->modem_cind_support_checked && self->priv->modem_cind_supported)
|
|
ctx->cmer_command = mm_3gpp_build_cmer_set_request (self->priv->modem_cmer_disable_mode, MM_3GPP_CMER_IND_NONE);
|
|
|
|
if (self->priv->modem_cgerep_support_checked && self->priv->modem_cgerep_supported)
|
|
ctx->cgerep_command = g_strdup ("+CGEREP=0");
|
|
|
|
run_unsolicited_events_setup (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setting modem charset (Modem interface) */
|
|
|
|
typedef struct {
|
|
MMModemCharset charset;
|
|
/* Commands to try in the sequence:
|
|
* First one with quotes
|
|
* Second without.
|
|
* + last NUL */
|
|
MMBaseModemAtCommandAlloc charset_commands[3];
|
|
} SetupCharsetContext;
|
|
|
|
static void
|
|
setup_charset_context_free (SetupCharsetContext *ctx)
|
|
{
|
|
mm_base_modem_at_command_alloc_clear (&ctx->charset_commands[0]);
|
|
mm_base_modem_at_command_alloc_clear (&ctx->charset_commands[1]);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
modem_setup_charset_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
current_charset_query_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetupCharsetContext *ctx;
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (!response)
|
|
g_task_return_error (task, 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_task_return_new_error (task,
|
|
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_task_return_boolean (task, TRUE);
|
|
}
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
charset_change_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_setup_charset (MMIfaceModem *self,
|
|
MMModemCharset charset,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SetupCharsetContext *ctx;
|
|
const gchar *charset_str;
|
|
GTask *task;
|
|
|
|
/* 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_task_report_new_error (self,
|
|
callback,
|
|
user_data,
|
|
modem_setup_charset,
|
|
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->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;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)setup_charset_context_free);
|
|
|
|
/* Launch sequence */
|
|
mm_base_modem_at_sequence (
|
|
MM_BASE_MODEM (self),
|
|
(const MMBaseModemAtCommand *)ctx->charset_commands,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
(GAsyncReadyCallback)charset_change_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Loading supported charsets (Modem interface) */
|
|
|
|
static MMModemCharset
|
|
modem_load_supported_charsets_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_CHARSET_UNKNOWN;
|
|
}
|
|
return (MMModemCharset)value;
|
|
}
|
|
|
|
static void
|
|
cscs_format_check_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else if (!mm_3gpp_parse_cscs_test_response (response, &charsets))
|
|
g_task_return_new_error (
|
|
task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse the supported character sets response");
|
|
else
|
|
g_task_return_int (task, charsets);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_load_supported_charsets (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* 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_task_return_int (task, MM_MODEM_CHARSET_UNKNOWN);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CSCS=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cscs_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* configuring flow control (Modem interface) */
|
|
|
|
static gboolean
|
|
modem_setup_flow_control_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
ifc_test_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
const gchar *cmd;
|
|
MMFlowControl flow_control_supported;
|
|
MMFlowControl flow_control_selected = MM_FLOW_CONTROL_UNKNOWN;
|
|
MMFlowControl flow_control_requested;
|
|
gchar *flow_control_supported_str = NULL;
|
|
gchar *flow_control_selected_str = NULL;
|
|
MMPortSerialAt *port;
|
|
|
|
self = MM_BROADBAND_MODEM (_self);
|
|
|
|
/* Completely ignore errors in AT+IFC=? */
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response)
|
|
goto out;
|
|
|
|
/* Parse response */
|
|
flow_control_supported = mm_parse_ifc_test_response (response, &error);
|
|
if (flow_control_supported == MM_FLOW_CONTROL_UNKNOWN)
|
|
goto out;
|
|
flow_control_supported_str = mm_flow_control_build_string_from_mask (flow_control_supported);
|
|
|
|
port = mm_base_modem_peek_best_at_port (_self, &error);
|
|
if (!port)
|
|
goto out;
|
|
|
|
flow_control_requested = mm_port_serial_get_flow_control (MM_PORT_SERIAL (port));
|
|
if (flow_control_requested != MM_FLOW_CONTROL_UNKNOWN) {
|
|
gchar *flow_control_requested_str;
|
|
|
|
flow_control_requested_str = mm_flow_control_build_string_from_mask (flow_control_requested);
|
|
|
|
/* If flow control settings requested via udev tag are not supported by
|
|
* the modem, we trigger a fatal error */
|
|
if (!(flow_control_supported & flow_control_requested)) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Explicitly requested flow control settings (%s) are not supported by the device (%s)",
|
|
flow_control_requested_str, flow_control_supported_str);
|
|
g_object_unref (task);
|
|
g_free (flow_control_requested_str);
|
|
g_free (flow_control_supported_str);
|
|
return;
|
|
}
|
|
|
|
mm_dbg ("Flow control settings explicitly requested (%s)", flow_control_requested_str);
|
|
flow_control_selected = flow_control_requested;
|
|
flow_control_selected_str = flow_control_requested_str;
|
|
} else {
|
|
/* If flow control is not set explicitly by udev tags,
|
|
* we prefer the methods in this order:
|
|
* RTS/CTS
|
|
* XON/XOFF
|
|
* None.
|
|
*/
|
|
if (flow_control_supported & MM_FLOW_CONTROL_RTS_CTS)
|
|
flow_control_selected = MM_FLOW_CONTROL_RTS_CTS;
|
|
else if (flow_control_supported & MM_FLOW_CONTROL_XON_XOFF)
|
|
flow_control_selected = MM_FLOW_CONTROL_XON_XOFF;
|
|
else if (flow_control_supported & MM_FLOW_CONTROL_NONE)
|
|
flow_control_selected = MM_FLOW_CONTROL_NONE;
|
|
else
|
|
g_assert_not_reached ();
|
|
flow_control_selected_str = mm_flow_control_build_string_from_mask (flow_control_selected);
|
|
mm_dbg ("Flow control settings automatically selected (%s)", flow_control_selected_str);
|
|
}
|
|
|
|
/* Select flow control for all connections */
|
|
self->priv->flow_control = flow_control_selected;
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FLOW_CONTROL]);
|
|
|
|
/* Set flow control settings and ignore result */
|
|
switch (flow_control_selected) {
|
|
case MM_FLOW_CONTROL_RTS_CTS:
|
|
cmd = "+IFC=2,2";
|
|
break;
|
|
case MM_FLOW_CONTROL_XON_XOFF:
|
|
cmd = "+IFC=1,1";
|
|
break;
|
|
case MM_FLOW_CONTROL_NONE:
|
|
cmd = "+IFC=0,0";
|
|
break;
|
|
case MM_FLOW_CONTROL_UNKNOWN:
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
mm_base_modem_at_command (_self, cmd, 3, FALSE, NULL, NULL);
|
|
|
|
out:
|
|
g_free (flow_control_supported_str);
|
|
g_free (flow_control_selected_str);
|
|
|
|
/* Ignore errors */
|
|
if (error) {
|
|
mm_dbg ("couldn't load supported flow control methods: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_setup_flow_control (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Query supported flow control methods */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+IFC=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)ifc_test_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Power state loading (Modem interface) */
|
|
|
|
static MMModemPowerState
|
|
modem_load_power_state_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_POWER_STATE_UNKNOWN;
|
|
}
|
|
return (MMModemPowerState)value;
|
|
}
|
|
|
|
static void
|
|
cfun_query_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *result;
|
|
MMModemPowerState state;
|
|
GError *error = NULL;
|
|
|
|
result = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!result || !mm_3gpp_parse_cfun_query_generic_response (result, &state, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_int (task, state);;
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_load_power_state (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* 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_task_return_int (task, MM_MODEM_POWER_STATE_ON);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_dbg ("loading power state...");
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CFUN?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)cfun_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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)
|
|
{
|
|
GTask *task;
|
|
|
|
/* 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);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Reprobing the modem if the SIM changed across a power-off or power-down */
|
|
|
|
typedef struct {
|
|
MMBaseSim *sim;
|
|
guint retries;
|
|
} SimSwapContext;
|
|
|
|
static gboolean load_sim_identifier (GTask *task);
|
|
|
|
static void
|
|
sim_swap_context_free (SimSwapContext *ctx)
|
|
{
|
|
g_clear_object (&ctx->sim);
|
|
g_slice_free (SimSwapContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
modem_check_for_sim_swap_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
load_sim_identifier_ready (MMBaseSim *sim,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
SimSwapContext *ctx;
|
|
const gchar *cached_simid;
|
|
gchar *current_simid;
|
|
GError *error = NULL;
|
|
|
|
self = MM_BROADBAND_MODEM (g_task_get_source_object (task));
|
|
ctx = g_task_get_task_data (task);
|
|
cached_simid = mm_gdbus_sim_get_sim_identifier (MM_GDBUS_SIM (sim));
|
|
current_simid = MM_BASE_SIM_GET_CLASS (sim)->load_sim_identifier_finish (sim, res, &error);
|
|
|
|
if (error) {
|
|
if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
|
|
g_task_return_error (task, error);
|
|
goto out;
|
|
}
|
|
|
|
if (ctx->retries > 0) {
|
|
mm_warn ("could not load SIM identifier: %s (%d retries left)",
|
|
error->message, ctx->retries);
|
|
--ctx->retries;
|
|
g_clear_error (&error);
|
|
g_timeout_add_seconds (1, (GSourceFunc) load_sim_identifier, task);
|
|
return;
|
|
}
|
|
|
|
mm_warn ("could not load SIM identifier: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
goto out;
|
|
}
|
|
|
|
if (g_strcmp0 (current_simid, cached_simid) != 0) {
|
|
mm_info ("sim identifier has changed: possible SIM swap during power down/low");
|
|
mm_broadband_modem_update_sim_hot_swap_detected (self);
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
out:
|
|
g_free (current_simid);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
load_sim_identifier (GTask *task)
|
|
{
|
|
SimSwapContext *ctx = g_task_get_task_data (task);
|
|
|
|
MM_BASE_SIM_GET_CLASS (ctx->sim)->load_sim_identifier (
|
|
ctx->sim,
|
|
(GAsyncReadyCallback)load_sim_identifier_ready,
|
|
task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
modem_check_for_sim_swap (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
SimSwapContext *ctx;
|
|
|
|
mm_dbg ("Checking if SIM was swapped...");
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (SimSwapContext);
|
|
ctx->retries = 3;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)sim_swap_context_free);
|
|
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_SIM, &ctx->sim,
|
|
NULL);
|
|
if (!ctx->sim) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"could not acquire sim object");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!MM_BASE_SIM_GET_CLASS (ctx->sim)->load_sim_identifier ||
|
|
!MM_BASE_SIM_GET_CLASS (ctx->sim)->load_sim_identifier_finish) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"sim identifier could not be loaded");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
load_sim_identifier (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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 {
|
|
guint current;
|
|
MMModem3gppFacility facilities;
|
|
MMModem3gppFacility locks;
|
|
} LoadEnabledFacilityLocksContext;
|
|
|
|
static void get_next_facility_lock_status (GTask *task);
|
|
|
|
static MMModem3gppFacility
|
|
modem_3gpp_load_enabled_facility_locks_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_3GPP_FACILITY_NONE;
|
|
}
|
|
return (MMModem3gppFacility)value;
|
|
}
|
|
|
|
static void
|
|
clck_single_query_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadEnabledFacilityLocksContext *ctx;
|
|
const gchar *response;
|
|
gboolean enabled = FALSE;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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 (task);
|
|
}
|
|
|
|
static void
|
|
get_next_facility_lock_status (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
LoadEnabledFacilityLocksContext *ctx;
|
|
guint i;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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 (self),
|
|
cmd,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)clck_single_query_ready,
|
|
task);
|
|
g_free (cmd);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* No more facilities to query, all done */
|
|
g_task_return_int (task, ctx->locks);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
clck_test_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadEnabledFacilityLocksContext *ctx;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!mm_3gpp_parse_clck_test_response (response, &ctx->facilities)) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't parse list of available lock facilities: '%s'",
|
|
response);
|
|
g_object_unref (task);
|
|
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 (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_load_enabled_facility_locks (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
LoadEnabledFacilityLocksContext *ctx;
|
|
GTask *task;
|
|
|
|
ctx = g_new (LoadEnabledFacilityLocksContext, 1);
|
|
ctx->facilities = MM_MODEM_3GPP_FACILITY_NONE;
|
|
ctx->locks = MM_MODEM_3GPP_FACILITY_NONE;
|
|
ctx->current = 0;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
mm_dbg ("loading enabled facility locks...");
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CLCK=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)clck_test_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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_3gpp_normalize_operator (&operator_code, MM_BROADBAND_MODEM (self)->priv->modem_current_charset);
|
|
if (operator_code)
|
|
mm_dbg ("loaded Operator Code: %s", operator_code);
|
|
return operator_code;
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_load_operator_code (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading Operator Code...");
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self), "+COPS=3,2", 3, FALSE, NULL, NULL);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self), "+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 (&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", 3, FALSE, NULL, NULL);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self), "+COPS?", 3, FALSE, callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* UE mode of operation for EPS loading (3GPP interface) */
|
|
|
|
static MMModem3gppEpsUeModeOperation
|
|
modem_3gpp_load_eps_ue_mode_operation_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMModem3gppEpsUeModeOperation mode;
|
|
const gchar *result;
|
|
|
|
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
if (!result || !mm_3gpp_parse_cemode_query_response (result, &mode, error))
|
|
return MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_UNKNOWN;
|
|
|
|
return mode;
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_load_eps_ue_mode_operation (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading UE mode of operation for EPS...");
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CEMODE?",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* UE mode of operation for EPS settin (3GPP interface) */
|
|
|
|
static gboolean
|
|
modem_3gpp_set_eps_ue_mode_operation_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_set_eps_ue_mode_operation (MMIfaceModem3gpp *self,
|
|
MMModem3gppEpsUeModeOperation mode,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
gchar *cmd;
|
|
|
|
mm_dbg ("updating UE mode of operation for EPS...");
|
|
cmd = mm_3gpp_build_cemode_set_request (mode);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
g_free (cmd);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Unsolicited registration messages handling (3GPP interface) */
|
|
|
|
static gboolean
|
|
modem_3gpp_setup_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (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, tac = 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 and fix LAC/TAC.
|
|
* According to 3GPP TS 27.007:
|
|
* - If CREG reports <AcT> 7 (LTE) then the <lac> field contains TAC
|
|
* - CEREG always reports TAC
|
|
*/
|
|
if (cgreg)
|
|
mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), state);
|
|
else if (cereg) {
|
|
tac = lac;
|
|
lac = 0;
|
|
mm_iface_modem_3gpp_update_eps_registration_state (MM_IFACE_MODEM_3GPP (self), state);
|
|
} else {
|
|
if (act == MM_MODEM_ACCESS_TECHNOLOGY_LTE) {
|
|
tac = lac;
|
|
lac = 0;
|
|
}
|
|
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, tac, cell_id);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_setup_unsolicited_registration_events (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMPortSerialAt *ports[2];
|
|
GPtrArray *array;
|
|
guint i;
|
|
guint j;
|
|
GTask *task;
|
|
|
|
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);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Unsolicited registration messages cleaning up (3GPP interface) */
|
|
|
|
static gboolean
|
|
modem_3gpp_cleanup_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_cleanup_unsolicited_registration_events (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMPortSerialAt *ports[2];
|
|
GPtrArray *array;
|
|
guint i;
|
|
guint j;
|
|
GTask *task;
|
|
|
|
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);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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, MM_BROADBAND_MODEM (self)->priv->modem_current_charset, 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 g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cops_set_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
cops_ascii_set_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error)) {
|
|
/* If it failed with an unsupported error, retry with current modem charset */
|
|
if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) {
|
|
gchar *operator_id;
|
|
gchar *operator_id_current_charset;
|
|
|
|
operator_id = g_task_get_task_data (task);
|
|
operator_id_current_charset = mm_broadband_modem_take_and_convert_to_current_charset (MM_BROADBAND_MODEM (self), g_strdup (operator_id));
|
|
|
|
if (g_strcmp0 (operator_id, operator_id_current_charset) != 0) {
|
|
gchar *command;
|
|
|
|
command = g_strdup_printf ("+COPS=1,2,\"%s\"", operator_id_current_charset);
|
|
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 */
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)cops_set_ready,
|
|
task);
|
|
g_error_free (error);
|
|
g_free (operator_id_current_charset);
|
|
g_free (command);
|
|
return;
|
|
}
|
|
/* operator id string would be the same on the current charset,
|
|
* so fallback and return the not supported error */
|
|
g_free (operator_id_current_charset);
|
|
}
|
|
g_task_return_error (task, error);
|
|
} else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_register_in_network (MMIfaceModem3gpp *self,
|
|
const gchar *operator_id,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
gchar *command;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
/* Trigger automatic network registration if no explicit operator id given */
|
|
if (!operator_id) {
|
|
/* Note that '+COPS=0,,' (same but with commas) won't work in some Nokia
|
|
* phones */
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), NULL),
|
|
"+COPS=0",
|
|
120,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
cancellable,
|
|
(GAsyncReadyCallback)cops_set_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Store operator id in context, in case we need to retry with the current
|
|
* modem charset */
|
|
g_task_set_task_data (task, g_strdup (operator_id), g_free);
|
|
|
|
/* Use the operator id given in ASCII initially */
|
|
command = g_strdup_printf ("+COPS=1,2,\"%s\"", operator_id);
|
|
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,
|
|
(GAsyncReadyCallback)cops_ascii_set_ready,
|
|
task);
|
|
g_free (command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Registration checks (3GPP interface) */
|
|
|
|
typedef struct {
|
|
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_free (RunRegistrationChecksContext *ctx)
|
|
{
|
|
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_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
modem_3gpp_run_registration_checks_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void run_registration_checks_context_step (GTask *task);
|
|
|
|
static void
|
|
registration_status_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
RunRegistrationChecksContext *ctx;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
GMatchInfo *match_info = NULL;
|
|
guint i;
|
|
gboolean parsed;
|
|
gboolean cgreg;
|
|
gboolean cereg;
|
|
MMModem3gppRegistrationState state;
|
|
MMModemAccessTechnology act;
|
|
gulong lac;
|
|
gulong tac;
|
|
gulong cid;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* 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 (task);
|
|
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 (task);
|
|
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 (task);
|
|
return;
|
|
}
|
|
|
|
cgreg = FALSE;
|
|
cereg = FALSE;
|
|
state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
tac = 0;
|
|
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 (task);
|
|
return;
|
|
}
|
|
|
|
/* Report new registration state and fix LAC/TAC.
|
|
* According to 3GPP TS 27.007:
|
|
* - If CREG reports <AcT> 7 (LTE) then the <lac> field contains TAC
|
|
* - CEREG always reports TAC
|
|
*/
|
|
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) {
|
|
tac = lac;
|
|
lac = 0;
|
|
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 (act == MM_MODEM_ACCESS_TECHNOLOGY_LTE) {
|
|
tac = lac;
|
|
lac = 0;
|
|
}
|
|
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, tac, cid);
|
|
|
|
run_registration_checks_context_step (task);
|
|
}
|
|
|
|
static void
|
|
run_registration_checks_context_step (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
RunRegistrationChecksContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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 (self),
|
|
"+CREG?",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)registration_status_check_ready,
|
|
task);
|
|
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 (self),
|
|
"+CGREG?",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)registration_status_check_ready,
|
|
task);
|
|
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 (self),
|
|
"+CEREG?",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)registration_status_check_ready,
|
|
task);
|
|
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_propagate_error (&error, ctx->eps_error);
|
|
ctx->eps_error = NULL;
|
|
} else if (ctx->ps_error) {
|
|
g_propagate_error (&error, ctx->ps_error);
|
|
ctx->ps_error = NULL;
|
|
} else if (ctx->cs_error) {
|
|
g_propagate_error (&error, ctx->cs_error);
|
|
ctx->cs_error = NULL;
|
|
} else
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
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;
|
|
GTask *task;
|
|
|
|
ctx = g_new0 (RunRegistrationChecksContext, 1);
|
|
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;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)run_registration_checks_context_free);
|
|
|
|
run_registration_checks_context_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Create initial EPS bearer object */
|
|
|
|
static MMBaseBearer *
|
|
modem_3gpp_create_initial_eps_bearer (MMIfaceModem3gpp *self,
|
|
MMBearerProperties *config)
|
|
{
|
|
MMBaseBearer *bearer;
|
|
|
|
/* NOTE: by default we create a bearer object that is CONNECTED but which doesn't
|
|
* have an associated data interface already set. This is so that upper layers don't
|
|
* attempt connection through this bearer object. */
|
|
bearer = g_object_new (MM_TYPE_BASE_BEARER,
|
|
MM_BASE_BEARER_MODEM, MM_BASE_MODEM (self),
|
|
MM_BASE_BEARER_CONFIG, config,
|
|
"bearer-type", MM_BEARER_TYPE_DEFAULT_ATTACH,
|
|
"connected", TRUE,
|
|
NULL);
|
|
mm_base_bearer_export (bearer);
|
|
return bearer;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable/Disable unsolicited registration events (3GPP interface) */
|
|
|
|
typedef struct {
|
|
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_free (UnsolicitedRegistrationEventsContext *ctx)
|
|
{
|
|
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_free (ctx);
|
|
}
|
|
|
|
static GTask *
|
|
unsolicited_registration_events_task_new (MMBroadbandModem *self,
|
|
gboolean enable,
|
|
gboolean cs_supported,
|
|
gboolean ps_supported,
|
|
gboolean eps_supported,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
UnsolicitedRegistrationEventsContext *ctx;
|
|
GTask *task;
|
|
|
|
ctx = g_new0 (UnsolicitedRegistrationEventsContext, 1);
|
|
ctx->enable = enable;
|
|
ctx->run_cs = cs_supported;
|
|
ctx->run_ps = ps_supported;
|
|
ctx->run_eps = eps_supported;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task,
|
|
ctx,
|
|
(GDestroyNotify)unsolicited_registration_events_context_free);
|
|
return task;
|
|
}
|
|
|
|
static gboolean
|
|
modem_3gpp_enable_disable_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (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 (GTask *task);
|
|
|
|
static void
|
|
unsolicited_registration_events_sequence_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
UnsolicitedRegistrationEventsContext *ctx;
|
|
GError *error = NULL;
|
|
GVariant *command;
|
|
MMPortSerialAt *secondary;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* 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 (task);
|
|
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,
|
|
task);
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* We're done */
|
|
unsolicited_registration_events_context_step (task);
|
|
}
|
|
|
|
static void
|
|
unsolicited_registration_events_context_step (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
UnsolicitedRegistrationEventsContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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 (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (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,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (ctx->run_ps) {
|
|
ctx->running_ps = TRUE;
|
|
ctx->run_ps = FALSE;
|
|
mm_base_modem_at_sequence_full (
|
|
MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (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,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (ctx->run_eps) {
|
|
ctx->running_eps = TRUE;
|
|
ctx->run_eps = FALSE;
|
|
mm_base_modem_at_sequence_full (
|
|
MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (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,
|
|
task);
|
|
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_propagate_error (&error, ctx->eps_error);
|
|
ctx->eps_error = NULL;
|
|
} else if (ctx->ps_error) {
|
|
g_propagate_error (&error, ctx->ps_error);
|
|
ctx->ps_error = NULL;
|
|
} else if (ctx->cs_error) {
|
|
g_propagate_error (&error, ctx->cs_error);
|
|
ctx->cs_error = NULL;
|
|
}
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
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_task_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_task_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_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cancel_command_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
|
|
/* Complete the pending action, regardless of the CUSD result */
|
|
if (self->priv->pending_ussd_action) {
|
|
GTask *pending_task;
|
|
|
|
pending_task = self->priv->pending_ussd_action;
|
|
self->priv->pending_ussd_action = NULL;
|
|
|
|
g_task_return_new_error (pending_task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"USSD session was cancelled");
|
|
g_object_unref (pending_task);
|
|
}
|
|
|
|
mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self),
|
|
MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE);
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_cancel (MMIfaceModem3gppUssd *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CUSD=2",
|
|
10,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cancel_command_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Send command (3GPP/USSD interface) */
|
|
|
|
typedef struct {
|
|
gchar *command;
|
|
gboolean current_is_unencoded;
|
|
gboolean encoded_used;
|
|
gboolean unencoded_used;
|
|
} Modem3gppUssdSendContext;
|
|
|
|
static void
|
|
modem_3gpp_ussd_send_context_free (Modem3gppUssdSendContext *ctx)
|
|
{
|
|
g_free (ctx->command);
|
|
g_slice_free (Modem3gppUssdSendContext, ctx);
|
|
}
|
|
|
|
static gchar *
|
|
modem_3gpp_ussd_send_finish (MMIfaceModem3gppUssd *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void modem_3gpp_ussd_context_step (MMBroadbandModem *self);
|
|
|
|
static void cusd_process_string (MMBroadbandModem *self,
|
|
const gchar *str);
|
|
|
|
static void
|
|
ussd_send_command_ready (MMBaseModem *_self,
|
|
GAsyncResult *res)
|
|
{
|
|
MMBroadbandModem *self;
|
|
Modem3gppUssdSendContext *ctx;
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
|
|
self = MM_BROADBAND_MODEM (_self);
|
|
|
|
response = mm_base_modem_at_command_finish (_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) {
|
|
modem_3gpp_ussd_context_step (self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!self->priv->pending_ussd_action) {
|
|
mm_dbg ("USSD operation finished already via URCs");
|
|
return;
|
|
}
|
|
|
|
/* Cache the hint for the next time we send something */
|
|
ctx = g_task_get_task_data (self->priv->pending_ussd_action);
|
|
if (!self->priv->use_unencoded_ussd && ctx->current_is_unencoded) {
|
|
mm_dbg ("Will assume we want unencoded USSD commands");
|
|
self->priv->use_unencoded_ussd = TRUE;
|
|
} else if (self->priv->use_unencoded_ussd && !ctx->current_is_unencoded) {
|
|
mm_dbg ("Will assume we want encoded USSD commands");
|
|
self->priv->use_unencoded_ussd = FALSE;
|
|
}
|
|
|
|
if (response && response[0]) {
|
|
response = mm_strip_tag (response, "+CUSD:");
|
|
cusd_process_string (self, response);
|
|
}
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_context_send_encoded (MMBroadbandModem *self)
|
|
{
|
|
Modem3gppUssdSendContext *ctx;
|
|
gchar *at_command = NULL;
|
|
GError *error = NULL;
|
|
guint scheme = 0;
|
|
gchar *encoded;
|
|
|
|
g_assert (self->priv->pending_ussd_action);
|
|
ctx = g_task_get_task_data (self->priv->pending_ussd_action);
|
|
|
|
/* Encode USSD command */
|
|
encoded = mm_iface_modem_3gpp_ussd_encode (MM_IFACE_MODEM_3GPP_USSD (self), ctx->command, &scheme, &error);
|
|
if (!encoded) {
|
|
GTask *task;
|
|
|
|
task = 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);
|
|
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
at_command,
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)ussd_send_command_ready,
|
|
NULL);
|
|
g_free (at_command);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_context_send_unencoded (MMBroadbandModem *self)
|
|
{
|
|
Modem3gppUssdSendContext *ctx;
|
|
gchar *at_command = NULL;
|
|
|
|
g_assert (self->priv->pending_ussd_action);
|
|
ctx = g_task_get_task_data (self->priv->pending_ussd_action);
|
|
|
|
/* 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);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
at_command,
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)ussd_send_command_ready,
|
|
NULL);
|
|
g_free (at_command);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_context_step (MMBroadbandModem *self)
|
|
{
|
|
Modem3gppUssdSendContext *ctx;
|
|
|
|
g_assert (self->priv->pending_ussd_action);
|
|
ctx = g_task_get_task_data (self->priv->pending_ussd_action);
|
|
|
|
if (ctx->encoded_used && ctx->unencoded_used) {
|
|
GTask *task;
|
|
|
|
task = 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);
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Sending USSD command failed");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->use_unencoded_ussd) {
|
|
if (!ctx->unencoded_used)
|
|
modem_3gpp_ussd_context_send_unencoded (self);
|
|
else if (!ctx->encoded_used)
|
|
modem_3gpp_ussd_context_send_encoded (self);
|
|
else
|
|
g_assert_not_reached ();
|
|
} else {
|
|
if (!ctx->encoded_used)
|
|
modem_3gpp_ussd_context_send_encoded (self);
|
|
else if (!ctx->unencoded_used)
|
|
modem_3gpp_ussd_context_send_unencoded (self);
|
|
else
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_send (MMIfaceModem3gppUssd *_self,
|
|
const gchar *command,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self;
|
|
GTask *task;
|
|
Modem3gppUssdSendContext *ctx;
|
|
|
|
self = MM_BROADBAND_MODEM (_self);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (Modem3gppUssdSendContext);
|
|
ctx->command = g_strdup (command);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) modem_3gpp_ussd_send_context_free);
|
|
|
|
/* Fail if there is an ongoing operation already */
|
|
if (self->priv->pending_ussd_action) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
|
|
"there is already an ongoing USSD operation");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Cache the action, as it may be completed via URCs */
|
|
self->priv->pending_ussd_action = task;
|
|
|
|
mm_iface_modem_3gpp_ussd_update_state (_self, MM_MODEM_3GPP_USSD_SESSION_STATE_ACTIVE);
|
|
|
|
modem_3gpp_ussd_context_step (self);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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 (as per AT+CSCS, which is what most modems
|
|
* (except for Huawei it seems) will ask for. */
|
|
if (mm_modem_charset_byte_array_append (ussd_command,
|
|
command,
|
|
FALSE,
|
|
broadband->priv->modem_current_charset)) {
|
|
/* The scheme value does NOT represent the encoding used to encode the string
|
|
* we're giving. This scheme reflects the encoding that the modem should use when
|
|
* sending the data out to the network. We're hardcoding this to GSM-7 because
|
|
* USSD commands fit well in GSM-7, unlike USSD responses that may contain code
|
|
* points that may only be encoded in UCS-2. */
|
|
*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);
|
|
|
|
/* Decode from current charset (as per AT+CSCS, which is what most modems
|
|
* (except for Huawei it seems) will ask for. */
|
|
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_events_finish (MMIfaceModem3gppUssd *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (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;
|
|
GError *error = NULL;
|
|
gint status;
|
|
GTask *task;
|
|
gchar *converted = NULL;
|
|
|
|
/* If there is a pending action, it is ALWAYS completed here */
|
|
task = self->priv->pending_ussd_action;
|
|
self->priv->pending_ussd_action = NULL;
|
|
|
|
if (!str || !isdigit (*str)) {
|
|
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Invalid USSD message received: '%s'", str ? str : "(none)");
|
|
goto out;
|
|
}
|
|
|
|
status = g_ascii_digit_value (*str);
|
|
switch (status) {
|
|
case 0:
|
|
/* no further action required */
|
|
converted = decode_ussd_response (self, str, &error);
|
|
if (!converted)
|
|
break;
|
|
|
|
/* Response to the user's request? */
|
|
if (task)
|
|
break;
|
|
|
|
/* Network-initiated USSD-Notify */
|
|
mm_iface_modem_3gpp_ussd_update_network_notification (MM_IFACE_MODEM_3GPP_USSD (self), converted);
|
|
g_clear_pointer (&converted, g_free);
|
|
break;
|
|
|
|
case 1:
|
|
/* further action required */
|
|
ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_USER_RESPONSE;
|
|
|
|
converted = decode_ussd_response (self, str, &error);
|
|
if (!converted)
|
|
break;
|
|
|
|
/* Response to the user's request? */
|
|
if (task)
|
|
break;
|
|
|
|
/* Network-initiated USSD-Request */
|
|
mm_iface_modem_3gpp_ussd_update_network_request (MM_IFACE_MODEM_3GPP_USSD (self), converted);
|
|
g_clear_pointer (&converted, g_free);
|
|
break;
|
|
|
|
case 2:
|
|
/* Response to the user's request? */
|
|
if (task)
|
|
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, "USSD terminated by network");
|
|
break;
|
|
|
|
case 4:
|
|
/* Response to the user's request? */
|
|
if (task)
|
|
error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, "Operation not supported");
|
|
break;
|
|
|
|
default:
|
|
/* Response to the user's request? */
|
|
if (task)
|
|
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Unhandled USSD reply: %s (%d)", str, status);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
|
|
mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self), ussd_state);
|
|
|
|
/* Complete the pending action */
|
|
if (task) {
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else if (converted)
|
|
g_task_return_pointer (task, converted, g_free);
|
|
else
|
|
g_assert_not_reached ();
|
|
return;
|
|
}
|
|
|
|
/* If no pending task, just report the error */
|
|
if (error) {
|
|
mm_warn ("Invalid USSD message: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_assert (!converted);
|
|
}
|
|
|
|
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)
|
|
{
|
|
MMPortSerialAt *ports[2];
|
|
GRegex *cusd_regex;
|
|
guint i;
|
|
GTask *task;
|
|
|
|
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);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_setup_unsolicited_events (MMIfaceModem3gppUssd *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
set_unsolicited_result_code_handlers (self, TRUE, callback, user_data);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_cleanup_unsolicited_events (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_events_finish (MMIfaceModem3gppUssd *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
urc_enable_disable_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_disable_unsolicited_events (MMIfaceModem3gppUssd *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CUSD=0",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)urc_enable_disable_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_enable_unsolicited_events (MMIfaceModem3gppUssd *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CUSD=1",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)urc_enable_disable_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check if USSD supported (3GPP/USSD interface) */
|
|
|
|
static gboolean
|
|
modem_3gpp_ussd_check_support_finish (MMIfaceModem3gppUssd *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cusd_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_ussd_check_support (MMIfaceModem3gppUssd *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Check USSD support */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CUSD=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cusd_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check if Messaging supported (Messaging interface) */
|
|
|
|
static gboolean
|
|
modem_messaging_check_support_finish (MMIfaceModemMessaging *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cnmi_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* CNMI command is supported; assume we have full messaging capabilities */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_check_support (MMIfaceModemMessaging *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* We assume that CDMA-only modems don't have messaging capabilities */
|
|
if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) {
|
|
g_task_return_new_error (
|
|
task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"CDMA-only modems don't have messaging capabilities");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Check CNMI support */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CNMI=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cnmi_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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;
|
|
|
|
result = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
*mem1 = g_array_ref (result->mem1);
|
|
*mem2 = g_array_ref (result->mem2);
|
|
*mem3 = g_array_ref (result->mem3);
|
|
supported_storages_result_free (result);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
cpms_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
SupportedStoragesResult *result;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
result = g_new0 (SupportedStoragesResult, 1);
|
|
|
|
/* Parse reply */
|
|
if (!mm_3gpp_parse_cpms_test_response (response,
|
|
&result->mem1,
|
|
&result->mem2,
|
|
&result->mem3)) {
|
|
supported_storages_result_free (result);
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't parse supported storages reply: '%s'",
|
|
response);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_pointer (task,
|
|
result,
|
|
(GDestroyNotify)supported_storages_result_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_load_supported_storages (MMIfaceModemMessaging *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Check support storages */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CPMS=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cpms_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Init current SMS storages (Messaging interface) */
|
|
|
|
static gboolean
|
|
modem_messaging_init_current_storages_finish (MMIfaceModemMessaging *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cpms_query_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Parse reply */
|
|
if (!mm_3gpp_parse_cpms_query_response (response,
|
|
&mem1,
|
|
&mem2,
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
gchar *aux;
|
|
|
|
self->priv->current_sms_mem1_storage = mem1;
|
|
self->priv->current_sms_mem2_storage = mem2;
|
|
|
|
mm_dbg ("Current storages initialized:");
|
|
|
|
aux = mm_common_build_sms_storages_string (&mem1, 1);
|
|
mm_dbg (" mem1 (list/read/delete) storages: '%s'", aux);
|
|
g_free (aux);
|
|
|
|
aux = mm_common_build_sms_storages_string (&mem2, 1);
|
|
mm_dbg (" mem2 (write/send) storages: '%s'", aux);
|
|
g_free (aux);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_init_current_storages (MMIfaceModemMessaging *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Check support storages */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CPMS?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cpms_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
typedef struct {
|
|
MMSmsStorage previous_mem1;
|
|
gboolean mem1_locked;
|
|
MMSmsStorage previous_mem2;
|
|
gboolean mem2_locked;
|
|
} LockSmsStoragesContext;
|
|
|
|
static void
|
|
lock_storages_cpms_set_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
LockSmsStoragesContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (error) {
|
|
/* Reset previous storages and set unlocked */
|
|
if (ctx->mem1_locked) {
|
|
self->priv->current_sms_mem1_storage = ctx->previous_mem1;
|
|
self->priv->mem1_storage_locked = FALSE;
|
|
}
|
|
if (ctx->mem2_locked) {
|
|
self->priv->current_sms_mem2_storage = ctx->previous_mem2;
|
|
self->priv->mem2_storage_locked = FALSE;
|
|
}
|
|
g_task_return_error (task, error);
|
|
}
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
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;
|
|
GTask *task;
|
|
gchar *cmd = NULL;
|
|
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_task_report_new_error (
|
|
self,
|
|
callback,
|
|
user_data,
|
|
mm_broadband_modem_lock_sms_storages,
|
|
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_new0 (LockSmsStoragesContext, 1);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
/* Some modems may not support empty string parameters, then if mem1 is
|
|
* UNKNOWN, and current sms mem1 storage has a valid value, 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 sync operation
|
|
* that have potentially locked mem1 previously.
|
|
*/
|
|
if (mem1 != MM_SMS_STORAGE_UNKNOWN) {
|
|
ctx->mem1_locked = TRUE;
|
|
ctx->previous_mem1 = self->priv->current_sms_mem1_storage;
|
|
|
|
self->priv->current_sms_mem1_storage = mem1;
|
|
self->priv->mem1_storage_locked = TRUE;
|
|
} else if (self->priv->current_sms_mem1_storage != MM_SMS_STORAGE_UNKNOWN) {
|
|
mm_dbg ("Given sms mem1 storage is unknown. Using current sms mem1 storage value '%s' instead",
|
|
mm_sms_storage_get_string (self->priv->current_sms_mem1_storage));
|
|
} else {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_RETRY,
|
|
"Cannot lock mem2 storage alone when current mem1 storage is unknown");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
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);
|
|
}
|
|
|
|
g_assert (mem1_str != NULL);
|
|
|
|
/* We don't touch 'mem3' here */
|
|
mm_dbg ("Locking SMS storages to: mem1 (%s), mem2 (%s)...",
|
|
mem1_str,
|
|
mem2_str ? mem2_str : "none");
|
|
|
|
if (mem2_str)
|
|
cmd = g_strdup_printf ("+CPMS=\"%s\",\"%s\"", mem1_str, mem2_str);
|
|
else
|
|
cmd = g_strdup_printf ("+CPMS=\"%s\"", mem1_str);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)lock_storages_cpms_set_ready,
|
|
task);
|
|
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_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cpms_set_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_set_default_storage (MMIfaceModemMessaging *_self,
|
|
MMSmsStorage storage,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
gchar *cmd;
|
|
gchar *mem1_str;
|
|
gchar *mem_str;
|
|
GTask *task;
|
|
|
|
/* We provide the current sms storage for mem1 if not UNKNOWN */
|
|
if (self->priv->current_sms_mem1_storage == MM_SMS_STORAGE_UNKNOWN) {
|
|
g_task_report_new_error (self,
|
|
callback,
|
|
user_data,
|
|
modem_messaging_set_default_storage,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_INVALID_ARGS,
|
|
"Cannot set default storage when current mem1 storage is unknown");
|
|
return;
|
|
}
|
|
|
|
/* Set defaults as current */
|
|
self->priv->current_sms_mem2_storage = storage;
|
|
|
|
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, mem_str, mem_str);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)cpms_set_ready,
|
|
task);
|
|
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_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
cmgf_set_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
set_preferred_sms_format (MMBroadbandModem *self,
|
|
GTask *task)
|
|
{
|
|
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,
|
|
task);
|
|
g_free (cmd);
|
|
}
|
|
|
|
static void
|
|
cmgf_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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, task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_setup_sms_format (MMIfaceModemMessaging *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* 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), task);
|
|
return;
|
|
}
|
|
|
|
/* Check supported SMS formats */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CMGF=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cmgf_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
typedef struct {
|
|
guint idx;
|
|
} SmsPartContext;
|
|
|
|
static void
|
|
sms_part_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
info = mm_3gpp_parse_cmgr_read_response (response, ctx->idx, &error);
|
|
if (!info) {
|
|
mm_warn ("Couldn't parse SMS part: '%s'",
|
|
error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
indication_lock_storages_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Storage now set and locked */
|
|
|
|
/* Retrieve the message */
|
|
ctx = g_task_get_task_data (task);
|
|
command = g_strdup_printf ("+CMGR=%d", ctx->idx);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
command,
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)sms_part_ready,
|
|
task);
|
|
g_free (command);
|
|
}
|
|
|
|
static void
|
|
cmti_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
SmsPartContext *ctx;
|
|
GTask *task;
|
|
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_new (SmsPartContext, 1);
|
|
ctx->idx = idx;
|
|
|
|
task = g_task_new (self, NULL, NULL, NULL);
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
/* First, request to set the proper storage to read from */
|
|
mm_broadband_modem_lock_sms_storages (self,
|
|
storage,
|
|
MM_SMS_STORAGE_UNKNOWN,
|
|
(GAsyncReadyCallback)indication_lock_storages_ready,
|
|
task);
|
|
}
|
|
|
|
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)
|
|
{
|
|
MMPortSerialAt *ports[2];
|
|
GRegex *cmti_regex;
|
|
GRegex *cds_regex;
|
|
guint i;
|
|
GTask *task;
|
|
|
|
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);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
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_task_propagate_boolean (G_TASK (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,
|
|
GTask *task)
|
|
{
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_enable_unsolicited_events_primary_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, inner_error);
|
|
g_object_unref (task);
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_enable_unsolicited_events (MMIfaceModemMessaging *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
MMPortSerialAt *primary;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
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,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load initial list of SMS parts (Messaging interface) */
|
|
|
|
typedef struct {
|
|
MMSmsStorage list_storage;
|
|
} ListPartsContext;
|
|
|
|
static gboolean
|
|
modem_messaging_load_initial_sms_parts_finish (MMIfaceModemMessaging *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (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,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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 (r, response, 0, &match_info)) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_INVALID_ARGS,
|
|
"Couldn't parse SMS list response");
|
|
g_object_unref (task);
|
|
g_match_info_free (match_info);
|
|
g_regex_unref (r);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
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,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
info_list = mm_3gpp_parse_pdu_cmgl_response (response, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
list_parts_lock_storages_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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),
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_messaging_load_initial_sms_parts (MMIfaceModemMessaging *self,
|
|
MMSmsStorage storage,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
ListPartsContext *ctx;
|
|
GTask *task;
|
|
|
|
ctx = g_new (ListPartsContext, 1);
|
|
ctx->list_storage = storage;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
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 (MM_BROADBAND_MODEM (self),
|
|
storage,
|
|
MM_SMS_STORAGE_UNKNOWN,
|
|
(GAsyncReadyCallback)list_parts_lock_storages_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
ignore_sim_related_errors (GError **error)
|
|
{
|
|
g_assert (error && *error);
|
|
if (g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) ||
|
|
g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) ||
|
|
g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK) ||
|
|
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_BUSY) ||
|
|
g_error_matches (*error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG)) {
|
|
g_clear_error (error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clcc_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
/* +CLCC supported unless we got any error response */
|
|
self->priv->clcc_supported = !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
|
|
|
|
/* If +CLCC unsupported we disable polling in the parent directly */
|
|
g_object_set (self,
|
|
MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, !self->priv->clcc_supported,
|
|
NULL);
|
|
|
|
/* ATH command is supported; assume we have full voice capabilities */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
ath_format_check_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
/* Ignore some errors that the module may return when there is no SIM inserted or
|
|
* if the SIM is PIN-locked. We do need the voice interface exposed even in those
|
|
* cases, in order to support emergency calls */
|
|
ignore_sim_related_errors (&error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Also check if +CLCC is supported */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CLCC=?",
|
|
3,
|
|
/* Do NOT cache as the reply may be different if PIN locked
|
|
* or unlocked. E.g. we may not support +CLCC for emergency
|
|
* voice calls. */
|
|
FALSE,
|
|
(GAsyncReadyCallback)clcc_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_check_support (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* We assume that all modems have voice capabilities, but ... */
|
|
|
|
/* Check ATH support */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"H",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)ath_format_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load full list of calls (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_load_call_list_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GList **out_call_info_list,
|
|
GError **error)
|
|
{
|
|
GList *call_info_list;
|
|
GError *inner_error = NULL;
|
|
|
|
call_info_list = g_task_propagate_pointer (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_assert (!call_info_list);
|
|
g_propagate_error (error, inner_error);
|
|
return FALSE;
|
|
}
|
|
|
|
*out_call_info_list = call_info_list;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
clcc_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
GList *call_info_list = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (modem, res, &error);
|
|
if (!response || !mm_3gpp_parse_clcc_response (response, &call_info_list, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_pointer (task, call_info_list, (GDestroyNotify)mm_3gpp_call_info_list_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_load_call_list (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CLCC",
|
|
5,
|
|
FALSE,
|
|
(GAsyncReadyCallback)clcc_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup/cleanup voice related in-call unsolicited events (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_setup_cleanup_in_call_unsolicited_events_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
in_call_event_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
MMCallInfo call_info;
|
|
gchar *str;
|
|
|
|
call_info.index = 0;
|
|
call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
|
|
call_info.state = MM_CALL_STATE_TERMINATED;
|
|
call_info.number = NULL;
|
|
|
|
str = g_match_info_fetch (info, 1);
|
|
mm_dbg ("Call terminated: %s", str);
|
|
g_free (str);
|
|
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
}
|
|
|
|
static void
|
|
set_voice_in_call_unsolicited_events_handlers (MMBroadbandModem *self,
|
|
PortsContext *ports_ctx,
|
|
gboolean enable)
|
|
{
|
|
MMPortSerialAt *ports[2];
|
|
GRegex *in_call_event_regex;
|
|
guint i;
|
|
|
|
in_call_event_regex = g_regex_new ("\\r\\n(NO CARRIER|BUSY|NO ANSWER|NO DIALTONE)(\\r)?\\r\\n$",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
|
|
ports[0] = MM_PORT_SERIAL_AT (ports_ctx->primary);
|
|
ports[1] = MM_PORT_SERIAL_AT (ports_ctx->secondary);
|
|
|
|
/* Enable unsolicited events in given port */
|
|
for (i = 0; i < 2; i++) {
|
|
if (!ports[i])
|
|
continue;
|
|
|
|
mm_dbg ("(%s) %s voice in-call unsolicited events handlers",
|
|
mm_port_get_device (MM_PORT (ports[i])),
|
|
enable ? "Setting" : "Removing");
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ports[i],
|
|
in_call_event_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn) in_call_event_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
}
|
|
|
|
g_regex_unref (in_call_event_regex);
|
|
}
|
|
|
|
static void
|
|
modem_voice_setup_in_call_unsolicited_events (MMIfaceModemVoice *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
|
|
self = MM_BROADBAND_MODEM (_self);
|
|
if (!self->priv->in_call_ports_ctx) {
|
|
PortsContext *ctx;
|
|
|
|
mm_dbg ("Setting up in-call ports context");
|
|
ctx = ports_context_new ();
|
|
if (!ports_context_open (self, ctx, FALSE, TRUE, FALSE, &error)) {
|
|
ports_context_unref (ctx);
|
|
g_prefix_error (&error, "Couldn't open ports in-call: ");
|
|
} else {
|
|
set_voice_in_call_unsolicited_events_handlers (self, ctx, TRUE);
|
|
self->priv->in_call_ports_ctx = ctx;
|
|
}
|
|
} else
|
|
mm_dbg ("In-call ports context already set up");
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_cleanup_in_call_unsolicited_events (MMIfaceModemVoice *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self;
|
|
GTask *task;
|
|
|
|
self = MM_BROADBAND_MODEM (_self);
|
|
if (self->priv->in_call_ports_ctx) {
|
|
mm_dbg ("Cleaning up in-call ports context");
|
|
set_voice_in_call_unsolicited_events_handlers (self, self->priv->in_call_ports_ctx, FALSE);
|
|
g_clear_pointer (&self->priv->in_call_ports_ctx, (GDestroyNotify) ports_context_unref);
|
|
} else
|
|
mm_dbg ("In-call ports context already cleaned up");
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
ccwa_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
MMCallInfo call_info;
|
|
|
|
call_info.index = 0;
|
|
call_info.direction = MM_CALL_DIRECTION_INCOMING;
|
|
call_info.state = MM_CALL_STATE_WAITING;
|
|
call_info.number = mm_get_string_unquoted_from_match_info (info, 1);
|
|
|
|
mm_dbg ("Call waiting (%s)", call_info.number);
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
|
|
g_free (call_info.number);
|
|
}
|
|
|
|
static void
|
|
ring_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
MMCallInfo call_info;
|
|
|
|
call_info.index = 0;
|
|
call_info.direction = MM_CALL_DIRECTION_INCOMING;
|
|
call_info.state = MM_CALL_STATE_RINGING_IN;
|
|
call_info.number = NULL;
|
|
|
|
mm_dbg ("Ringing");
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
}
|
|
|
|
static void
|
|
cring_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
MMCallInfo call_info;
|
|
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);
|
|
|
|
call_info.index = 0;
|
|
call_info.direction = MM_CALL_DIRECTION_INCOMING;
|
|
call_info.state = MM_CALL_STATE_RINGING_IN;
|
|
call_info.number = NULL;
|
|
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
}
|
|
|
|
static void
|
|
clip_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMBroadbandModem *self)
|
|
{
|
|
MMCallInfo call_info;
|
|
|
|
call_info.index = 0;
|
|
call_info.direction = MM_CALL_DIRECTION_INCOMING;
|
|
call_info.state = MM_CALL_STATE_RINGING_IN;
|
|
call_info.number = mm_get_string_unquoted_from_match_info (info, 1);
|
|
|
|
mm_dbg ("Ringing (%s)", call_info.number);
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
|
|
g_free (call_info.number);
|
|
}
|
|
|
|
static void
|
|
set_voice_unsolicited_events_handlers (MMIfaceModemVoice *self,
|
|
gboolean enable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMPortSerialAt *ports[2];
|
|
GRegex *cring_regex;
|
|
GRegex *ring_regex;
|
|
GRegex *clip_regex;
|
|
GRegex *ccwa_regex;
|
|
guint i;
|
|
GTask *task;
|
|
|
|
cring_regex = mm_voice_cring_regex_get ();
|
|
ring_regex = mm_voice_ring_regex_get ();
|
|
clip_regex = mm_voice_clip_regex_get ();
|
|
ccwa_regex = mm_voice_ccwa_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);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ports[i],
|
|
ccwa_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn) ccwa_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
}
|
|
|
|
g_regex_unref (ccwa_regex);
|
|
g_regex_unref (clip_regex);
|
|
g_regex_unref (cring_regex);
|
|
g_regex_unref (ring_regex);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
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) */
|
|
|
|
typedef struct {
|
|
gboolean enable;
|
|
MMPortSerialAt *primary;
|
|
MMPortSerialAt *secondary;
|
|
gchar *clip_command;
|
|
gboolean clip_primary_done;
|
|
gboolean clip_secondary_done;
|
|
gchar *crc_command;
|
|
gboolean crc_primary_done;
|
|
gboolean crc_secondary_done;
|
|
gchar *ccwa_command;
|
|
gboolean ccwa_primary_done;
|
|
gboolean ccwa_secondary_done;
|
|
} VoiceUnsolicitedEventsContext;
|
|
|
|
static void
|
|
voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx)
|
|
{
|
|
g_clear_object (&ctx->secondary);
|
|
g_clear_object (&ctx->primary);
|
|
g_free (ctx->clip_command);
|
|
g_free (ctx->crc_command);
|
|
g_free (ctx->ccwa_command);
|
|
g_slice_free (VoiceUnsolicitedEventsContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
modem_voice_enable_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void run_voice_unsolicited_events_setup (GTask *task);
|
|
|
|
static void
|
|
voice_unsolicited_events_setup_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
VoiceUnsolicitedEventsContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) {
|
|
mm_dbg ("Couldn't %s voice event reporting: '%s'",
|
|
ctx->enable ? "enable" : "disable",
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
/* Continue on next port/command */
|
|
run_voice_unsolicited_events_setup (task);
|
|
}
|
|
|
|
static void
|
|
run_voice_unsolicited_events_setup (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
VoiceUnsolicitedEventsContext *ctx;
|
|
MMPortSerialAt *port = NULL;
|
|
const gchar *command = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* CLIP on primary port */
|
|
if (!ctx->clip_primary_done && ctx->clip_command && ctx->primary) {
|
|
mm_dbg ("%s +CLIP calling line reporting in primary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->clip_primary_done = TRUE;
|
|
command = ctx->clip_command;
|
|
port = ctx->primary;
|
|
}
|
|
/* CLIP on secondary port */
|
|
else if (!ctx->clip_secondary_done && ctx->clip_command && ctx->secondary) {
|
|
mm_dbg ("%s +CLIP calling line reporting in secondary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->clip_secondary_done = TRUE;
|
|
command = ctx->clip_command;
|
|
port = ctx->secondary;
|
|
}
|
|
/* CRC on primary port */
|
|
else if (!ctx->crc_primary_done && ctx->crc_command && ctx->primary) {
|
|
mm_dbg ("%s +CRC extended format of incoming call indications in primary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->crc_primary_done = TRUE;
|
|
command = ctx->crc_command;
|
|
port = ctx->primary;
|
|
}
|
|
/* CRC on secondary port */
|
|
else if (!ctx->crc_secondary_done && ctx->crc_command && ctx->secondary) {
|
|
mm_dbg ("%s +CRC extended format of incoming call indications in secondary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->crc_secondary_done = TRUE;
|
|
command = ctx->crc_command;
|
|
port = ctx->secondary;
|
|
}
|
|
/* CCWA on primary port */
|
|
else if (!ctx->ccwa_primary_done && ctx->ccwa_command && ctx->primary) {
|
|
mm_dbg ("%s +CCWA call waiting indications in primary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->ccwa_primary_done = TRUE;
|
|
command = ctx->ccwa_command;
|
|
port = ctx->primary;
|
|
}
|
|
/* CCWA on secondary port */
|
|
else if (!ctx->ccwa_secondary_done && ctx->ccwa_command && ctx->secondary) {
|
|
mm_dbg ("%s +CCWA call waiting indications in secondary port...", ctx->enable ? "Enabling" : "Disabling");
|
|
ctx->ccwa_secondary_done = TRUE;
|
|
command = ctx->ccwa_command;
|
|
port = ctx->secondary;
|
|
}
|
|
|
|
/* Enable/Disable unsolicited events in given port */
|
|
if (port && command) {
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
port,
|
|
command,
|
|
3,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)voice_unsolicited_events_setup_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Fully done now */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
}
|
|
|
|
static void
|
|
modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
VoiceUnsolicitedEventsContext *ctx;
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
|
|
ctx->enable = TRUE;
|
|
ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
|
|
ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
|
|
|
|
/* enable +CLIP URCs with calling line identity */
|
|
ctx->clip_command = g_strdup ("+CLIP=1");
|
|
/* enable +CRING URCs instead of plain RING */
|
|
ctx->crc_command = g_strdup ("+CRC=1");
|
|
/* enable +CCWA call waiting indications */
|
|
ctx->ccwa_command = g_strdup ("+CCWA=1");
|
|
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
|
|
|
|
run_voice_unsolicited_events_setup (task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
VoiceUnsolicitedEventsContext *ctx;
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (VoiceUnsolicitedEventsContext);
|
|
ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
|
|
ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
|
|
|
|
/* disable +CLIP URCs with calling line identity */
|
|
ctx->clip_command = g_strdup ("+CLIP=0");
|
|
/* disable +CRING URCs instead of plain RING */
|
|
ctx->crc_command = g_strdup ("+CRC=0");
|
|
/* disable +CCWA call waiting indications */
|
|
ctx->ccwa_command = g_strdup ("+CCWA=0");
|
|
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free);
|
|
|
|
run_voice_unsolicited_events_setup (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Create CALL (Voice interface) */
|
|
|
|
static MMBaseCall *
|
|
modem_voice_create_call (MMIfaceModemVoice *_self,
|
|
MMCallDirection direction,
|
|
const gchar *number)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
|
|
return mm_base_call_new (MM_BASE_MODEM (self),
|
|
direction,
|
|
number,
|
|
/* If +CLCC is supported, we want no incoming timeout.
|
|
* Also, we're able to support detailed call state updates without
|
|
* additional vendor-specific commands. */
|
|
self->priv->clcc_supported, /* skip incoming timeout */
|
|
self->priv->clcc_supported, /* dialing->ringing supported */
|
|
self->priv->clcc_supported); /* ringing->active supported */
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hold and accept (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_hold_and_accept_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_hold_and_accept (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CHLD=2",
|
|
20,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hangup and accept (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_hangup_and_accept_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_hangup_and_accept (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CHLD=1",
|
|
20,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hangup all (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_hangup_all_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_hangup_all (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CHUP",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Join multiparty (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_join_multiparty_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_join_multiparty (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CHLD=3",
|
|
20,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Leave multiparty (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_leave_multiparty_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
chld_leave_multiparty_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_leave_multiparty (MMIfaceModemVoice *self,
|
|
MMBaseCall *call,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
guint idx;
|
|
gchar *cmd;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
idx = mm_base_call_get_index (call);
|
|
if (!idx) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
|
|
"unknown call index");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
cmd = g_strdup_printf ("+CHLD=2%u", idx);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback) chld_leave_multiparty_ready,
|
|
task);
|
|
g_free (cmd);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Transfer (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_transfer_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_transfer (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CHLD=4",
|
|
20,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Call waiting setup (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_call_waiting_setup_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_call_waiting_setup (MMIfaceModemVoice *self,
|
|
gboolean enable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
gchar *cmd;
|
|
|
|
/* Enabling or disabling the call waiting service will only be allowed when
|
|
* the modem is registered in the network, and so, CCWA URC handling will
|
|
* always be setup at this point (as it's part of the modem enabling phase).
|
|
* So, just enable or disable the service (second field) but leaving URCs
|
|
* (first field) always enabled. */
|
|
cmd = g_strdup_printf ("+CCWA=1,%u", enable);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
60,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
g_free (cmd);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Call waiting query (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_call_waiting_query_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
gboolean *status,
|
|
GError **error)
|
|
{
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
if (!response)
|
|
return FALSE;
|
|
|
|
return mm_3gpp_parse_ccwa_service_query_response (response, status, error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_call_waiting_query (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
/* This operation will only be allowed while enabled, and so, CCWA URC
|
|
* handling would always be enabled at this point. So, just perform the
|
|
* query, but leaving URCs enabled either way. */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CCWA=1,2",
|
|
60,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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 {
|
|
gboolean setup;
|
|
MMPortSerialQcdm *qcdm;
|
|
gboolean close_port;
|
|
} CdmaUnsolicitedEventsContext;
|
|
|
|
static void
|
|
cdma_unsolicited_events_context_free (CdmaUnsolicitedEventsContext *ctx)
|
|
{
|
|
if (ctx->qcdm && ctx->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,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
CdmaUnsolicitedEventsContext *ctx;
|
|
QcdmResult *result;
|
|
gint err = QCDM_SUCCESS;
|
|
GByteArray *response;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mm_port_serial_qcdm_command_finish (port, res, &error);
|
|
if (error) {
|
|
ctx->close_port = TRUE;
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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) {
|
|
ctx->close_port = TRUE;
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse Log Config Set Mask command result: %d",
|
|
err);
|
|
g_object_unref (task);
|
|
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,
|
|
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.
|
|
*/
|
|
ctx->close_port = ctx->setup ? FALSE : TRUE;
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_cdma_setup_cleanup_unsolicited_events (MMBroadbandModem *self,
|
|
gboolean setup,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
CdmaUnsolicitedEventsContext *ctx;
|
|
GTask *task;
|
|
GByteArray *logcmd;
|
|
uint16_t log_items[] = { DM_LOG_ITEM_EVDO_PILOT_SETS_V2, 0 };
|
|
GError *error = NULL;
|
|
|
|
ctx = g_new0 (CdmaUnsolicitedEventsContext, 1);
|
|
ctx->setup = TRUE;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)cdma_unsolicited_events_context_free);
|
|
|
|
ctx->qcdm = mm_base_modem_get_port_qcdm (MM_BASE_MODEM (self));
|
|
if (!ctx->qcdm) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
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)) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
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,
|
|
task);
|
|
g_byte_array_unref (logcmd);
|
|
}
|
|
|
|
static gboolean
|
|
modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (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;
|
|
|
|
static void
|
|
hdr_state_cleanup_port (MMPortSerial *port)
|
|
{
|
|
mm_port_serial_close (port);
|
|
g_object_unref (port);
|
|
}
|
|
|
|
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;
|
|
|
|
results = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!results)
|
|
return FALSE;
|
|
|
|
*hybrid_mode = results->hybrid_mode;
|
|
*session_state = results->session_state;
|
|
*almp_state = results->almp_state;
|
|
g_free (results);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
hdr_subsys_state_info_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse HDR subsys state info command result: %d",
|
|
err);
|
|
g_object_unref (task);
|
|
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_task_return_pointer (task, results, g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_cdma_get_hdr_state (MMIfaceModemCdma *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMPortSerialQcdm *qcdm;
|
|
GTask *task;
|
|
GByteArray *hdrstate;
|
|
GError *error = NULL;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
qcdm = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
|
|
if (!qcdm) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot get HDR state without a QCDM port");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!mm_port_serial_open (MM_PORT_SERIAL (qcdm), &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_set_task_data (task,
|
|
g_object_ref (qcdm),
|
|
(GDestroyNotify) hdr_state_cleanup_port);
|
|
|
|
/* 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 (qcdm,
|
|
hdrstate,
|
|
3,
|
|
NULL,
|
|
(GAsyncReadyCallback)hdr_subsys_state_info_ready,
|
|
task);
|
|
g_byte_array_unref (hdrstate);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Call Manager state check (CDMA interface) */
|
|
|
|
typedef struct {
|
|
guint system_mode;
|
|
guint operating_mode;
|
|
} CallManagerStateResults;
|
|
|
|
static void
|
|
cm_state_cleanup_port (MMPortSerial *port)
|
|
{
|
|
mm_port_serial_close (port);
|
|
g_object_unref (port);
|
|
}
|
|
|
|
static gboolean
|
|
modem_cdma_get_call_manager_state_finish (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
guint *system_mode,
|
|
guint *operating_mode,
|
|
GError **error)
|
|
{
|
|
CallManagerStateResults *result;
|
|
|
|
result = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
*system_mode = result->system_mode;
|
|
*operating_mode = result->operating_mode;
|
|
g_free (result);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
cm_subsys_state_info_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse CM subsys state info command result: %d",
|
|
err);
|
|
g_object_unref (task);
|
|
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_task_return_pointer (task, results, g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_cdma_get_call_manager_state (MMIfaceModemCdma *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMPortSerialQcdm *qcdm;
|
|
GTask *task;
|
|
GByteArray *cmstate;
|
|
GError *error = NULL;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
qcdm = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
|
|
if (!qcdm) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot get call manager state without a QCDM port");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!mm_port_serial_open (MM_PORT_SERIAL (qcdm), &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_task_set_task_data (task,
|
|
g_object_ref (qcdm),
|
|
(GDestroyNotify) cm_state_cleanup_port);
|
|
|
|
/* 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 (qcdm,
|
|
cmstate,
|
|
3,
|
|
NULL,
|
|
(GAsyncReadyCallback)cm_subsys_state_info_ready,
|
|
task);
|
|
g_byte_array_unref (cmstate);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Serving System check (CDMA interface) */
|
|
|
|
typedef struct {
|
|
guint sid;
|
|
guint nid;
|
|
guint class;
|
|
guint band;
|
|
} Cdma1xServingSystemResults;
|
|
|
|
static void
|
|
cdma1x_serving_system_state_cleanup_port (MMPortSerial *port)
|
|
{
|
|
mm_port_serial_close (port);
|
|
g_object_unref (port);
|
|
}
|
|
|
|
static void
|
|
cdma1x_serving_system_complete_and_free (GTask *task,
|
|
guint sid,
|
|
guint nid,
|
|
guint class,
|
|
guint band)
|
|
{
|
|
Cdma1xServingSystemResults *results;
|
|
|
|
results = g_new0 (Cdma1xServingSystemResults, 1);
|
|
results->sid = sid;
|
|
results->band = band;
|
|
results->class = class;
|
|
results->nid = nid;
|
|
|
|
g_task_return_pointer (task, results, g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
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;
|
|
|
|
results = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!results)
|
|
return FALSE;
|
|
|
|
*sid = results->sid;
|
|
*nid = results->nid;
|
|
*class = results->class;
|
|
*band = results->band;
|
|
g_free (results);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
css_query_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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;
|
|
|
|
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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);
|
|
g_assert (r);
|
|
|
|
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_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse Serving System results");
|
|
g_object_unref (task);
|
|
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_task_return_error (task, cdma1x_serving_system_no_service_error ());
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* No means to get NID with AT commands right now */
|
|
cdma1x_serving_system_complete_and_free (task, sid, MM_MODEM_CDMA_NID_UNKNOWN, class, band);
|
|
}
|
|
|
|
static void
|
|
serving_system_query_css (GTask *task)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (g_task_get_source_object (task)),
|
|
"+CSS?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)css_query_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
qcdm_cdma_status_ready (MMPortSerialQcdm *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
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) {
|
|
mm_dbg ("Failed to get cdma status: %s", error->message);
|
|
g_clear_error (&error);
|
|
|
|
/* Fall back to AT+CSS */
|
|
serving_system_query_css (task);
|
|
return;
|
|
}
|
|
|
|
result = qcdm_cmd_cdma_status_result ((const gchar *) response->data,
|
|
response->len,
|
|
&err);
|
|
if (!result) {
|
|
if (err != QCDM_SUCCESS)
|
|
mm_dbg ("Failed to parse cdma status command result: %d", err);
|
|
g_byte_array_unref (response);
|
|
|
|
/* Fall back to AT+CSS */
|
|
serving_system_query_css (task);
|
|
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);
|
|
|
|
cdma1x_serving_system_complete_and_free (task,
|
|
sid,
|
|
nid,
|
|
0,
|
|
(sid == MM_MODEM_CDMA_SID_UNKNOWN) ? 0 : 'Z');
|
|
}
|
|
|
|
static void
|
|
modem_cdma_get_cdma1x_serving_system (MMIfaceModemCdma *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
GByteArray *cdma_status;
|
|
GTask *task;
|
|
MMPortSerialQcdm *qcdm;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
qcdm = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
|
|
if (!qcdm) {
|
|
/* Fall back to AT+CSS */
|
|
serving_system_query_css (task);
|
|
return;
|
|
}
|
|
|
|
if (!mm_port_serial_open (MM_PORT_SERIAL (qcdm), &error)) {
|
|
mm_dbg ("Failed to open QCDM port for serving-system request: %s", error->message);
|
|
g_error_free (error);
|
|
|
|
/* Fall back to AT+CSS */
|
|
serving_system_query_css (task);
|
|
return;
|
|
}
|
|
|
|
g_task_set_task_data (task,
|
|
g_object_ref (qcdm),
|
|
(GDestroyNotify) cdma1x_serving_system_state_cleanup_port);
|
|
|
|
/* 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 (qcdm,
|
|
cdma_status,
|
|
3,
|
|
NULL,
|
|
(GAsyncReadyCallback) qcdm_cdma_status_ready,
|
|
task);
|
|
g_byte_array_unref (cdma_status);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gboolean value;
|
|
|
|
value = g_task_propagate_boolean (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return FALSE;
|
|
}
|
|
|
|
*has_cdma_service = value;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
cad_query_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
const gchar *result;
|
|
|
|
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error)
|
|
g_task_return_error (task, 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_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Failed to parse +CAD response '%s'",
|
|
result);
|
|
else
|
|
/* 1 == CDMA service */
|
|
g_task_return_boolean (task, (cad == 1));
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_cdma_get_service_status (MMIfaceModemCdma *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CAD?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)cad_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Detailed registration state (CDMA interface) */
|
|
|
|
typedef struct {
|
|
MMModemCdmaRegistrationState detailed_cdma1x_state;
|
|
MMModemCdmaRegistrationState detailed_evdo_state;
|
|
} DetailedRegistrationStateResults;
|
|
|
|
typedef struct {
|
|
MMPortSerialAt *port;
|
|
MMModemCdmaRegistrationState cdma1x_state;
|
|
MMModemCdmaRegistrationState evdo_state;
|
|
} DetailedRegistrationStateContext;
|
|
|
|
static DetailedRegistrationStateResults *
|
|
detailed_registration_state_result_new (DetailedRegistrationStateContext *ctx)
|
|
{
|
|
DetailedRegistrationStateResults *results;
|
|
|
|
results = g_new (DetailedRegistrationStateResults, 1);
|
|
results->detailed_cdma1x_state = ctx->cdma1x_state;
|
|
results->detailed_evdo_state = ctx->evdo_state;
|
|
|
|
return results;
|
|
}
|
|
|
|
static void
|
|
detailed_registration_state_context_free (DetailedRegistrationStateContext *ctx)
|
|
{
|
|
g_object_unref (ctx->port);
|
|
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;
|
|
|
|
results = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!results)
|
|
return FALSE;
|
|
|
|
*detailed_cdma1x_state = results->detailed_cdma1x_state;
|
|
*detailed_evdo_state = results->detailed_evdo_state;
|
|
g_free (results);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
speri_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
DetailedRegistrationStateContext *ctx;
|
|
gboolean roaming = FALSE;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
/* silently discard SPERI errors */
|
|
g_error_free (error);
|
|
g_task_return_pointer (task,
|
|
detailed_registration_state_result_new (ctx),
|
|
g_free);
|
|
g_object_unref (task);
|
|
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);
|
|
g_task_return_pointer (task,
|
|
detailed_registration_state_result_new (ctx),
|
|
g_free);
|
|
g_object_unref (task);
|
|
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;
|
|
}
|
|
|
|
g_task_return_pointer (task,
|
|
detailed_registration_state_result_new (ctx),
|
|
g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
spservice_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
DetailedRegistrationStateContext *ctx;
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
MMModemCdmaRegistrationState cdma1x_state;
|
|
MMModemCdmaRegistrationState evdo_state;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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)) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't parse SPSERVICE response '%s'",
|
|
response);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* Store new intermediate results */
|
|
ctx->cdma1x_state = cdma1x_state;
|
|
ctx->evdo_state = evdo_state;
|
|
|
|
/* If SPERI not supported, we're done */
|
|
if (!MM_BROADBAND_MODEM (self)->priv->has_speri) {
|
|
g_task_return_pointer (task,
|
|
detailed_registration_state_result_new (ctx),
|
|
g_free);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Get roaming status to override generic registration state */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"$SPERI?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)speri_ready,
|
|
task);
|
|
}
|
|
|
|
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;
|
|
GTask *task;
|
|
|
|
/* 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_task_report_error (self,
|
|
callback,
|
|
user_data,
|
|
modem_cdma_get_detailed_registration_state,
|
|
error);
|
|
return;
|
|
}
|
|
|
|
/* Setup context */
|
|
ctx = g_new0 (DetailedRegistrationStateContext, 1);
|
|
ctx->port = g_object_ref (port);
|
|
ctx->cdma1x_state = cdma1x_state;
|
|
ctx->evdo_state = evdo_state;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)detailed_registration_state_context_free);
|
|
|
|
/* 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,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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 {
|
|
gboolean has_qcdm_port;
|
|
gboolean has_sprint_commands;
|
|
} SetupRegistrationChecksContext;
|
|
|
|
static SetupRegistrationChecksResults *
|
|
setup_registration_checks_results_new (MMBroadbandModem *self,
|
|
SetupRegistrationChecksContext *ctx)
|
|
{
|
|
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 (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;
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
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;
|
|
|
|
results = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!results)
|
|
return FALSE;
|
|
|
|
*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;
|
|
g_free (results);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
speri_check_ready (MMIfaceModemCdma *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
SetupRegistrationChecksContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error)
|
|
g_error_free (error);
|
|
else
|
|
/* We DO have SPERI */
|
|
self->priv->has_speri = TRUE;
|
|
|
|
/* All done */
|
|
self->priv->checked_sprint_support = TRUE;
|
|
g_task_return_pointer (task,
|
|
setup_registration_checks_results_new (self, ctx),
|
|
g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
spservice_check_ready (MMIfaceModemCdma *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
SetupRegistrationChecksContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error) {
|
|
g_error_free (error);
|
|
self->priv->checked_sprint_support = TRUE;
|
|
g_task_return_pointer (task,
|
|
setup_registration_checks_results_new (self, ctx),
|
|
g_free);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* We DO have SPSERVICE, look for SPERI */
|
|
ctx->has_sprint_commands = TRUE;
|
|
self->priv->has_spservice = TRUE;
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"$SPERI?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)speri_check_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_cdma_setup_registration_checks (MMIfaceModemCdma *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
|
|
SetupRegistrationChecksContext *ctx;
|
|
GTask *task;
|
|
|
|
ctx = g_new0 (SetupRegistrationChecksContext, 1);
|
|
|
|
/* Check if we have a QCDM port */
|
|
ctx->has_qcdm_port = !!mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
/* If we have cached results of Sprint command checking, use them */
|
|
if (self->priv->checked_sprint_support) {
|
|
ctx->has_sprint_commands = self->priv->has_spservice;
|
|
|
|
/* Completes in idle */
|
|
g_task_return_pointer (task,
|
|
setup_registration_checks_results_new (self, ctx),
|
|
g_free);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, launch Sprint command checks. */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+SPSERVICE?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)spservice_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Register in network (CDMA interface) */
|
|
|
|
typedef struct {
|
|
MMBroadbandModem *self;
|
|
GCancellable *cancellable;
|
|
GTimer *timer;
|
|
guint max_registration_time;
|
|
} RegisterInCdmaNetworkContext;
|
|
|
|
static void
|
|
register_in_cdma_network_context_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_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_task_propagate_boolean (G_TASK (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,
|
|
GTask *task);
|
|
|
|
static gboolean
|
|
run_cdma_registration_checks_again (GTask *task)
|
|
{
|
|
/* Get fresh registration state */
|
|
mm_iface_modem_cdma_run_registration_checks (
|
|
MM_IFACE_MODEM_CDMA (g_task_get_source_object (task)),
|
|
(GAsyncReadyCallback)run_cdma_registration_checks_ready,
|
|
task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
run_cdma_registration_checks_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
RegisterInCdmaNetworkContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
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);
|
|
error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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,
|
|
task);
|
|
}
|
|
|
|
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;
|
|
GTask *task;
|
|
|
|
/* (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->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 ();
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)register_in_cdma_network_context_free);
|
|
|
|
mm_iface_modem_cdma_run_registration_checks (
|
|
self,
|
|
(GAsyncReadyCallback)run_cdma_registration_checks_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load location capabilities (Location interface) */
|
|
|
|
static MMModemLocationSource
|
|
modem_location_load_capabilities_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_LOCATION_SOURCE_NONE;
|
|
}
|
|
return (MMModemLocationSource)value;
|
|
}
|
|
|
|
static void
|
|
modem_location_load_capabilities (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* 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_task_return_int (task, MM_MODEM_LOCATION_SOURCE_NONE);
|
|
else
|
|
g_task_return_int (task, MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable location gathering (Location interface) */
|
|
|
|
static gboolean
|
|
enable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
enable_location_gathering (MMIfaceModemLocation *self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
/* 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 */
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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,
|
|
FALSE,
|
|
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;
|
|
guint 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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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;
|
|
}
|
|
|
|
static gpointer
|
|
initialization_started_finish (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
initialization_started (MMBroadbandModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
GTask *task;
|
|
PortsContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Open ports for initialization, just the primary AT port */
|
|
ctx = ports_context_new ();
|
|
if (!ports_context_open (self, ctx, FALSE, FALSE, FALSE, &error)) {
|
|
ports_context_unref (ctx);
|
|
g_prefix_error (&error, "Couldn't open ports during modem initialization: ");
|
|
g_task_return_error (task, error);
|
|
} else
|
|
g_task_return_pointer (task, ctx, (GDestroyNotify)ports_context_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disabling stopped */
|
|
|
|
static gboolean
|
|
disabling_stopped (MMBroadbandModem *self,
|
|
GError **error)
|
|
{
|
|
if (self->priv->enabled_ports_ctx) {
|
|
ports_context_unref (self->priv->enabled_ports_ctx);
|
|
self->priv->enabled_ports_ctx = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Initializing the modem (during first enabling) */
|
|
|
|
static gboolean
|
|
enabling_modem_init_finish (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
enabling_modem_init (MMBroadbandModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
/* Init command. ITU rec v.250 (6.1.1) says:
|
|
* The DTE should not include additional commands on the same command line
|
|
* after the Z command because such commands may be ignored.
|
|
* So run ATZ alone.
|
|
*/
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
"Z",
|
|
6,
|
|
FALSE,
|
|
FALSE,
|
|
NULL, /* cancellable */
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enabling started */
|
|
|
|
typedef struct {
|
|
PortsContext *ports;
|
|
gboolean modem_init_required;
|
|
} EnablingStartedContext;
|
|
|
|
static void
|
|
enabling_started_context_free (EnablingStartedContext *ctx)
|
|
{
|
|
ports_context_unref (ctx->ports);
|
|
g_slice_free (EnablingStartedContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
enabling_started_finish (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static gboolean
|
|
enabling_after_modem_init_timeout (GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
EnablingStartedContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* Reset init sequence enabled flags and run them explicitly */
|
|
g_assert (ctx->modem_init_required);
|
|
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 */
|
|
self->priv->enabled_ports_ctx = ports_context_ref (ctx->ports);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
enabling_modem_init_ready (MMBroadbandModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_modem_init_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Specify that the modem init was run once */
|
|
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, task);
|
|
}
|
|
|
|
static void
|
|
enabling_flash_done (MMPortSerial *port,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModem *self;
|
|
EnablingStartedContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
if (!mm_port_serial_flash_finish (port, res, &error)) {
|
|
g_prefix_error (&error, "Primary port flashing failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->modem_init_required) {
|
|
mm_dbg ("Running modem initialization sequence...");
|
|
MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_modem_init (self,
|
|
(GAsyncReadyCallback)enabling_modem_init_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Store enabled ports context and complete */
|
|
self->priv->enabled_ports_ctx = ports_context_ref (ctx->ports);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
enabling_started (MMBroadbandModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
EnablingStartedContext *ctx;
|
|
GTask *task;
|
|
|
|
ctx = g_slice_new0 (EnablingStartedContext);
|
|
ctx->ports = ports_context_new ();
|
|
|
|
/* 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 (self->priv->modem_init_run)
|
|
mm_dbg ("Skipping modem initialization: not first enabling");
|
|
else if (mm_base_modem_get_hotplugged (MM_BASE_MODEM (self))) {
|
|
self->priv->modem_init_run = TRUE;
|
|
mm_dbg ("Skipping modem initialization: device hotplugged");
|
|
} else if (!MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_modem_init ||
|
|
!MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_modem_init_finish)
|
|
mm_dbg ("Skipping modem initialization: not required");
|
|
else
|
|
ctx->modem_init_required = TRUE;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)enabling_started_context_free);
|
|
|
|
/* Open ports for enabling, including secondary AT port and QCDM if available */
|
|
if (!ports_context_open (self, ctx->ports, ctx->modem_init_required, TRUE, TRUE, &error)) {
|
|
g_prefix_error (&error, "Couldn't open ports during modem enabling: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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_VOICE,
|
|
DISABLING_STEP_IFACE_SIGNAL,
|
|
DISABLING_STEP_IFACE_OMA,
|
|
DISABLING_STEP_IFACE_TIME,
|
|
DISABLING_STEP_IFACE_MESSAGING,
|
|
DISABLING_STEP_IFACE_LOCATION,
|
|
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;
|
|
DisablingStep step;
|
|
MMModemState previous_state;
|
|
gboolean disabled;
|
|
} DisablingContext;
|
|
|
|
static void disabling_step (GTask *task);
|
|
|
|
static void
|
|
disabling_context_free (DisablingContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
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->self);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
disable_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (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, \
|
|
GTask *task) \
|
|
{ \
|
|
DisablingContext *ctx; \
|
|
GError *error = NULL; \
|
|
\
|
|
if (!mm_##NAME##_disable_finish (TYPE (self), \
|
|
result, \
|
|
&error)) { \
|
|
if (FATAL_ERRORS) { \
|
|
g_task_return_error (task, error); \
|
|
g_object_unref (task); \
|
|
return; \
|
|
} \
|
|
\
|
|
mm_dbg ("Couldn't disable interface: '%s'", \
|
|
error->message); \
|
|
g_error_free (error); \
|
|
return; \
|
|
} \
|
|
\
|
|
/* Go on to next step */ \
|
|
ctx = g_task_get_task_data (task); \
|
|
ctx->step++; \
|
|
disabling_step (task); \
|
|
}
|
|
|
|
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,
|
|
GTask *task)
|
|
{
|
|
DisablingContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
if (!mm_bearer_list_disconnect_all_bearers_finish (list, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Go on to next step */
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->step++;
|
|
disabling_step (task);
|
|
}
|
|
|
|
static void
|
|
disabling_wait_for_final_state_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
DisablingContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
ctx->previous_state = mm_iface_modem_wait_for_final_state_finish (self, res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
case MM_MODEM_STATE_INITIALIZING:
|
|
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:
|
|
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 (task);
|
|
}
|
|
|
|
static void
|
|
disabling_step (GTask *task)
|
|
{
|
|
DisablingContext *ctx;
|
|
|
|
/* Don't run new steps if we're cancelled */
|
|
if (g_task_return_error_if_cancelled (task)) {
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case DISABLING_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case DISABLING_STEP_IFACE_SIMPLE:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case DISABLING_STEP_IFACE_FIRMWARE:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case DISABLING_STEP_LAST:
|
|
ctx->disabled = TRUE;
|
|
/* All disabled without errors! */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
disable (MMBaseModem *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
DisablingContext *ctx;
|
|
GTask *task;
|
|
|
|
ctx = g_new0 (DisablingContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->step = DISABLING_STEP_FIRST;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)disabling_context_free);
|
|
|
|
disabling_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_LOCATION,
|
|
ENABLING_STEP_IFACE_MESSAGING,
|
|
ENABLING_STEP_IFACE_TIME,
|
|
ENABLING_STEP_IFACE_SIGNAL,
|
|
ENABLING_STEP_IFACE_OMA,
|
|
ENABLING_STEP_IFACE_VOICE,
|
|
ENABLING_STEP_IFACE_FIRMWARE,
|
|
ENABLING_STEP_IFACE_SIMPLE,
|
|
ENABLING_STEP_LAST,
|
|
} EnablingStep;
|
|
|
|
typedef struct {
|
|
MMBroadbandModem *self;
|
|
EnablingStep step;
|
|
MMModemState previous_state;
|
|
gboolean enabled;
|
|
} EnablingContext;
|
|
|
|
static void enabling_step (GTask *task);
|
|
|
|
static void
|
|
enabling_context_free (EnablingContext *ctx)
|
|
{
|
|
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->self);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
enable_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
#undef INTERFACE_ENABLE_READY_FN
|
|
#define INTERFACE_ENABLE_READY_FN(NAME,TYPE,FATAL_ERRORS) \
|
|
static void \
|
|
NAME##_enable_ready (MMBroadbandModem *self, \
|
|
GAsyncResult *result, \
|
|
GTask *task) \
|
|
{ \
|
|
EnablingContext *ctx; \
|
|
GError *error = NULL; \
|
|
\
|
|
if (!mm_##NAME##_enable_finish (TYPE (self), \
|
|
result, \
|
|
&error)) { \
|
|
if (FATAL_ERRORS) { \
|
|
g_task_return_error (task, error); \
|
|
g_object_unref (task); \
|
|
return; \
|
|
} \
|
|
\
|
|
mm_dbg ("Couldn't enable interface: '%s'", \
|
|
error->message); \
|
|
g_error_free (error); \
|
|
} \
|
|
\
|
|
/* Go on to next step */ \
|
|
ctx = g_task_get_task_data (task); \
|
|
ctx->step++; \
|
|
enabling_step (task); \
|
|
}
|
|
|
|
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,
|
|
GTask *task)
|
|
{
|
|
EnablingContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
if (!MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_started_finish (self, result, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Go on to next step */
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->step++;
|
|
enabling_step (task);
|
|
}
|
|
|
|
static void
|
|
enabling_wait_for_final_state_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
EnablingContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
ctx->previous_state = mm_iface_modem_wait_for_final_state_finish (self, res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (ctx->previous_state >= MM_MODEM_STATE_ENABLED) {
|
|
/* Just return success, don't relaunch enabling */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
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 (task);
|
|
}
|
|
|
|
static void
|
|
enabling_step (GTask *task)
|
|
{
|
|
EnablingContext *ctx;
|
|
|
|
/* Don't run new steps if we're cancelled */
|
|
if (g_task_return_error_if_cancelled (task)) {
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case ENABLING_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_enable_ready,
|
|
task);
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_3gpp_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_cdma_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_location_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_messaging_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_time_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_signal_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_oma_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_voice_enable_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case ENABLING_STEP_IFACE_FIRMWARE:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case ENABLING_STEP_IFACE_SIMPLE:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
enable (MMBaseModem *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
/* 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:
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Cannot enable modem: initialization failed");
|
|
break;
|
|
|
|
case MM_MODEM_STATE_LOCKED:
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Cannot enable modem: device locked");
|
|
break;
|
|
|
|
case MM_MODEM_STATE_INITIALIZING:
|
|
case MM_MODEM_STATE_DISABLED:
|
|
case MM_MODEM_STATE_DISABLING: {
|
|
EnablingContext *ctx;
|
|
|
|
ctx = g_new0 (EnablingContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->step = ENABLING_STEP_FIRST;
|
|
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)enabling_context_free);
|
|
|
|
enabling_step (task);
|
|
return;
|
|
}
|
|
|
|
case MM_MODEM_STATE_ENABLING:
|
|
g_assert_not_reached ();
|
|
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_task_return_boolean (task, TRUE);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_LOCATION,
|
|
INITIALIZE_STEP_IFACE_MESSAGING,
|
|
INITIALIZE_STEP_IFACE_TIME,
|
|
INITIALIZE_STEP_IFACE_SIGNAL,
|
|
INITIALIZE_STEP_IFACE_OMA,
|
|
INITIALIZE_STEP_FALLBACK_LIMITED,
|
|
INITIALIZE_STEP_IFACE_VOICE,
|
|
INITIALIZE_STEP_IFACE_FIRMWARE,
|
|
INITIALIZE_STEP_SIM_HOT_SWAP,
|
|
INITIALIZE_STEP_IFACE_SIMPLE,
|
|
INITIALIZE_STEP_LAST,
|
|
} InitializeStep;
|
|
|
|
typedef struct {
|
|
MMBroadbandModem *self;
|
|
InitializeStep step;
|
|
gpointer ports_ctx;
|
|
} InitializeContext;
|
|
|
|
static void initialize_step (GTask *task);
|
|
|
|
static void
|
|
initialize_context_free (InitializeContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
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->self);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gboolean
|
|
initialize_finish (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
initialization_started_ready (MMBroadbandModem *self,
|
|
GAsyncResult *result,
|
|
GTask *task)
|
|
{
|
|
InitializeContext *ctx;
|
|
GError *error = NULL;
|
|
gpointer ports_ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* 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 (task);
|
|
return;
|
|
}
|
|
|
|
/* Keep the ctx for later use when stopping initialization */
|
|
ctx->ports_ctx = ports_ctx;
|
|
|
|
/* Go on to next step */
|
|
ctx->step++;
|
|
initialize_step (task);
|
|
}
|
|
|
|
static void
|
|
iface_modem_initialize_ready (MMBroadbandModem *self,
|
|
GAsyncResult *result,
|
|
GTask *task)
|
|
{
|
|
InitializeContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* 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 fallback step when on failure, we will allow some additional
|
|
* interfaces even in failed state. */
|
|
ctx->step = INITIALIZE_STEP_FALLBACK_LIMITED;
|
|
initialize_step (task);
|
|
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 fallback step when locked, we will allow some additional
|
|
* interfaces even in locked state. */
|
|
ctx->step = INITIALIZE_STEP_FALLBACK_LIMITED;
|
|
initialize_step (task);
|
|
return;
|
|
}
|
|
|
|
/* Go on to next step */
|
|
ctx->step++;
|
|
initialize_step (task);
|
|
}
|
|
|
|
#undef INTERFACE_INIT_READY_FN
|
|
#define INTERFACE_INIT_READY_FN(NAME,TYPE,FATAL_ERRORS) \
|
|
static void \
|
|
NAME##_initialize_ready (MMBroadbandModem *self, \
|
|
GAsyncResult *result, \
|
|
GTask *task) \
|
|
{ \
|
|
InitializeContext *ctx; \
|
|
GError *error = NULL; \
|
|
\
|
|
ctx = g_task_get_task_data (task); \
|
|
\
|
|
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 (task); \
|
|
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 (task); \
|
|
}
|
|
|
|
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 (GTask *task)
|
|
{
|
|
InitializeContext *ctx;
|
|
|
|
/* Don't run new steps if we're cancelled */
|
|
if (g_task_return_error_if_cancelled (task)) {
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case INITIALIZE_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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);
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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 ();
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case INITIALIZE_STEP_IFACE_MODEM:
|
|
/* Initialize the Modem interface */
|
|
mm_iface_modem_initialize (MM_IFACE_MODEM (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_initialize_ready,
|
|
task);
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_3gpp_initialize_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_cdma_initialize_ready,
|
|
task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case INITIALIZE_STEP_IFACE_LOCATION:
|
|
/* Initialize the Location interface */
|
|
mm_iface_modem_location_initialize (MM_IFACE_MODEM_LOCATION (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_location_initialize_ready,
|
|
task);
|
|
return;
|
|
|
|
case INITIALIZE_STEP_IFACE_MESSAGING:
|
|
/* Initialize the Messaging interface */
|
|
mm_iface_modem_messaging_initialize (MM_IFACE_MODEM_MESSAGING (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_messaging_initialize_ready,
|
|
task);
|
|
return;
|
|
|
|
case INITIALIZE_STEP_IFACE_TIME:
|
|
/* Initialize the Time interface */
|
|
mm_iface_modem_time_initialize (MM_IFACE_MODEM_TIME (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_time_initialize_ready,
|
|
task);
|
|
return;
|
|
|
|
case INITIALIZE_STEP_IFACE_SIGNAL:
|
|
/* Initialize the Signal interface */
|
|
mm_iface_modem_signal_initialize (MM_IFACE_MODEM_SIGNAL (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_signal_initialize_ready,
|
|
task);
|
|
return;
|
|
|
|
case INITIALIZE_STEP_IFACE_OMA:
|
|
/* Initialize the Oma interface */
|
|
mm_iface_modem_oma_initialize (MM_IFACE_MODEM_OMA (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_oma_initialize_ready,
|
|
task);
|
|
return;
|
|
|
|
case INITIALIZE_STEP_FALLBACK_LIMITED:
|
|
/* All the initialization steps after this one will be run both on
|
|
* successful and locked/failed initializations. */
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case INITIALIZE_STEP_IFACE_VOICE:
|
|
/* Initialize the Voice interface */
|
|
mm_iface_modem_voice_initialize (MM_IFACE_MODEM_VOICE (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_voice_initialize_ready,
|
|
task);
|
|
return;
|
|
|
|
case INITIALIZE_STEP_IFACE_FIRMWARE:
|
|
/* Initialize the Firmware interface */
|
|
mm_iface_modem_firmware_initialize (MM_IFACE_MODEM_FIRMWARE (ctx->self),
|
|
g_task_get_cancellable (task),
|
|
(GAsyncReadyCallback)iface_modem_firmware_initialize_ready,
|
|
task);
|
|
return;
|
|
|
|
case INITIALIZE_STEP_SIM_HOT_SWAP:
|
|
/* Create the SIM hot swap ports context only if not already done before
|
|
* (we may be re-running the initialization step after SIM-PIN unlock) */
|
|
if (!ctx->self->priv->sim_hot_swap_ports_ctx) {
|
|
gboolean is_sim_hot_swap_supported = FALSE;
|
|
gboolean is_sim_hot_swap_configured = FALSE;
|
|
|
|
g_object_get (ctx->self,
|
|
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, &is_sim_hot_swap_supported,
|
|
MM_IFACE_MODEM_SIM_HOT_SWAP_CONFIGURED, &is_sim_hot_swap_configured,
|
|
NULL);
|
|
|
|
if (is_sim_hot_swap_supported) {
|
|
|
|
if (!is_sim_hot_swap_configured) {
|
|
mm_warn ("SIM hot swap supported but not configured. Skipping opening ports");
|
|
} else {
|
|
PortsContext *ports;
|
|
GError *error = NULL;
|
|
|
|
mm_dbg ("Creating ports context for SIM hot swap");
|
|
ports = ports_context_new ();
|
|
if (!ports_context_open (ctx->self, ports, FALSE, FALSE, 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);
|
|
}
|
|
}
|
|
} else
|
|
mm_dbg ("Ports context for SIM hot swap already available");
|
|
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
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));
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case INITIALIZE_STEP_LAST:
|
|
if (ctx->self->priv->modem_state == MM_MODEM_STATE_FAILED) {
|
|
GError *error;
|
|
|
|
if (!ctx->self->priv->modem_dbus_skeleton) {
|
|
/* Error setting up ports. Abort without even exporting the
|
|
* Modem interface */
|
|
error = g_error_new (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;
|
|
gboolean is_sim_hot_swap_configured = 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,
|
|
MM_IFACE_MODEM_SIM_HOT_SWAP_CONFIGURED, &is_sim_hot_swap_configured,
|
|
NULL);
|
|
|
|
if (reason == MM_MODEM_STATE_FAILED_REASON_SIM_MISSING) {
|
|
if (!is_sim_hot_swap_supported) {
|
|
mm_dbg ("SIM is missing, but this modem does not support SIM hot swap.");
|
|
} else if (!is_sim_hot_swap_configured) {
|
|
mm_warn ("SIM is missing, but SIM hot swap could not be configured.");
|
|
} else if (!ctx->self->priv->sim_hot_swap_ports_ctx) {
|
|
mm_err ("SIM is missing and SIM hot swap is configured, but ports are not opened.");
|
|
} else {
|
|
mm_dbg ("SIM is missing, but SIM hot swap is enabled. Waiting for SIM...");
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Modem is unusable due to SIM missing, "
|
|
"cannot fully initialize, waiting for SIM insertion.");
|
|
goto sim_hot_swap_enabled;
|
|
}
|
|
}
|
|
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Modem is unusable, "
|
|
"cannot fully initialize");
|
|
sim_hot_swap_enabled:
|
|
/* Ensure we only leave the Modem and Firmware interfaces
|
|
* around. A failure could be caused by firmware issues, which
|
|
* a firmware update, switch, or provisioning could fix. We also
|
|
* leave the Voice interface around so that we can attempt
|
|
* emergency voice calls.
|
|
*/
|
|
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_signal_shutdown (MM_IFACE_MODEM_SIGNAL (ctx->self));
|
|
mm_iface_modem_messaging_shutdown (MM_IFACE_MODEM_MESSAGING (ctx->self));
|
|
mm_iface_modem_time_shutdown (MM_IFACE_MODEM_TIME (ctx->self));
|
|
mm_iface_modem_simple_shutdown (MM_IFACE_MODEM_SIMPLE (ctx->self));
|
|
}
|
|
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (ctx->self->priv->modem_state == MM_MODEM_STATE_LOCKED) {
|
|
/* We're locked :-/ */
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Modem is currently locked, "
|
|
"cannot fully initialize");
|
|
g_object_unref (task);
|
|
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_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
initialize (MMBaseModem *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
/* 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_task_return_new_error (task,
|
|
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->step = INITIALIZE_STEP_FIRST;
|
|
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)initialize_context_free);
|
|
|
|
/* 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 (task);
|
|
return;
|
|
}
|
|
|
|
case MM_MODEM_STATE_INITIALIZING:
|
|
g_task_return_new_error (task,
|
|
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_task_return_boolean (task, TRUE);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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 (self->priv->modem_dbus_skeleton)),
|
|
mm_gdbus_modem_get_revision (
|
|
MM_GDBUS_MODEM (self->priv->modem_dbus_skeleton)),
|
|
mm_gdbus_modem_get_model (
|
|
MM_GDBUS_MODEM (self->priv->modem_dbus_skeleton)),
|
|
mm_gdbus_modem_get_manufacturer (
|
|
MM_GDBUS_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 (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_3GPP_INITIAL_EPS_BEARER:
|
|
g_clear_object (&self->priv->modem_3gpp_initial_eps_bearer);
|
|
self->priv->modem_3gpp_initial_eps_bearer = g_value_dup_object (value);
|
|
break;
|
|
case PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE:
|
|
self->priv->modem_cdma_cdma1x_registration_state = g_value_get_enum (value);
|
|
break;
|
|
case PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE:
|
|
self->priv->modem_cdma_evdo_registration_state = g_value_get_enum (value);
|
|
break;
|
|
case PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED:
|
|
self->priv->modem_cdma_cdma1x_network_supported = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED:
|
|
self->priv->modem_cdma_evdo_network_supported = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_MODEM_MESSAGING_SMS_LIST:
|
|
g_clear_object (&self->priv->modem_messaging_sms_list);
|
|
self->priv->modem_messaging_sms_list = g_value_dup_object (value);
|
|
break;
|
|
case PROP_MODEM_MESSAGING_SMS_PDU_MODE:
|
|
self->priv->modem_messaging_sms_pdu_mode = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE:
|
|
self->priv->modem_messaging_sms_default_storage = g_value_get_enum (value);
|
|
break;
|
|
case PROP_MODEM_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS:
|
|
self->priv->modem_location_allow_gps_unmanaged_always = g_value_get_boolean (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_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;
|
|
case PROP_MODEM_SIM_HOT_SWAP_CONFIGURED:
|
|
self->priv->sim_hot_swap_configured = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
|
|
self->priv->periodic_signal_check_disabled = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
|
|
self->priv->periodic_access_tech_check_disabled = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
|
|
self->priv->periodic_call_list_check_disabled = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_MODEM_CARRIER_CONFIG_MAPPING:
|
|
self->priv->carrier_config_mapping = g_value_dup_string (value);
|
|
break;
|
|
case PROP_FLOW_CONTROL:
|
|
self->priv->flow_control = g_value_get_flags (value);
|
|
break;
|
|
case PROP_INDICATORS_DISABLED:
|
|
self->priv->modem_cind_disabled = 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_3GPP_INITIAL_EPS_BEARER:
|
|
g_value_set_object (value, self->priv->modem_3gpp_initial_eps_bearer);
|
|
break;
|
|
case PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE:
|
|
g_value_set_enum (value, self->priv->modem_cdma_cdma1x_registration_state);
|
|
break;
|
|
case PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE:
|
|
g_value_set_enum (value, self->priv->modem_cdma_evdo_registration_state);
|
|
break;
|
|
case PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED:
|
|
g_value_set_boolean (value, self->priv->modem_cdma_cdma1x_network_supported);
|
|
break;
|
|
case PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED:
|
|
g_value_set_boolean (value, self->priv->modem_cdma_evdo_network_supported);
|
|
break;
|
|
case PROP_MODEM_MESSAGING_SMS_LIST:
|
|
g_value_set_object (value, self->priv->modem_messaging_sms_list);
|
|
break;
|
|
case PROP_MODEM_MESSAGING_SMS_PDU_MODE:
|
|
g_value_set_boolean (value, self->priv->modem_messaging_sms_pdu_mode);
|
|
break;
|
|
case PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE:
|
|
g_value_set_enum (value, self->priv->modem_messaging_sms_default_storage);
|
|
break;
|
|
case PROP_MODEM_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS:
|
|
g_value_set_boolean (value, self->priv->modem_location_allow_gps_unmanaged_always);
|
|
break;
|
|
case PROP_MODEM_VOICE_CALL_LIST:
|
|
g_value_set_object (value, self->priv->modem_voice_call_list);
|
|
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;
|
|
case PROP_MODEM_SIM_HOT_SWAP_CONFIGURED:
|
|
g_value_set_boolean (value, self->priv->sim_hot_swap_configured);
|
|
break;
|
|
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
|
|
g_value_set_boolean (value, self->priv->periodic_signal_check_disabled);
|
|
break;
|
|
case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
|
|
g_value_set_boolean (value, self->priv->periodic_access_tech_check_disabled);
|
|
break;
|
|
case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
|
|
g_value_set_boolean (value, self->priv->periodic_call_list_check_disabled);
|
|
break;
|
|
case PROP_MODEM_CARRIER_CONFIG_MAPPING:
|
|
g_value_set_string (value, self->priv->carrier_config_mapping);
|
|
break;
|
|
case PROP_FLOW_CONTROL:
|
|
g_value_set_flags (value, self->priv->flow_control);
|
|
break;
|
|
case PROP_INDICATORS_DISABLED:
|
|
g_value_set_boolean (value, self->priv->modem_cind_disabled);
|
|
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;
|
|
self->priv->periodic_signal_check_disabled = FALSE;
|
|
self->priv->periodic_access_tech_check_disabled = FALSE;
|
|
self->priv->periodic_call_list_check_disabled = FALSE;
|
|
self->priv->modem_cmer_enable_mode = MM_3GPP_CMER_MODE_NONE;
|
|
self->priv->modem_cmer_disable_mode = MM_3GPP_CMER_MODE_NONE;
|
|
self->priv->modem_cmer_ind = MM_3GPP_CMER_IND_NONE;
|
|
self->priv->flow_control = MM_FLOW_CONTROL_NONE;
|
|
}
|
|
|
|
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->sim_hot_swap_ports_ctx)
|
|
ports_context_unref (self->priv->sim_hot_swap_ports_ctx);
|
|
|
|
if (self->priv->in_call_ports_ctx)
|
|
ports_context_unref (self->priv->in_call_ports_ctx);
|
|
|
|
if (self->priv->modem_3gpp_registration_regex)
|
|
mm_3gpp_creg_regex_destroy (self->priv->modem_3gpp_registration_regex);
|
|
|
|
g_free (self->priv->carrier_config_mapping);
|
|
|
|
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_signal_dbus_skeleton) {
|
|
mm_iface_modem_signal_shutdown (MM_IFACE_MODEM_SIGNAL (object));
|
|
g_clear_object (&self->priv->modem_signal_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);
|
|
}
|
|
|
|
if (self->priv->modem_firmware_dbus_skeleton) {
|
|
mm_iface_modem_firmware_shutdown (MM_IFACE_MODEM_FIRMWARE (object));
|
|
g_clear_object (&self->priv->modem_firmware_dbus_skeleton);
|
|
}
|
|
|
|
g_clear_object (&self->priv->modem_3gpp_initial_eps_bearer);
|
|
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->load_unlock_retries = load_unlock_retries;
|
|
iface->load_unlock_retries_finish = load_unlock_retries_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 = modem_load_power_state;
|
|
iface->load_power_state_finish = modem_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->check_for_sim_swap = modem_check_for_sim_swap;
|
|
iface->check_for_sim_swap_finish = modem_check_for_sim_swap_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;
|
|
iface->load_eps_ue_mode_operation = modem_3gpp_load_eps_ue_mode_operation;
|
|
iface->load_eps_ue_mode_operation_finish = modem_3gpp_load_eps_ue_mode_operation_finish;
|
|
|
|
/* Enabling steps */
|
|
iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
|
|
iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
|
|
iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
|
|
iface->enable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
|
|
iface->setup_unsolicited_registration_events = modem_3gpp_setup_unsolicited_registration_events;
|
|
iface->setup_unsolicited_registration_events_finish = modem_3gpp_setup_unsolicited_registration_events_finish;
|
|
iface->enable_unsolicited_registration_events = modem_3gpp_enable_unsolicited_registration_events;
|
|
iface->enable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish;
|
|
|
|
/* Disabling steps */
|
|
iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
|
|
iface->disable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
|
|
iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
|
|
iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
|
|
iface->disable_unsolicited_registration_events = modem_3gpp_disable_unsolicited_registration_events;
|
|
iface->disable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish;
|
|
iface->cleanup_unsolicited_registration_events = modem_3gpp_cleanup_unsolicited_registration_events;
|
|
iface->cleanup_unsolicited_registration_events_finish = modem_3gpp_cleanup_unsolicited_registration_events_finish;
|
|
|
|
/* Additional actions */
|
|
iface->load_operator_code = modem_3gpp_load_operator_code;
|
|
iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish;
|
|
iface->load_operator_name = modem_3gpp_load_operator_name;
|
|
iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish;
|
|
iface->run_registration_checks = modem_3gpp_run_registration_checks;
|
|
iface->run_registration_checks_finish = modem_3gpp_run_registration_checks_finish;
|
|
iface->register_in_network = modem_3gpp_register_in_network;
|
|
iface->register_in_network_finish = modem_3gpp_register_in_network_finish;
|
|
iface->scan_networks = modem_3gpp_scan_networks;
|
|
iface->scan_networks_finish = modem_3gpp_scan_networks_finish;
|
|
iface->set_eps_ue_mode_operation = modem_3gpp_set_eps_ue_mode_operation;
|
|
iface->set_eps_ue_mode_operation_finish = modem_3gpp_set_eps_ue_mode_operation_finish;
|
|
iface->create_initial_eps_bearer = modem_3gpp_create_initial_eps_bearer;
|
|
}
|
|
|
|
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_events = modem_3gpp_ussd_setup_unsolicited_events;
|
|
iface->setup_unsolicited_events_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish;
|
|
iface->enable_unsolicited_events = modem_3gpp_ussd_enable_unsolicited_events;
|
|
iface->enable_unsolicited_events_finish = modem_3gpp_ussd_enable_disable_unsolicited_events_finish;
|
|
|
|
/* Disabling steps */
|
|
iface->cleanup_unsolicited_events_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish;
|
|
iface->cleanup_unsolicited_events = modem_3gpp_ussd_cleanup_unsolicited_events;
|
|
iface->disable_unsolicited_events = modem_3gpp_ussd_disable_unsolicited_events;
|
|
iface->disable_unsolicited_events_finish = modem_3gpp_ussd_enable_disable_unsolicited_events_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_disable_unsolicited_events_finish;
|
|
iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
|
|
iface->disable_unsolicited_events_finish = modem_voice_enable_disable_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->setup_in_call_unsolicited_events = modem_voice_setup_in_call_unsolicited_events;
|
|
iface->setup_in_call_unsolicited_events_finish = modem_voice_setup_cleanup_in_call_unsolicited_events_finish;
|
|
iface->cleanup_in_call_unsolicited_events = modem_voice_cleanup_in_call_unsolicited_events;
|
|
iface->cleanup_in_call_unsolicited_events_finish = modem_voice_setup_cleanup_in_call_unsolicited_events_finish;
|
|
|
|
iface->create_call = modem_voice_create_call;
|
|
iface->load_call_list = modem_voice_load_call_list;
|
|
iface->load_call_list_finish = modem_voice_load_call_list_finish;
|
|
iface->hold_and_accept = modem_voice_hold_and_accept;
|
|
iface->hold_and_accept_finish = modem_voice_hold_and_accept_finish;
|
|
iface->hangup_and_accept = modem_voice_hangup_and_accept;
|
|
iface->hangup_and_accept_finish = modem_voice_hangup_and_accept_finish;
|
|
iface->hangup_all = modem_voice_hangup_all;
|
|
iface->hangup_all_finish = modem_voice_hangup_all_finish;
|
|
iface->join_multiparty = modem_voice_join_multiparty;
|
|
iface->join_multiparty_finish = modem_voice_join_multiparty_finish;
|
|
iface->leave_multiparty = modem_voice_leave_multiparty;
|
|
iface->leave_multiparty_finish = modem_voice_leave_multiparty_finish;
|
|
iface->transfer = modem_voice_transfer;
|
|
iface->transfer_finish = modem_voice_transfer_finish;
|
|
iface->call_waiting_setup = modem_voice_call_waiting_setup;
|
|
iface->call_waiting_setup_finish = modem_voice_call_waiting_setup_finish;
|
|
iface->call_waiting_query = modem_voice_call_waiting_query;
|
|
iface->call_waiting_query_finish = modem_voice_call_waiting_query_finish;
|
|
}
|
|
|
|
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_3GPP_INITIAL_EPS_BEARER,
|
|
MM_IFACE_MODEM_3GPP_INITIAL_EPS_BEARER);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE,
|
|
MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE,
|
|
MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED,
|
|
MM_IFACE_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED,
|
|
MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_MESSAGING_SMS_LIST,
|
|
MM_IFACE_MODEM_MESSAGING_SMS_LIST);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_MESSAGING_SMS_PDU_MODE,
|
|
MM_IFACE_MODEM_MESSAGING_SMS_PDU_MODE);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE,
|
|
MM_IFACE_MODEM_MESSAGING_SMS_DEFAULT_STORAGE);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS,
|
|
MM_IFACE_MODEM_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS);
|
|
|
|
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_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);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_SIM_HOT_SWAP_CONFIGURED,
|
|
MM_IFACE_MODEM_SIM_HOT_SWAP_CONFIGURED);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
|
|
MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
|
|
MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
|
|
MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED);
|
|
|
|
g_object_class_override_property (object_class,
|
|
PROP_MODEM_CARRIER_CONFIG_MAPPING,
|
|
MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING);
|
|
|
|
properties[PROP_FLOW_CONTROL] =
|
|
g_param_spec_flags (MM_BROADBAND_MODEM_FLOW_CONTROL,
|
|
"Flow control",
|
|
"Flow control settings to use in the connected TTY",
|
|
MM_TYPE_FLOW_CONTROL,
|
|
MM_FLOW_CONTROL_NONE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_FLOW_CONTROL, properties[PROP_FLOW_CONTROL]);
|
|
|
|
properties[PROP_INDICATORS_DISABLED] =
|
|
g_param_spec_boolean (MM_BROADBAND_MODEM_INDICATORS_DISABLED,
|
|
"Disable indicators",
|
|
"Avoid explicitly setting up +CIND URCs",
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_INDICATORS_DISABLED, properties[PROP_INDICATORS_DISABLED]);
|
|
}
|