Files
ModemManager/src/mm-broadband-modem.c
T
Dominique Martinet 04c9797456 modem-helpers: fix multiple C*REG events in single URC
When receiving a message from modem containing multiple CREG events that
have different format, the parsing happens in the order CREG regexes are
registered, and not in the order the modem sent the events.

That means that when receiving the following URC:
<CR><LF>+CEREG:  2<CR><LF><CR><LF>+CNSMOD: 8<CR><LF><CR><LF>+CEREG: 1,AAAA,01234567,7<CR><LF>
we'd incorrectly parse the `CEREG: 1` before `CEREG: 2`, and remember
the state as searching (2) instead of home (1), which can cause trouble
connecting because of waiting for state to go home which would never
happen again.

This fixes that problem by merging all the regexes together, so the
parsing should properly process the events in matching order in
parse_unsolicited()

Unfortunately changing the regex like this means that
mm_3gpp_parse_creg_response() match info handling also needs to be
reworked: matches number are increased for each regex, so we can
reliably know which regex was matched based on match count.
This is actually cleaner than the old approach that just guessed if nth
field with "item_is_lac_not_stat()", but requires careful testing.

This was validated checking that tests fail if any of the indices is
changed by iterating over them all and running the test:
grep -n 'n_matches -' src/mm-modem-helpers.c | sed -e 's/:.*//' \
	| while read lineno; do
		echo $lineno
		git show HEAD:src/mm-modem-helpers.c \
			| awk -v l=$lineno '{ if (NR == l) {
					print $1,$2,$3,$4,$5 - 1,";"
				} else {
					print
			}}' > src/mm-modem-helpers.c
		ninja -C build src/tests/test-modem-helpers || break
		build/src/tests/test-modem-helpers && break
	done

A test case was added to make this work, as the Samsung Wave S8500
sample 'act' doesn't seem to actually be a technology value?

(case 36/42 could actually be merged but were kept separate on purpose
for easier maintenance/testing)

Fixes: #1087
Signed-off-by: Dominique Martinet <dominique.martinet@atmark-techno.com>
2026-09-08 18:51:58 +02:00

14941 lines
534 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) 2015 Marco Bascetta <marco.bascetta@sadel.it>
* Copyright (C) 2019 Purism SPC
* Copyright (C) 2011 - 2022 Google, Inc.
* Copyright (c) 2022 Qualcomm Innovation Center, Inc.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include <ModemManager-tags.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-profile-manager.h"
#include "mm-iface-modem-3gpp-ussd.h"
#include "mm-iface-modem-cdma.h"
#include "mm-iface-modem-cell-broadcast.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-sar.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-cbm-list.h"
#include "mm-cbm-part.h"
#include "mm-call-at.h"
#include "mm-sms-list.h"
#include "mm-sms-part-3gpp.h"
#include "mm-sms-at.h"
#include "mm-call-list.h"
#include "mm-base-sim.h"
#include "mm-log-object.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"
#include "mm-bind.h"
static void iface_modem_init (MMIfaceModemInterface *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gppInterface *iface);
static void iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManagerInterface *iface);
static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssdInterface *iface);
static void iface_modem_cdma_init (MMIfaceModemCdmaInterface *iface);
static void iface_modem_cell_broadcast_init (MMIfaceModemCellBroadcastInterface *iface);
static void iface_modem_simple_init (MMIfaceModemSimpleInterface *iface);
static void iface_modem_location_init (MMIfaceModemLocationInterface *iface);
static void iface_modem_messaging_init (MMIfaceModemMessagingInterface *iface);
static void iface_modem_voice_init (MMIfaceModemVoiceInterface *iface);
static void iface_modem_time_init (MMIfaceModemTimeInterface *iface);
static void iface_modem_signal_init (MMIfaceModemSignalInterface *iface);
static void iface_modem_oma_init (MMIfaceModemOmaInterface *iface);
static void iface_modem_firmware_init (MMIfaceModemFirmwareInterface *iface);
static void iface_modem_sar_init (MMIfaceModemSarInterface *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_PROFILE_MANAGER, iface_modem_3gpp_profile_manager_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_CELL_BROADCAST, iface_modem_cell_broadcast_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_SAR, iface_modem_sar_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_PROFILE_MANAGER_DBUS_SKELETON,
PROP_MODEM_3GPP_USSD_DBUS_SKELETON,
PROP_MODEM_CDMA_DBUS_SKELETON,
PROP_MODEM_CELL_BROADCAST_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_SAR_DBUS_SKELETON,
PROP_MODEM_SIM,
PROP_MODEM_SIM_SLOTS,
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_5GS_NETWORK_SUPPORTED,
PROP_MODEM_3GPP_IGNORED_FACILITY_LOCKS,
PROP_MODEM_3GPP_INITIAL_EPS_BEARER,
PROP_MODEM_3GPP_PACKET_SERVICE_STATE,
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_CELL_BROADCAST_CBM_LIST,
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_PERIODIC_SIGNAL_CHECK_DISABLED,
PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED,
PROP_MODEM_CARRIER_CONFIG_MAPPING,
PROP_MODEM_FIRMWARE_IGNORE_CARRIER,
PROP_FLOW_CONTROL,
PROP_INDICATORS_DISABLED,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
#if defined WITH_SUSPEND_RESUME
enum {
SIGNAL_SYNC_NEEDED,
SIGNAL_LAST
};
static guint signals[SIGNAL_LAST];
#endif
/* 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 periodic_signal_check_disabled;
gboolean periodic_access_tech_check_disabled;
/*<--- Modem interface --->*/
/* Properties */
GObject *modem_dbus_skeleton;
MMBaseSim *modem_sim;
GPtrArray *modem_sim_slots;
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;
MM3gppCgerepMode modem_cgerep_enable_mode;
MM3gppCgerepMode modem_cgerep_disable_mode;
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;
gboolean modem_3gpp_5gs_network_supported;
/* Implementation helpers */
GRegex *modem_3gpp_registration_regex;
MMModem3gppFacility modem_3gpp_ignored_facility_locks;
MMBaseBearer *modem_3gpp_initial_eps_bearer;
MMModem3gppPacketServiceState modem_3gpp_packet_service_state;
gboolean initial_eps_bearer_cid_support_checked;
gint initial_eps_bearer_cid;
/*<--- Modem 3GPP Profile Manager interface --->*/
/* Properties */
GObject *modem_3gpp_profile_manager_dbus_skeleton;
/*<--- 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 indication_call_list_reload_enabled;
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;
/*<--- Modem Sar interface --->*/
/* Properties */
GObject *modem_sar_dbus_skeleton;
/*<--- Modem CellBroadcast interface --->*/
/* Properties */
GObject *modem_cell_broadcast_dbus_skeleton;
MMCbmList *modem_cell_broadcast_cbm_list;
gboolean modem_firmware_ignore_carrier;
};
/*****************************************************************************/
/* 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_dispose (PortsContext *ctx)
{
if (ctx->primary && ctx->primary_open) {
mm_port_serial_close (MM_PORT_SERIAL (ctx->primary));
ctx->primary_open = FALSE;
}
if (ctx->secondary && ctx->secondary_open) {
mm_port_serial_close (MM_PORT_SERIAL (ctx->secondary));
ctx->secondary_open = FALSE;
}
if (ctx->qcdm && ctx->qcdm_open) {
mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
ctx->qcdm_open = FALSE;
}
}
static void
ports_context_unref (PortsContext *ctx)
{
if (g_atomic_int_dec_and_test (&ctx->ref_count)) {
ports_context_dispose (ctx);
g_clear_object (&ctx->primary);
g_clear_object (&ctx->secondary);
g_clear_object (&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;
}
/*****************************************************************************/
/* 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_obj_dbg (self, "creating broadband bearer in broadband modem...");
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
props,
NULL, /* cancellable */
(GAsyncReadyCallback)broadband_bearer_new_ready,
task);
}
/*****************************************************************************/
/* Create Bearer List (Modem interface) */
static MMBearerList *
modem_create_bearer_list (MMIfaceModem *self)
{
guint n;
/* The maximum number of available/connected modems is guessed from
* the size of the data ports list. */
n = g_list_length (mm_base_modem_peek_data_ports (MM_BASE_MODEM (self)));
mm_obj_dbg (self, "allowed up to %u active bearers", n);
/* by default, no multiplexing support */
return mm_bearer_list_new (n, 0);
}
/*****************************************************************************/
/* 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),
G_OBJECT (self),
NULL, /* cancellable */
callback,
user_data);
}
/*****************************************************************************/
/* Helper to manage AT-based SIM hot swap ports context */
gboolean
mm_broadband_modem_sim_hot_swap_ports_context_init (MMBroadbandModem *self,
GError **error)
{
PortsContext *ports;
mm_obj_dbg (self, "creating serial ports context for SIM hot swap...");
ports = ports_context_new ();
if (!ports_context_open (self, ports, FALSE, FALSE, FALSE, error)) {
ports_context_unref (ports);
return FALSE;
}
g_assert (!self->priv->sim_hot_swap_ports_ctx);
self->priv->sim_hot_swap_ports_ctx = ports;
return TRUE;
}
void
mm_broadband_modem_sim_hot_swap_ports_context_reset (MMBroadbandModem *self)
{
if (self->priv->sim_hot_swap_ports_ctx) {
mm_obj_dbg (self, "releasing serial ports context for SIM hot swap");
ports_context_unref (self->priv->sim_hot_swap_ports_ctx);
self->priv->sim_hot_swap_ports_ctx = NULL;
}
}
/*****************************************************************************/
/* 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;
gboolean is_2g = FALSE;
gboolean is_3g = FALSE;
gboolean is_4g = FALSE;
gboolean is_5g = FALSE;
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, self, NULL);
if (!modes)
goto out;
/* Process list of modes to gather supported ones */
for (i = 0; i < modes->len; i++) {
if (g_array_index (modes, MMModemMode, i) & MM_MODEM_MODE_2G)
is_2g = TRUE;
if (g_array_index (modes, MMModemMode, i) & MM_MODEM_MODE_3G)
is_3g = TRUE;
if (g_array_index (modes, MMModemMode, i) & MM_MODEM_MODE_4G)
is_4g = TRUE;
if (g_array_index (modes, MMModemMode, i) & MM_MODEM_MODE_5G)
is_5g = TRUE;
}
/* Add capabilities from modes */
if (is_2g || is_3g)
ctx->caps |= MM_MODEM_CAPABILITY_GSM_UMTS;
if (is_4g)
ctx->caps |= MM_MODEM_CAPABILITY_LTE;
if (is_5g)
ctx->caps |= MM_MODEM_CAPABILITY_5GNR;
/* 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
* or 5GNR-only device. We did add the GSM_UMTS capability when +CGSM was
* found, so now we'll check if the device only reports 4G or 5G modes, and
* remove the capability if so.
*
* Note that we don't change the default +CGSM -> GSM/UMTS logic, we just
* fix it up.
*/
if ((is_4g || is_5g) && !is_2g && !is_3g)
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 MMBaseModemAtResponseProcessorResult
parse_caps_gcap (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
const ModemCaps *cap = modem_caps;
guint32 ret = 0;
*result = NULL;
*result_error = NULL;
if (!response)
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
/* 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:") || (error && error->domain == MM_MOBILE_EQUIPMENT_ERROR))
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
while (cap->name) {
if (strstr (response, cap->name))
ret |= cap->bits;
cap++;
}
/* No result built? */
if (ret == 0)
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
*result = g_variant_new_uint32 (ret);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
static MMBaseModemAtResponseProcessorResult
parse_caps_cpin (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
*result = NULL;
*result_error = NULL;
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 MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
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 MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
static MMBaseModemAtResponseProcessorResult
parse_caps_cgmm (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
*result = NULL;
*result_error = NULL;
if (!response)
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
/* This check detects some really old Motorola and Mediatek GPRS dongles
* and phones.
*/
if (strstr (response, "GSM900") ||
strstr (response, "GSM1800") ||
strstr (response, "GSM1900") ||
strstr (response, "GSM850") ||
strstr (response, "MTK2")) {
/* At least, it's a GSM modem */
*result = g_variant_new_uint32 (MM_MODEM_CAPABILITY_GSM_UMTS);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
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)
{
MMBroadbandModem *self;
LoadCapabilitiesContext *ctx;
QcdmResult *result;
gint err = QCDM_SUCCESS;
uint8_t pref = 0;
GError *error = NULL;
GByteArray *response;
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) {
/* Fall back to AT checking */
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "loading current capabilities...");
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (LoadCapabilitiesContext);
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_obj_dbg (self, "loaded manufacturer: %s", manufacturer);
}
return manufacturer;
}
static const MMBaseModemAtCommand manufacturers[] = {
{ "+CGMI", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
{ "+GMI", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
{ NULL }
};
static void
modem_load_manufacturer (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_obj_dbg (self, "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_obj_dbg (self, "loaded model: %s", model);
}
return model;
}
static const MMBaseModemAtCommand models[] = {
{ "+CGMM", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
{ "+GMM", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
{ NULL }
};
static void
modem_load_model (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_obj_dbg (self, "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_obj_dbg (self, "loaded revision: %s", revision);
}
return revision;
}
static const MMBaseModemAtCommand revisions[] = {
{ "+CGMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
{ "+GMR", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
{ NULL }
};
static void
modem_load_revision (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_obj_dbg (self, "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_obj_dbg (self, "loaded equipment identifier: %s", equip_id);
}
return equip_id;
}
static const MMBaseModemAtCommand equipment_identifiers[] = {
{ "+CGSN", 3, TRUE, mm_base_modem_response_processor_string_ignore_at_errors },
{ "+GSN", 3, TRUE, mm_base_modem_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_obj_dbg (self, "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,
&inner_error);
if (!device_identifier) {
g_propagate_error (error, inner_error);
return NULL;
}
mm_obj_dbg (self, "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_obj_dbg (self, "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 MMBaseModemAtResponseProcessorResult
modem_load_own_numbers_continue_on_sim_busy (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
if (error) {
*result = NULL;
if (!g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY) || last_command) {
*result_error = g_error_copy (error);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
}
/* Retry on SIM BUSY errors */
*result_error = NULL;
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
*result = g_variant_new_string (response);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
static const MMBaseModemAtCommand own_numbers_sequence[] = {
{ "+CNUM", 3, FALSE, modem_load_own_numbers_continue_on_sim_busy, 0 },
{ "+CNUM", 3, FALSE, modem_load_own_numbers_continue_on_sim_busy, 3 },
{ "+CNUM", 3, FALSE, modem_load_own_numbers_continue_on_sim_busy, 3 },
{ NULL }
};
static void
modem_load_own_numbers_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
OwnNumbersContext *ctx;
GVariant *result;
GError *error = NULL;
GStrv numbers;
ctx = g_task_get_task_data (task);
result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, &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 (g_variant_get_string (result, NULL));
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_obj_dbg (self, "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_obj_dbg (self, "loading own numbers...");
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
own_numbers_sequence,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
(GAsyncReadyCallback)modem_load_own_numbers_ready,
task);
}
/*****************************************************************************/
/* Check if unlock required (Modem interface) */
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);
} else {
if (result)
lock = mm_parse_cpin_response (result, TRUE);
g_task_return_int (task, lock);
}
g_object_unref (task);
}
static void
modem_load_unlock_required (MMIfaceModem *self,
gboolean last_attempt,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
/* CDMA-only modems don't need this */
if (mm_iface_modem_is_cdma_only (self)) {
mm_obj_dbg (self, "skipping unlock check in CDMA-only modem...");
g_task_return_int (task, MM_MODEM_LOCK_NONE);
g_object_unref (task);
return;
}
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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;
gboolean run_cereg;
gboolean run_c5greg;
} 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_c5greg_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
LoadSupportedModesContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
mm_base_modem_at_command_finish (_self, res, &error);
if (!error)
ctx->mode |= MM_MODEM_MODE_5G;
ctx->run_c5greg = FALSE;
load_supported_modes_step (task);
}
static void
supported_modes_cereg_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
LoadSupportedModesContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
mm_base_modem_at_command_finish (_self, res, &error);
if (!error)
ctx->mode |= MM_MODEM_MODE_4G;
ctx->run_cereg = FALSE;
load_supported_modes_step (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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "assuming device allows (CDMA) 2G network mode");
ctx->mode |= MM_MODEM_MODE_2G;
}
if (self->priv->modem_cdma_evdo_network_supported) {
mm_obj_dbg (self, "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_obj_dbg (self, "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, self, &error);
if (!modes) {
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "device allows (3GPP) 3G networks");
mode |= MM_MODEM_MODE_3G;
}
if (g_strstr_len (lower, -1, "lte")) {
mm_obj_dbg (self, "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_obj_dbg (self, "invalid list of supported networks reported by *CNTI: '%s'", response);
else
ctx->mode |= mode;
} else {
mm_obj_dbg (self, "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;
}
if (ctx->run_cereg) {
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+CEREG=?",
3,
TRUE, /* allow caching */
(GAsyncReadyCallback)supported_modes_cereg_ready,
task);
return;
}
if (ctx->run_c5greg) {
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+C5GREG=?",
3,
TRUE, /* allow caching */
(GAsyncReadyCallback)supported_modes_c5greg_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_obj_dbg (self, "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;
ctx->run_cereg = TRUE;
ctx->run_c5greg = 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, self, &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_obj_dbg (self, "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_obj_dbg (self, "EVDO active pilot RSSI: %ddBm", rssi_dbm);
self->priv->evdo_pilot_rssi = rssi_dbm;
}
qcdm_result_unref (result);
}
typedef struct {
MMIfacePortAt *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)
{
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;
return MM_RSSI_TO_QUALITY (self->priv->evdo_pilot_rssi);
}
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, mm_base_modem_response_processor_string_ignore_at_errors },
{ "+CSQ?", 3, FALSE, mm_base_modem_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),
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)
{
GError *error = NULL;
const gchar *result;
GByteArray *indicators;
guint quality = 0;
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error) {
g_clear_error (&error);
goto try_csq;
}
indicators = mm_3gpp_parse_cind_read_response (result, &error);
if (!indicators) {
mm_obj_dbg (self, "could not parse CIND signal quality results: %s", error->message);
g_clear_error (&error);
goto try_csq;
}
if (indicators->len < self->priv->modem_cind_indicator_signal_quality) {
mm_obj_dbg (self,
"could not parse CIND signal quality results; signal "
"index (%u) outside received range (0-%u)",
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),
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_obj_dbg (self, "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 = 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_obj_dbg (self, "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 (MMBroadbandModem *self,
AccessTechContext *ctx)
{
AccessTechAndMask *tech;
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
guint mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
if (ctx->fallback_mask) {
mm_obj_dbg (self, "fallback access technology: 0x%08x", ctx->fallback_act);
act = ctx->fallback_act;
mask = ctx->fallback_mask;
goto done;
}
mm_obj_dbg (self, "QCDM operating mode: %d", ctx->opmode);
mm_obj_dbg (self, "QCDM system mode: %d", ctx->sysmode);
mm_obj_dbg (self, "QCDM hybrid pref: %d", ctx->hybrid);
mm_obj_dbg (self, "QCDM WCDMA open: %d", ctx->wcdma_open);
mm_obj_dbg (self, "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)
{
MMBroadbandModem *self;
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;
}
self = g_task_get_source_object (task);
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 (MM_BROADBAND_MODEM (self), 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)
{
MMBroadbandModem *self;
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;
}
self = g_task_get_source_object (task);
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 (self, 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_obj_dbg (self, "EVDO registration: %d", self->priv->modem_cdma_evdo_registration_state);
mm_obj_dbg (self, "CDMA1x registration: %d", self->priv->modem_cdma_cdma1x_registration_state);
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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 (MM_BROADBAND_MODEM (self), 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);
}
/*****************************************************************************/
gint
mm_broadband_modem_get_initial_eps_bearer_cid (MMBroadbandModem *self)
{
return self->priv->initial_eps_bearer_cid_support_checked ? self->priv->initial_eps_bearer_cid : -1;
}
/*****************************************************************************/
/* Load initial EPS bearer cid */
static gint
load_initial_eps_bearer_cid_finish (MMBroadbandModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_int (G_TASK (res), error);
}
static void
initial_eps_bearer_cid_cgdcont_test_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
const gchar *response;
GError *error = NULL;
guint min_cid;
response = mm_base_modem_at_command_full_finish (_self, res, &error);
if (!response)
mm_obj_dbg (self, "failed +CGDCONT format check : %s", error->message);
else {
GList *format_list;
format_list = mm_3gpp_parse_cgdcont_test_response (response, self, &error);
if (error)
mm_obj_dbg (self, "error parsing +CGDCONT test response: %s", error->message);
else if (!mm_3gpp_pdp_context_format_list_find_range (format_list, MM_BEARER_IP_FAMILY_IPV4, &min_cid, NULL)) {
/* We check for IPv4 following the assumption that modems will generally support v4, while
* v6 is considered optional. If we ever find a modem supporting v6 exclusively and not v4
* we would need to update this logic to also check for v6. */
mm_obj_dbg (self, "context format check for IP not found");
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unexpected +CGDCONT test response");
} else {
mm_obj_dbg (self, "initial EPS bearer context cid found: %u", min_cid);
}
mm_3gpp_pdp_context_format_list_free (format_list);
}
if (error)
g_task_return_error (task, error);
else
g_task_return_int (task, (gint) min_cid);
g_object_unref (task);
}
static void
load_initial_eps_bearer_cid (MMBroadbandModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/*
* The cid for the attach settings:
* - As per 3GPP specs, it should be cid=0
* - Qualcomm based modems default this to cid=1
*/
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CGDCONT=?",
3,
TRUE, /* cached */
(GAsyncReadyCallback)initial_eps_bearer_cid_cgdcont_test_ready,
task);
}
/*****************************************************************************/
/* Load initial EPS bearer settings currently configured in modem (3GPP interface) */
static MMBearerProperties *
modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
}
static void
load_initial_eps_bearer_get_profile_ready (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
g_autoptr(MM3gppProfile) profile = NULL;
MMBearerProperties *props;
profile = mm_iface_modem_3gpp_profile_manager_get_profile_finish (self, res, &error);
if (!profile) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
props = mm_bearer_properties_new_from_profile (profile, &error);
if (!props)
g_task_return_error (task, error);
else
g_task_return_pointer (task, props, g_object_unref);
g_object_unref (task);
}
static void
load_initial_eps_bearer_profile (GTask *task)
{
MMBroadbandModem *self;
self = g_task_get_source_object (task);
g_assert (self->priv->initial_eps_bearer_cid_support_checked);
if (self->priv->initial_eps_bearer_cid < 0) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"initial EPS bearer context management unsupported");
g_object_unref (task);
return;
}
/* Note that we may be calling this before initializing the 3GPP profile manager interface,
* but it should not be an issue, because the interface initialization exclusively checks
* for the feature support and takes care of creating the DBus skeleton. There is currently
* no explicit state initialized that is required for operations later on. Ideally, though,
* an interface method should not be used unless it is initialized, but in this case it's
* problematic because the profile manager interface is initialized always *after* the 3GPP
* interface. */
mm_iface_modem_3gpp_profile_manager_get_profile (
MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self),
self->priv->initial_eps_bearer_cid,
(GAsyncReadyCallback) load_initial_eps_bearer_get_profile_ready,
task);
}
static void
load_initial_eps_bearer_cid_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
g_autoptr(GError) error = NULL;
g_assert (self->priv->initial_eps_bearer_cid < 0);
self->priv->initial_eps_bearer_cid = MM_BROADBAND_MODEM_GET_CLASS (self)->load_initial_eps_bearer_cid_finish (self, res, &error);
if (error)
mm_obj_dbg (self, "couldn't load initial EPS bearer cid: %s", error->message);
load_initial_eps_bearer_profile (task);
}
static void
modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Lookup which is supposed to be the initial EPS bearer context cid */
if (G_UNLIKELY (!self->priv->initial_eps_bearer_cid_support_checked)) {
self->priv->initial_eps_bearer_cid_support_checked = TRUE;
g_assert (self->priv->initial_eps_bearer_cid < 0);
mm_obj_dbg (self, "looking for the initial EPS bearer context cid,,,");
MM_BROADBAND_MODEM_GET_CLASS (self)->load_initial_eps_bearer_cid (self,
(GAsyncReadyCallback)load_initial_eps_bearer_cid_ready,
task);
return;
}
load_initial_eps_bearer_profile (task);
}
/*****************************************************************************/
/* Load initial EPS bearer properties as agreed with network (3GPP interface) */
static MMBearerProperties *
modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
}
static void
load_initial_eps_cgcontrdp_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
const gchar *response;
g_autofree gchar *apn = NULL;
response = mm_base_modem_at_command_finish (self, res, &error);
if (!response || !mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error))
g_task_return_error (task, error);
else {
MMBearerProperties *props;
props = mm_bearer_properties_new ();
mm_bearer_properties_set_apn (props, apn);
g_task_return_pointer (task, props, g_object_unref);
}
g_object_unref (task);
}
static void
modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
GTask *task;
g_autofree gchar *cmd = NULL;
task = g_task_new (self, NULL, callback, user_data);
g_assert (self->priv->initial_eps_bearer_cid_support_checked);
if (self->priv->initial_eps_bearer_cid < 0) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"initial EPS bearer context ID unknown");
g_object_unref (task);
return;
}
cmd = g_strdup_printf ("+CGCONTRDP=%d", self->priv->initial_eps_bearer_cid);
mm_base_modem_at_command (MM_BASE_MODEM (self),
cmd,
3,
FALSE,
(GAsyncReadyCallback) load_initial_eps_cgcontrdp_ready,
task);
}
/*****************************************************************************/
/* Set initial EPS bearer settings (3GPP interface) */
static gboolean
modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
set_initial_eps_bearer_modify_profile_ready (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
g_autoptr(MM3gppProfile) stored = NULL;
stored = mm_iface_modem_3gpp_profile_manager_set_profile_finish (self, res, &error);
if (!stored)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *_self,
MMBearerProperties *props,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
GTask *task;
MMBearerIpFamily ip_family;
MM3gppProfile *profile = NULL;
task = g_task_new (self, NULL, callback, user_data);
g_assert (self->priv->initial_eps_bearer_cid_support_checked);
if (self->priv->initial_eps_bearer_cid < 0) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"initial EPS bearer context ID unknown");
g_object_unref (task);
return;
}
profile = mm_bearer_properties_peek_3gpp_profile (props);
mm_3gpp_profile_set_profile_id (profile, self->priv->initial_eps_bearer_cid);
ip_family = mm_3gpp_profile_get_ip_type (profile);
if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY)
mm_3gpp_profile_set_ip_type (profile, MM_BEARER_IP_FAMILY_IPV4);
mm_iface_modem_3gpp_profile_manager_set_profile (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self),
profile,
"profile-id",
TRUE,
(GAsyncReadyCallback) set_initial_eps_bearer_modify_profile_ready,
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)
{
gint profile_id;
profile_id = GPOINTER_TO_INT (user_data);
/* If we're told to disconnect a single context and this is not the
* bearer associated to that context, ignore operation */
if ((profile_id != MM_3GPP_PROFILE_ID_UNKNOWN) && (mm_base_bearer_get_profile_id (bearer) != profile_id))
return;
/* If already disconnected, ignore operation */
if (mm_base_bearer_get_status (bearer) == MM_BEARER_STATUS_DISCONNECTED)
return;
mm_obj_msg (bearer, "explicitly disconnected");
mm_base_bearer_report_connection_status (bearer, MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
}
static void
bearer_list_report_disconnections (MMBroadbandModem *self,
gint profile_id)
{
g_autoptr(MMBearerList) list = NULL;
g_object_get (self,
MM_IFACE_MODEM_BEARER_LIST, &list,
NULL);
/* If empty bearer list, nothing else to do */
if (list)
mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_report_disconnected, GINT_TO_POINTER (profile_id));
}
static void
cgev_process_detach (MMBroadbandModem *self,
MM3gppCgev type)
{
if (type == MM_3GPP_CGEV_NW_DETACH) {
mm_obj_msg (self, "network forced PS detach: all contexts have been deactivated");
bearer_list_report_disconnections (self, MM_3GPP_PROFILE_ID_UNKNOWN);
return;
}
if (type == MM_3GPP_CGEV_ME_DETACH) {
mm_obj_msg (self, "mobile equipment forced PS detach: all contexts have been deactivated");
bearer_list_report_disconnections (self, MM_3GPP_PROFILE_ID_UNKNOWN);
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_obj_warn (self, "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_obj_msg (self, "network request to activate context (cid %u)", cid);
break;
case MM_3GPP_CGEV_ME_ACT_PRIMARY:
mm_obj_msg (self, "mobile equipment request to activate context (cid %u)", cid);
break;
case MM_3GPP_CGEV_NW_DEACT_PRIMARY:
mm_obj_msg (self, "network request to deactivate context (cid %u)", cid);
bearer_list_report_disconnections (self, (gint)cid);
break;
case MM_3GPP_CGEV_ME_DEACT_PRIMARY:
mm_obj_msg (self, "mobile equipment request to deactivate context (cid %u)", cid);
bearer_list_report_disconnections (self, (gint)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_obj_warn (self, "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_obj_msg (self, "network request to activate secondary context (cid %u, primary cid %u)", cid, p_cid);
break;
case MM_3GPP_CGEV_ME_ACT_SECONDARY:
mm_obj_msg (self, "mobile equipment request to activate secondary context (cid %u, primary cid %u)", cid, p_cid);
break;
case MM_3GPP_CGEV_NW_DEACT_SECONDARY:
mm_obj_msg (self, "network request to deactivate secondary context (cid %u, primary cid %u)", cid, p_cid);
bearer_list_report_disconnections (self, (gint)cid);
break;
case MM_3GPP_CGEV_ME_DEACT_SECONDARY:
mm_obj_msg (self, "mobile equipment request to deactivate secondary context (cid %u, primary cid %u)", cid, p_cid);
bearer_list_report_disconnections (self, (gint)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_obj_warn (self, "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_obj_msg (self, "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_obj_msg (self, "network request to reactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
else
mm_obj_msg (self, "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_obj_msg (self, "network request to deactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
bearer_list_report_disconnections (self, (gint)cid);
} else
mm_obj_msg (self, "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_obj_msg (self, "mobile equipment request to deactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
bearer_list_report_disconnections (self, (gint)cid);
} else
mm_obj_msg (self, "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_obj_dbg (self, "unhandled +CGEV indication: %s", str);
break;
}
g_free (str);
}
static void
set_cgev_unsolicited_events_handlers (MMBroadbandModem *self,
gboolean enable)
{
MMPortSerialAt *ports[2];
g_autoptr(GRegex) cgev_regex = NULL;
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_obj_dbg (self, "%s 3GPP +CGEV unsolicited events handlers in %s",
enable ? "setting" : "removing",
mm_port_get_device (MM_PORT (ports[i])));
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
cgev_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) cgev_received : NULL,
enable ? self : NULL,
NULL);
}
}
static void
ciev_signal_received (MMBroadbandModem *self,
GMatchInfo *match_info)
{
guint quality;
if (!mm_get_uint_from_match_info (match_info, 2, &quality)) {
mm_obj_dbg (self, "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];
g_autoptr(GRegex) ciev_regex = NULL;
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_obj_dbg (self, "%s 3GPP +CIEV unsolicited events handlers in %s",
enable ? "setting" : "removing",
mm_port_get_device (MM_PORT (ports[i])));
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
ciev_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) ciev_received : NULL,
enable ? self : NULL,
NULL);
}
}
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)
{
MM3gppCgerepMode supported_modes = MM_3GPP_CGEREP_MODE_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_cgerep_test_response (result, self, &supported_modes, &error)) {
mm_obj_dbg (self, "+CGEREP check failed: %s", error->message);
mm_obj_dbg (self, "packet domain event reporting is unsupported");
g_error_free (error);
goto out;
}
aux = mm_3gpp_cgerep_mode_build_string_from_mask (supported_modes);
mm_obj_dbg (self, "supported +CGEREP modes: %s", aux);
g_free (aux);
self->priv->modem_cgerep_supported = TRUE;
if (supported_modes & MM_3GPP_CGEREP_MODE_BUFFER_URCS_IF_LINK_RESERVED)
self->priv->modem_cgerep_enable_mode = MM_3GPP_CGEREP_MODE_BUFFER_URCS_IF_LINK_RESERVED;
else if (supported_modes & MM_3GPP_CGEREP_MODE_DISCARD_URCS_IF_LINK_RESERVED)
self->priv->modem_cgerep_enable_mode = MM_3GPP_CGEREP_MODE_DISCARD_URCS_IF_LINK_RESERVED;
if (supported_modes & MM_3GPP_CGEREP_MODE_DISCARD_URCS)
self->priv->modem_cgerep_disable_mode = MM_3GPP_CGEREP_MODE_DISCARD_URCS;
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, self, &supported_modes, &supported_inds, &error)) {
mm_obj_dbg (self, "+CMER check failed: %s", error->message);
mm_obj_dbg (self, "generic indications are unsupported");
g_error_free (error);
goto out;
}
aux = mm_3gpp_cmer_mode_build_string_from_mask (supported_modes);
mm_obj_dbg (self, "supported +CMER modes: %s", aux);
g_free (aux);
aux = mm_3gpp_cmer_ind_build_string_from_mask (supported_inds);
mm_obj_dbg (self, "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_obj_dbg (self, "+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_obj_dbg (self, "+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_obj_dbg (self, "+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_obj_dbg (self, "+CIND check failed: %s", error->message);
mm_obj_dbg (self, "generic indications are unsupported");
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_obj_dbg (self, "signal quality indications via CIND are supported 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_obj_dbg (self, "roaming indications via CIND are supported 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_obj_dbg (self, "service indications via CIND are supported 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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "%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_obj_dbg (self, "%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_obj_dbg (self, "%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_obj_dbg (self, "%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),
MM_IFACE_PORT_AT (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 = mm_3gpp_build_cgerep_set_request (self->priv->modem_cgerep_enable_mode);
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 = mm_3gpp_build_cgerep_set_request (self->priv->modem_cgerep_disable_mode);
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_obj_dbg (self, "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, self, &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_port_primary (_self);
if (!port) {
g_set_error (&error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No primary AT 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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "assuming full power state in CDMA-only modem...");
g_task_return_int (task, MM_MODEM_POWER_STATE_ON);
g_object_unref (task);
return;
}
mm_obj_dbg (self, "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_obj_dbg (self, "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 */
#define SIM_SWAP_CHECK_LOAD_RETRIES_MAX 3
typedef enum {
SIM_SWAP_CHECK_STEP_FIRST,
SIM_SWAP_CHECK_STEP_ICCID_CHANGED,
SIM_SWAP_CHECK_STEP_IMSI_CHANGED,
SIM_SWAP_CHECK_STEP_LAST,
} SimSwapCheckStep;
typedef struct {
MMBaseSim *sim;
guint retries;
gchar *iccid;
gboolean iccid_check_done;
gchar *imsi;
gboolean imsi_check_done;
SimSwapCheckStep step;
} SimSwapContext;
static void
sim_swap_context_free (SimSwapContext *ctx)
{
g_free (ctx->iccid);
g_free (ctx->imsi);
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 gboolean load_sim_identifier (GTask *task);
static gboolean load_sim_imsi (GTask *task);
static void sim_swap_check_step (GTask *task);
static void
complete_sim_swap_check (GTask *task,
const gchar *current)
{
MMBroadbandModem *self;
SimSwapContext *ctx;
const gchar *cached;
const gchar *str;
gboolean ignore_sim_event = FALSE;
gboolean same;
self = MM_BROADBAND_MODEM (g_task_get_source_object (task));
ctx = g_task_get_task_data (task);
if (ctx->step == SIM_SWAP_CHECK_STEP_ICCID_CHANGED) {
ctx->iccid_check_done = TRUE;
cached = mm_gdbus_sim_get_sim_identifier (MM_GDBUS_SIM (ctx->sim));
str = "identifier";
} else if (ctx->step == SIM_SWAP_CHECK_STEP_IMSI_CHANGED) {
ctx->imsi_check_done = TRUE;
cached = mm_gdbus_sim_get_imsi (MM_GDBUS_SIM (ctx->sim));
str = "imsi";
/* If the modem is locked and we couldn't previously read the IMSI
* (because it was locked) then the IMSI change is probably not a swap.
*/
ignore_sim_event = (self->priv->modem_state == MM_MODEM_STATE_LOCKED && !cached);
} else
g_assert_not_reached();
same = (g_strcmp0 (current, cached) == 0);
if (same) {
mm_obj_info (self, "SIM %s has not changed: %s",
str, mm_log_str_personal_info (current));
} else {
mm_obj_msg (self, "SIM %s has changed: '%s' -> '%s'",
str,
mm_log_str_personal_info (cached ? cached : ""),
mm_log_str_personal_info (current ? current : ""));
}
if (same || ignore_sim_event) {
ctx->step++;
} else {
mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
ctx->step = SIM_SWAP_CHECK_STEP_LAST;
}
sim_swap_check_step (task);
}
static void
load_sim_step_ready (MMBaseSim *sim,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModem *self;
SimSwapContext *ctx;
g_autofree gchar *current = NULL;
g_autoptr(GError) error = NULL;
const gchar *str;
self = MM_BROADBAND_MODEM (g_task_get_source_object (task));
ctx = g_task_get_task_data (task);
if (ctx->step == SIM_SWAP_CHECK_STEP_ICCID_CHANGED) {
current = mm_base_sim_load_sim_identifier_finish (sim, res, &error);
str = "identifier";
} else if (ctx->step == SIM_SWAP_CHECK_STEP_IMSI_CHANGED) {
current = MM_BASE_SIM_GET_CLASS (sim)->load_imsi_finish (sim, res, &error);
str = "imsi";
} else
g_assert_not_reached();
if (error) {
if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
/* Skip checking this field right away */
ctx->step++;
sim_swap_check_step (task);
return;
}
if (ctx->retries > 0) {
mm_obj_warn (self, "could not load SIM %s: %s (%d retries left)",
str, error->message, ctx->retries);
--ctx->retries;
if (ctx->step == SIM_SWAP_CHECK_STEP_ICCID_CHANGED)
g_timeout_add_seconds (1, (GSourceFunc) load_sim_identifier, task);
else if (ctx->step == SIM_SWAP_CHECK_STEP_IMSI_CHANGED)
g_timeout_add_seconds (1, (GSourceFunc) load_sim_imsi, task);
else
g_assert_not_reached();
return;
}
mm_obj_warn (self, "could not load SIM %s: %s", str, error->message);
complete_sim_swap_check (task, NULL);
return;
}
complete_sim_swap_check (task, current);
}
static gboolean
load_sim_identifier (GTask *task)
{
SimSwapContext *ctx;
ctx = g_task_get_task_data (task);
mm_base_sim_load_sim_identifier (ctx->sim,
(GAsyncReadyCallback)load_sim_step_ready,
task);
return G_SOURCE_REMOVE;
}
static gboolean
load_sim_imsi (GTask *task)
{
SimSwapContext *ctx;
ctx = g_task_get_task_data (task);
if (MM_BASE_SIM_GET_CLASS (ctx->sim)->load_imsi &&
MM_BASE_SIM_GET_CLASS (ctx->sim)->load_imsi_finish) {
MM_BASE_SIM_GET_CLASS (ctx->sim)->load_imsi (ctx->sim,
(GAsyncReadyCallback)load_sim_step_ready,
task);
} else {
ctx->step++;
sim_swap_check_step (task);
}
return G_SOURCE_REMOVE;
}
static void
sim_swap_check_step (GTask *task)
{
SimSwapContext *ctx;
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case SIM_SWAP_CHECK_STEP_FIRST:
ctx->step++;
/* fall-through */
case SIM_SWAP_CHECK_STEP_ICCID_CHANGED:
ctx->retries = SIM_SWAP_CHECK_LOAD_RETRIES_MAX;
load_sim_identifier (task);
return;
case SIM_SWAP_CHECK_STEP_IMSI_CHANGED:
ctx->retries = SIM_SWAP_CHECK_LOAD_RETRIES_MAX;
load_sim_imsi (task);
return;
case SIM_SWAP_CHECK_STEP_LAST:
if (!ctx->iccid_check_done && !ctx->imsi_check_done)
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't do either ICCID or IMSI check");
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
g_assert_not_reached ();
}
}
static void
modem_check_for_sim_swap (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
SimSwapContext *ctx;
mm_obj_info (self, "checking if SIM was swapped...");
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (SimSwapContext);
ctx->step = SIM_SWAP_CHECK_STEP_FIRST;
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) {
MMModemState modem_state;
modem_state = MM_MODEM_STATE_UNKNOWN;
g_object_get (self,
MM_IFACE_MODEM_STATE, &modem_state,
NULL);
if (modem_state == MM_MODEM_STATE_FAILED) {
mm_obj_msg (self, "new SIM detected, handle as SIM hot-swap");
mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
g_task_return_boolean (task, TRUE);
} else {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"could not acquire SIM object");
}
g_object_unref (task);
return;
}
sim_swap_check_step (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_obj_dbg (self, "loaded IMEI: %s", imei);
return imei;
}
static void
modem_3gpp_load_imei (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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 */
self,
error))
return NULL;
mm_3gpp_normalize_operator (&operator_code, MM_BROADBAND_MODEM (self)->priv->modem_current_charset, self);
if (operator_code)
mm_obj_dbg (self, "loaded Operator Code: %s", operator_code);
return operator_code;
}
static void
modem_3gpp_load_operator_code (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_obj_dbg (self, "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 */
self,
error))
return NULL;
mm_3gpp_normalize_operator (&operator_name, MM_BROADBAND_MODEM (self)->priv->modem_current_charset, self);
if (operator_name)
mm_obj_dbg (self, "loaded Operator Name: %s", operator_name);
return operator_name;
}
static void
modem_3gpp_load_operator_name (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_obj_dbg (self, "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_obj_dbg (self, "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 setting (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_obj_dbg (self, "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);
}
/*****************************************************************************/
/* Set packet service state (3GPP interface) */
static gboolean
modem_3gpp_set_packet_service_state_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_packet_service_state (MMIfaceModem3gpp *self,
MMModem3gppPacketServiceState state,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autofree gchar *cmd = NULL;
cmd = mm_3gpp_build_cgatt_set_request (state);
mm_base_modem_at_command (MM_BASE_MODEM (self),
cmd,
10,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Unsolicited registration messages handling (3GPP interface) */
static gboolean
modem_3gpp_setup_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_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;
gboolean c5greg = FALSE;
GError *error = NULL;
if (!mm_3gpp_parse_creg_response (match_info,
self,
&state,
&lac,
&cell_id,
&act,
&cgreg,
&cereg,
&c5greg,
&error)) {
mm_obj_warn (self, "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/C5GREG always reports TAC
*/
if (cgreg)
mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), state, FALSE);
else if (cereg) {
tac = lac;
lac = 0;
mm_iface_modem_3gpp_update_eps_registration_state (MM_IFACE_MODEM_3GPP (self), state, FALSE);
} else if (c5greg) {
tac = lac;
lac = 0;
mm_iface_modem_3gpp_update_5gs_registration_state (MM_IFACE_MODEM_3GPP (self), state, FALSE);
} 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, FALSE);
}
/* 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_IFACE (self)->load_access_technologies == modem_load_access_technologies ||
MM_IFACE_MODEM_GET_IFACE (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];
g_autoptr(GRegex) regex = NULL;
guint i;
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 */
regex = mm_3gpp_creg_regex_get (FALSE);
for (i = 0; i < 2; i++) {
if (!ports[i])
continue;
mm_obj_dbg (self, "setting up 3GPP unsolicited registration messages handlers in %s",
mm_port_get_device (MM_PORT (ports[i])));
mm_port_serial_at_add_unsolicited_msg_handler (
MM_PORT_SERIAL_AT (ports[i]),
regex,
(MMPortSerialAtUnsolicitedMsgFn)registration_state_changed,
self,
NULL);
}
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];
g_autoptr(GRegex) regex = NULL;
guint i;
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 */
regex = mm_3gpp_creg_regex_get (FALSE);
for (i = 0; i < 2; i++) {
if (!ports[i])
continue;
mm_obj_dbg (self, "cleaning up unsolicited registration messages handlers in %s",
mm_port_get_device (MM_PORT (ports[i])));
mm_port_serial_at_add_unsolicited_msg_handler (
MM_PORT_SERIAL_AT (ports[i]),
regex,
NULL,
NULL,
NULL);
}
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, self, error);
}
static void
modem_3gpp_scan_networks (MMIfaceModem3gpp *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+COPS=?",
315,
FALSE,
callback,
user_data);
}
/*****************************************************************************/
/* Register in network (3GPP interface) */
typedef struct {
gchar *operator_id;
MMIfacePortAt *port;
} RegisterInNetworkContext;
static void
register_in_network_context_free (RegisterInNetworkContext *ctx)
{
g_free (ctx->operator_id);
g_object_unref (ctx->port);
g_slice_free (RegisterInNetworkContext, ctx);
}
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)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
g_autoptr(GError) error = NULL;
if (!mm_base_modem_at_command_full_finish (_self, res, &error)) {
RegisterInNetworkContext *ctx;
g_autoptr(GError) enc_error = NULL;
g_autofree gchar *operator_id_enc = NULL;
g_autofree gchar *command = NULL;
if (!g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) {
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
return;
}
/* If it failed with an unsupported error, retry with current modem charset */
ctx = g_task_get_task_data (task);
operator_id_enc = mm_modem_charset_str_from_utf8 (ctx->operator_id, self->priv->modem_current_charset, FALSE, &enc_error);
if (!operator_id_enc) {
mm_obj_dbg (self, "couldn't convert operator id to current charset: %s", enc_error->message);
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
return;
}
/* retry only if encoded string is different to the non-encoded one */
if (g_strcmp0 (ctx->operator_id, operator_id_enc) == 0) {
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
return;
}
command = g_strdup_printf ("+COPS=1,2,\"%s\"", operator_id_enc);
mm_base_modem_at_command_full (_self,
ctx->port,
command,
120,
FALSE,
FALSE, /* raw */
g_task_get_cancellable (task),
(GAsyncReadyCallback)cops_set_ready,
task);
return;
}
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)
{
RegisterInNetworkContext *ctx;
GTask *task;
MMIfacePortAt *port;
GError *error = NULL;
g_autofree gchar *command = NULL;
task = g_task_new (self, cancellable, callback, user_data);
port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), &error);
if (!port) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
ctx = g_slice_new0 (RegisterInNetworkContext);
ctx->port = g_object_ref (port);
ctx->operator_id = g_strdup (operator_id);
g_task_set_task_data (task, ctx, (GDestroyNotify)register_in_network_context_free);
/* Trigger automatic network registration if no explicit operator id given */
if (!ctx->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),
port,
"+COPS=0",
120,
FALSE,
FALSE, /* raw */
cancellable,
(GAsyncReadyCallback)cops_set_ready,
task);
return;
}
/* Use the operator id given in ASCII initially */
command = g_strdup_printf ("+COPS=1,2,\"%s\"", ctx->operator_id);
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
port,
command,
120,
FALSE,
FALSE, /* raw */
cancellable,
(GAsyncReadyCallback)cops_ascii_set_ready,
task);
}
/*****************************************************************************/
/* Registration checks (3GPP interface) */
typedef struct {
gboolean is_cs_supported;
gboolean is_ps_supported;
gboolean is_eps_supported;
gboolean is_5gs_supported;
gboolean run_cs;
gboolean run_ps;
gboolean run_eps;
gboolean run_5gs;
gboolean running_cs;
gboolean running_ps;
gboolean running_eps;
gboolean running_5gs;
GError *error_cs;
GError *error_ps;
GError *error_eps;
GError *error_5gs;
} RunRegistrationChecksContext;
static void
run_registration_checks_context_free (RunRegistrationChecksContext *ctx)
{
g_clear_error (&ctx->error_cs);
g_clear_error (&ctx->error_ps);
g_clear_error (&ctx->error_eps);
g_clear_error (&ctx->error_5gs);
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_reload_current_registration_info_ready (MMIfaceModem3gpp *modem,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_iface_modem_3gpp_reload_current_registration_info_finish (modem, res, &error);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void run_registration_checks_context_step (GTask *task);
static void
run_registration_checks_context_set_error (RunRegistrationChecksContext *ctx,
GError *error)
{
g_assert (error != NULL);
if (ctx->running_cs)
ctx->error_cs = error;
else if (ctx->running_ps)
ctx->error_ps = error;
else if (ctx->running_eps)
ctx->error_eps = error;
else if (ctx->running_5gs)
ctx->error_5gs = error;
else
g_assert_not_reached ();
}
static void
registration_status_check_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
g_autoptr(GMatchInfo) match_info = NULL;
RunRegistrationChecksContext *ctx;
const gchar *response;
GError *error = NULL;
gboolean parsed;
gboolean cgreg = FALSE;
gboolean cereg = FALSE;
gboolean c5greg = FALSE;
MMModem3gppRegistrationState state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
gulong lac = 0;
gulong tac = 0;
gulong cid = 0;
ctx = g_task_get_task_data (task);
/* Only one must be running */
g_assert ((ctx->running_cs + ctx->running_ps + ctx->running_eps + ctx->running_5gs) == 1);
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (!response) {
run_registration_checks_context_set_error (ctx, 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 */
if (!g_regex_match (self->priv->modem_3gpp_registration_regex,
response,
0,
&match_info)) {
error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Unknown registration status response: '%s'",
response);
run_registration_checks_context_set_error (ctx, error);
run_registration_checks_context_step (task);
return;
}
parsed = mm_3gpp_parse_creg_response (match_info,
self,
&state,
&lac,
&cid,
&act,
&cgreg,
&cereg,
&c5greg,
&error);
if (!parsed) {
if (!error)
error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Error parsing registration response: '%s'",
response);
run_registration_checks_context_set_error (ctx, 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_obj_dbg (self, "got PS registration state when checking CS registration state");
else if (ctx->running_eps)
mm_obj_dbg (self, "got PS registration state when checking EPS registration state");
else if (ctx->running_5gs)
mm_obj_dbg (self, "got PS registration state when checking 5GS registration state");
mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), state, FALSE);
} else if (cereg) {
tac = lac;
lac = 0;
if (ctx->running_cs)
mm_obj_dbg (self, "got EPS registration state when checking CS registration state");
else if (ctx->running_ps)
mm_obj_dbg (self, "got EPS registration state when checking PS registration state");
else if (ctx->running_5gs)
mm_obj_dbg (self, "got EPS registration state when checking 5GS registration state");
mm_iface_modem_3gpp_update_eps_registration_state (MM_IFACE_MODEM_3GPP (self), state, FALSE);
} else if (c5greg) {
tac = lac;
lac = 0;
if (ctx->running_cs)
mm_obj_dbg (self, "got 5GS registration state when checking CS registration state");
else if (ctx->running_ps)
mm_obj_dbg (self, "got 5GS registration state when checking PS registration state");
else if (ctx->running_eps)
mm_obj_dbg (self, "got 5GS registration state when checking EPS registration state");
mm_iface_modem_3gpp_update_5gs_registration_state (MM_IFACE_MODEM_3GPP (self), state, FALSE);
} else {
if (act == MM_MODEM_ACCESS_TECHNOLOGY_LTE) {
tac = lac;
lac = 0;
}
if (ctx->running_ps)
mm_obj_dbg (self, "got CS registration state when checking PS registration state");
else if (ctx->running_eps)
mm_obj_dbg (self, "got CS registration state when checking EPS registration state");
else if (ctx->running_5gs)
mm_obj_dbg (self, "got CS registration state when checking 5GS registration state");
mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), state, FALSE);
}
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;
ctx->running_5gs = 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 (ctx->run_5gs) {
ctx->running_5gs = TRUE;
ctx->run_5gs = FALSE;
/* Check current 5GS-registration state. */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+C5GREG?",
10,
FALSE,
(GAsyncReadyCallback)registration_status_check_ready,
task);
return;
}
/* If all run checks returned errors we fail */
if ((ctx->is_cs_supported || ctx->is_ps_supported || ctx->is_eps_supported || ctx->is_5gs_supported) &&
(!ctx->is_cs_supported || ctx->error_cs) &&
(!ctx->is_ps_supported || ctx->error_ps) &&
(!ctx->is_eps_supported || ctx->error_eps) &&
(!ctx->is_5gs_supported || ctx->error_5gs)) {
/* When reporting errors, prefer the 5GS, then EPS, then PS, then CS */
if (ctx->error_5gs)
error = g_steal_pointer (&ctx->error_5gs);
else if (ctx->error_eps)
error = g_steal_pointer (&ctx->error_eps);
else if (ctx->error_ps)
error = g_steal_pointer (&ctx->error_ps);
else if (ctx->error_cs)
error = g_steal_pointer (&ctx->error_cs);
else
g_assert_not_reached ();
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* When everything is done, recheck operator info as it may have changed */
mm_iface_modem_3gpp_reload_current_registration_info (
MM_IFACE_MODEM_3GPP (self),
(GAsyncReadyCallback)run_registration_checks_reload_current_registration_info_ready,
task);
}
static void
modem_3gpp_run_registration_checks (MMIfaceModem3gpp *self,
gboolean is_cs_supported,
gboolean is_ps_supported,
gboolean is_eps_supported,
gboolean is_5gs_supported,
GAsyncReadyCallback callback,
gpointer user_data)
{
RunRegistrationChecksContext *ctx;
GTask *task;
ctx = g_new0 (RunRegistrationChecksContext, 1);
ctx->is_cs_supported = is_cs_supported;
ctx->is_ps_supported = is_ps_supported;
ctx->is_eps_supported = is_eps_supported;
ctx->is_5gs_supported = is_5gs_supported;
ctx->run_cs = is_cs_supported;
ctx->run_ps = is_ps_supported;
ctx->run_eps = is_eps_supported;
ctx->run_5gs = is_5gs_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_BIND_TO, G_OBJECT (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 {
MMPortSerialAt *primary;
MMPortSerialAt *secondary; /* optional */
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)
{
g_clear_object (&ctx->primary);
g_clear_object (&ctx->secondary);
g_clear_error (&ctx->cs_error);
g_clear_error (&ctx->ps_error);
g_clear_error (&ctx->eps_error);
g_slice_free (UnsolicitedRegistrationEventsContext, 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_slice_new0 (UnsolicitedRegistrationEventsContext);
ctx->enable = enable;
ctx->run_cs = cs_supported;
ctx->run_ps = ps_supported;
ctx->run_eps = eps_supported;
ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
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 MMBaseModemAtResponseProcessorResult
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)
{
*result_error = NULL;
/* If error, try next command */
if (error) {
*result = NULL;
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
/* Set COMMAND as result! */
*result = g_variant_new_string (command);
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
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;
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_obj_dbg (self, "%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_clear_error (&ctx->cs_error);
else if (ctx->running_ps && ctx->ps_error)
g_clear_error (&ctx->ps_error);
else if (ctx->running_eps && ctx->eps_error)
g_clear_error (&ctx->eps_error);
}
/* 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_obj_dbg (self, "%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 */
}
if (ctx->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),
MM_IFACE_PORT_AT (ctx->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),
MM_IFACE_PORT_AT (ctx->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;
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_IFACE_PORT_AT (ctx->primary),
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_IFACE_PORT_AT (ctx->primary),
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_IFACE_PORT_AT (ctx->primary),
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_task_return_error (task, g_steal_pointer (&ctx->eps_error));
else if (ctx->ps_error)
g_task_return_error (task, g_steal_pointer (&ctx->ps_error));
else if (ctx->cs_error)
g_task_return_error (task, g_steal_pointer (&ctx->cs_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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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;
gchar *quoted_command;
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;
quoted_command = mm_at_quote_string (ctx->command);
at_command = g_strdup_printf ("+CUSD=1,%s,%d",
quoted_command,
MM_MODEM_GSM_USSD_SCHEME_7BIT);
g_free (quoted_command);
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 *self = MM_BROADBAND_MODEM (_self);
g_autoptr(GByteArray) ussd_command = NULL;
/* Encode to the current charset (as per AT+CSCS, which is what most modems
* (except for Huawei it seems) will ask for. */
ussd_command = mm_modem_charset_bytearray_from_utf8 (command, self->priv->modem_current_charset, FALSE, error);
if (!ussd_command) {
g_prefix_error (error, "Failed to encode USSD command: ");
return NULL;
}
/* 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 */
return (gchar *) mm_utils_bin2hexstr (ussd_command->data, ussd_command->len);
}
static gchar *
modem_3gpp_ussd_decode (MMIfaceModem3gppUssd *self,
const gchar *reply,
GError **error)
{
MMBroadbandModem *broadband = MM_BROADBAND_MODEM (self);
guint8 *bin = NULL;
gsize bin_len = 0;
g_autoptr(GByteArray) barray = NULL;
bin = (guint8 *) mm_utils_hexstr2bin (reply, -1, &bin_len, error);
if (!bin) {
g_prefix_error (error, "Couldn't convert HEX string to binary: ");
return NULL;
}
barray = g_byte_array_new_take (bin, bin_len);
/* 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_bytearray_to_utf8 (barray, broadband->priv->modem_current_charset, FALSE, error);
}
/*****************************************************************************/
/* 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, g_steal_pointer (&converted), g_free);
else
g_assert_not_reached ();
g_clear_pointer (&converted, g_free);
return;
}
/* If no pending task, just report the error */
if (error) {
mm_obj_warn (self, "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_obj_dbg (self, "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];
g_autoptr(GRegex) cusd_regex = NULL;
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_obj_dbg (self, "%s unsolicited result code handlers in %s",
enable ? "setting" : "removing",
mm_port_get_device (MM_PORT (ports[i])));
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
cusd_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) cusd_received : NULL,
enable ? self : NULL,
NULL);
}
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,
&error)) {
supported_storages_result_free (result);
g_task_return_error (task, error);
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,
MMSmsStorage *current_storage,
GError **error)
{
gssize result;
result = g_task_propagate_int (G_TASK (res), error);
if (result < 0)
return FALSE;
if (current_storage)
*current_storage = (MMSmsStorage)result;
return TRUE;
}
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_obj_dbg (self, "current storages initialized:");
aux = mm_common_build_sms_storages_string (&mem1, 1);
mm_obj_dbg (self, " mem1 (list/read/delete) storages: '%s'", aux);
g_free (aux);
aux = mm_common_build_sms_storages_string (&mem2, 1);
mm_obj_dbg (self, " mem2 (write/send) storages: '%s'", aux);
g_free (aux);
g_task_return_int (task, mem2);
}
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.
*/
static void
modem_messaging_unlock_storages (MMIfaceModemMessaging *_self,
gboolean mem1,
gboolean mem2)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
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;
}
}
static gboolean
modem_messaging_lock_storages_finish (MMIfaceModemMessaging *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);
}
static void
modem_messaging_lock_storages (MMIfaceModemMessaging *_self,
MMSmsStorage mem1, /* reading/listing/deleting */
MMSmsStorage mem2, /* storing/sending */
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
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,
modem_messaging_lock_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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "PDU mode not supported, will try to use Text mode");
self->priv->modem_messaging_sms_pdu_mode = FALSE;
} else
mm_obj_dbg (self, "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_iface_modem_messaging_unlock_storages (MM_IFACE_MODEM_MESSAGING (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_obj_warn (self, "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_obj_warn (self, "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, self, &error);
if (!part) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error parsing PDU (%d): %s", ctx->idx, error->message);
g_error_free (error);
} else {
mm_obj_dbg (self, "correctly parsed PDU (%d)", ctx->idx);
if (!mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
mm_broadband_modem_create_sms (MM_BROADBAND_MODEM (self)),
part,
MM_SMS_STATE_RECEIVED,
self->priv->modem_messaging_sms_default_storage,
&error)) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error adding SMS (%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 (MMIfaceModemMessaging *messaging,
GAsyncResult *res,
GTask *task)
{
SmsPartContext *ctx;
gchar *command;
GError *error = NULL;
if (!mm_iface_modem_messaging_lock_storages_finish (messaging, 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 (messaging),
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_obj_dbg (self, "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_obj_dbg (self, "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_iface_modem_messaging_lock_storages (MM_IFACE_MODEM_MESSAGING (self),
storage,
MM_SMS_STORAGE_UNKNOWN,
(GAsyncReadyCallback)indication_lock_storages_ready,
task);
}
static void
cds_received (MMPortSerialAt *port,
GMatchInfo *info,
MMBroadbandModem *self)
{
g_autoptr(GError) error = NULL;
MMSmsPart *part;
guint length;
gchar *pdu;
mm_obj_dbg (self, "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, self, &error);
if (!part) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error parsing non-stored PDU: %s", error->message);
} else {
mm_obj_dbg (self, "correctly parsed non-stored PDU");
if (!mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
mm_broadband_modem_create_sms (MM_BROADBAND_MODEM (self)),
part,
MM_SMS_STATE_RECEIVED,
MM_SMS_STORAGE_UNKNOWN,
&error)) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error adding SMS: %s", error->message);
}
}
}
static void
set_messaging_unsolicited_events_handlers (MMIfaceModemMessaging *self,
gboolean enable,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMPortSerialAt *ports[2];
g_autoptr(GRegex) cmti_regex = NULL;
g_autoptr(GRegex) cds_regex = NULL;
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_obj_dbg (self, "%s messaging unsolicited events handlers in %s",
enable ? "setting" : "removing",
mm_port_get_device (MM_PORT (ports[i])));
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);
}
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) */
typedef struct {
MMPortSerialAt *primary;
MMPortSerialAt *secondary;
} MessagingEnableUnsolicitedEventsContext;
static void
messaging_enable_unsolicited_events_context_free (MessagingEnableUnsolicitedEventsContext *ctx)
{
g_clear_object (&ctx->primary);
g_clear_object (&ctx->secondary);
g_slice_free (MessagingEnableUnsolicitedEventsContext, ctx);
}
static gboolean
modem_messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static MMBaseModemAtResponseProcessorResult
cnmi_response_processor (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
*result = NULL;
*result_error = NULL;
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 MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_FAILURE;
}
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
}
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_SUCCESS;
}
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)
{
MessagingEnableUnsolicitedEventsContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
/* 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, &error);
if (error) {
mm_obj_dbg (self, "failed to enable messaging unsolicited events on secondary port %s: %s",
mm_port_get_device (MM_PORT (ctx->secondary)),
error->message);
} else {
mm_obj_dbg (self, "messaging unsolicited events enabled on secondary port %s",
mm_port_get_device (MM_PORT (ctx->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)
{
MessagingEnableUnsolicitedEventsContext *ctx;
GError *error = NULL;
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;
}
ctx = g_task_get_task_data (task);
mm_obj_dbg (self, "messaging unsolicited events enabled on primary port %s",
mm_port_get_device (MM_PORT (ctx->primary)));
/* Try to enable unsolicited events for secondary port */
if (ctx->secondary) {
mm_obj_dbg (self, "enabling messaging unsolicited events on secondary port %s",
mm_port_get_device (MM_PORT (ctx->secondary)));
mm_base_modem_at_sequence_full (
MM_BASE_MODEM (self),
MM_IFACE_PORT_AT (ctx->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)
{
MessagingEnableUnsolicitedEventsContext *ctx;
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (MessagingEnableUnsolicitedEventsContext);
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) messaging_enable_unsolicited_events_context_free);
/* Do nothing if the modem doesn't have any AT port (e.g. it could be
* a QMI modem trying to enable the parent unsolicited messages) */
if (!ctx->primary) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No AT port to enable messaging unsolicited events");
g_object_unref (task);
return;
}
/* Enable unsolicited events for primary port */
mm_obj_dbg (self, "enabling messaging unsolicited events on primary port %s",
mm_port_get_device (MM_PORT (ctx->primary)));
mm_base_modem_at_sequence_full (
MM_BASE_MODEM (self),
MM_IFACE_PORT_AT (ctx->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;
g_autoptr(GRegex) r = NULL;
g_autoptr(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);
return;
}
ctx = g_task_get_task_data (task);
while (g_match_info_matches (match_info)) {
MMSmsPart *part;
guint matches;
guint idx;
g_autofree gchar *number_enc = NULL;
g_autofree gchar *number = NULL;
g_autofree gchar *timestamp = NULL;
g_autofree gchar *text_enc = NULL;
g_autofree gchar *text = NULL;
g_autofree gchar *stat = NULL;
g_autoptr(GByteArray) raw = NULL;
g_autoptr(GError) inner_error = NULL;
matches = g_match_info_get_match_count (match_info);
if (matches != 7) {
mm_obj_dbg (self, "failed to match entire CMGL response (count %d)", matches);
goto next;
}
if (!mm_get_uint_from_match_info (match_info, 1, &idx)) {
mm_obj_dbg (self, "failed to convert message index");
goto next;
}
/* Get part state */
stat = mm_get_string_unquoted_from_match_info (match_info, 2);
if (!stat) {
mm_obj_dbg (self, "failed to get part status");
goto next;
}
/* Get and parse number */
number_enc = mm_get_string_unquoted_from_match_info (match_info, 3);
if (!number_enc) {
mm_obj_dbg (self, "failed to get message sender number");
goto next;
}
number = mm_modem_charset_str_to_utf8 (number_enc, -1, self->priv->modem_current_charset, FALSE, &inner_error);
if (!number) {
mm_obj_dbg (self, "failed to convert message sender number to UTF-8: %s", inner_error->message);
goto next;
}
/* Get and parse timestamp (always expected in ASCII) */
timestamp = mm_get_string_unquoted_from_match_info (match_info, 5);
if (timestamp && !g_str_is_ascii (timestamp)) {
mm_obj_dbg (self, "failed to parse input timestamp as ASCII");
goto next;
}
/* Get and parse text */
text_enc = g_match_info_fetch (match_info, 6);
text = mm_modem_charset_str_to_utf8 (text_enc, -1, self->priv->modem_current_charset, FALSE, &inner_error);
if (!text) {
mm_obj_dbg (self, "failed to convert message text to UTF-8: %s", inner_error->message);
goto next;
}
/* The raw SMS data can only be GSM, UCS2, or unknown (8-bit), so we
* need to convert to UCS2 here.
*/
raw = mm_modem_charset_bytearray_from_utf8 (text, MM_MODEM_CHARSET_UCS2, FALSE, NULL);
g_assert (raw);
/* 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, g_steal_pointer (&number));
mm_sms_part_take_timestamp (part, g_steal_pointer (&timestamp));
mm_sms_part_take_text (part, g_steal_pointer (&text));
mm_sms_part_take_data (part, g_steal_pointer (&raw));
mm_sms_part_set_class (part, -1);
mm_obj_dbg (self, "correctly parsed SMS list entry (%d)", idx);
if (!mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
mm_broadband_modem_create_sms (MM_BROADBAND_MODEM (self)),
part,
sms_state_from_str (stat),
ctx->list_storage,
&error)) {
mm_obj_dbg (self, "failed to add SMS: %s", inner_error->message);
goto next;
}
next:
g_match_info_next (match_info, NULL);
}
/* 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_iface_modem_messaging_unlock_storages (MM_IFACE_MODEM_MESSAGING (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, self, &error);
if (!part) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error parsing PDU (%d): %s", info->index, error->message);
g_clear_error (&error);
} else {
mm_obj_dbg (self, "correctly parsed PDU (%d)", info->index);
if (!mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
mm_broadband_modem_create_sms (MM_BROADBAND_MODEM (self)),
part,
sms_state_from_index (info->status),
ctx->list_storage,
&error)) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error adding SMS (%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 (MMIfaceModemMessaging *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_iface_modem_messaging_lock_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\""),
120,
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_obj_dbg (self, "listing SMS parts in storage '%s'", mm_sms_storage_get_string (storage));
/* First, request to set the proper storage to read from */
mm_iface_modem_messaging_lock_storages (self,
storage,
MM_SMS_STORAGE_UNKNOWN,
(GAsyncReadyCallback)list_parts_lock_storages_ready,
task);
}
/*****************************************************************************/
/* Create SMS */
MMBaseSms *
mm_broadband_modem_create_sms (MMBroadbandModem *self)
{
g_assert (MM_BROADBAND_MODEM_GET_CLASS (self)->create_sms != NULL);
return MM_BROADBAND_MODEM_GET_CLASS (self)->create_sms (self);
}
static MMBaseSms *
modem_messaging_create_sms (MMBroadbandModem *self)
{
MMSmsStorage default_storage;
default_storage = mm_iface_modem_messaging_get_default_storage (MM_IFACE_MODEM_MESSAGING (self));
return mm_sms_at_new (MM_BASE_MODEM (self),
mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self)),
default_storage);
}
/*****************************************************************************/
/* 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 *self,
GAsyncResult *res,
GTask *task)
{
const gchar *response;
GError *error = NULL;
GList *call_info_list = NULL;
response = mm_base_modem_at_command_finish (self, res, &error);
if (!response || !mm_3gpp_parse_clcc_response (response, self, &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_obj_dbg (self, "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];
g_autoptr(GRegex) in_call_event_regex = NULL;
guint i;
in_call_event_regex = mm_call_end_regex_get ();
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_obj_dbg (self, "%s voice in-call unsolicited events handlers in %s",
enable ? "setting" : "removing",
mm_port_get_device (MM_PORT (ports[i])));
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);
}
}
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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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;
gboolean indication_call_list_reload_enabled = FALSE;
g_object_get (self,
MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED, &indication_call_list_reload_enabled,
NULL);
if (indication_call_list_reload_enabled) {
mm_obj_dbg (self, "call waiting, refreshing call list");
mm_iface_modem_voice_reload_all_calls (MM_IFACE_MODEM_VOICE (self), NULL, NULL);
return;
}
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_obj_dbg (self, "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;
gboolean indication_call_list_reload_enabled = FALSE;
g_object_get (self,
MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED, &indication_call_list_reload_enabled,
NULL);
if (indication_call_list_reload_enabled) {
mm_obj_dbg (self, "ringing, refreshing call list");
mm_iface_modem_voice_reload_all_calls (MM_IFACE_MODEM_VOICE (self), NULL, NULL);
return;
}
call_info.index = 0;
call_info.direction = MM_CALL_DIRECTION_INCOMING;
call_info.state = MM_CALL_STATE_RINGING_IN;
call_info.number = NULL;
mm_obj_dbg (self, "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;
gboolean indication_call_list_reload_enabled = FALSE;
g_object_get (self,
MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED, &indication_call_list_reload_enabled,
NULL);
if (indication_call_list_reload_enabled) {
mm_obj_dbg (self, "ringing, refreshing call list");
mm_iface_modem_voice_reload_all_calls (MM_IFACE_MODEM_VOICE (self), NULL, NULL);
return;
}
/* We could have "VOICE" or "DATA". Now consider only "VOICE" */
str = mm_get_string_unquoted_from_match_info (info, 1);
mm_obj_dbg (self, "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;
gboolean indication_call_list_reload_enabled = FALSE;
g_object_get (self,
MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED, &indication_call_list_reload_enabled,
NULL);
if (indication_call_list_reload_enabled) {
mm_obj_dbg (self, "ringing, refreshing call list");
mm_iface_modem_voice_reload_all_calls (MM_IFACE_MODEM_VOICE (self), NULL, NULL);
return;
}
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_obj_dbg (self, "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];
g_autoptr(GRegex) cring_regex = NULL;
g_autoptr(GRegex) ring_regex = NULL;
g_autoptr(GRegex) clip_regex = NULL;
g_autoptr(GRegex) ccwa_regex = NULL;
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_obj_dbg (self, "%s voice unsolicited events handlers in %s",
enable ? "setting" : "removing",
mm_port_get_device (MM_PORT (ports[i])));
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);
}
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_obj_dbg (self, "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_obj_dbg (self, "%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_obj_dbg (self, "%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_obj_dbg (self, "%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_obj_dbg (self, "%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_obj_dbg (self, "%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_obj_dbg (self, "%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),
MM_IFACE_PORT_AT (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,
const guint dtmf_tone_duration)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
return mm_call_at_new (MM_BASE_MODEM (self),
G_OBJECT (self),
direction,
number,
dtmf_tone_duration,
/* 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, self, 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_obj_dbg (self, "loaded ESN: %s", esn);
return esn;
}
static void
modem_cdma_load_esn (MMIfaceModemCdma *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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 {
g_autoptr(GRegex) r = NULL;
g_autoptr(GMatchInfo) match_info = NULL;
/* 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;
}
}
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)
{
MMBroadbandModem *self;
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;
self = g_task_get_source_object (task);
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "CDMA 1x Status RX state: %d", rxstate);
mm_obj_dbg (self, "CDMA 1x Status SID: %d", sid);
mm_obj_dbg (self, "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_obj_dbg (self, "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 {
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 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_obj_warn (self, "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)
{
DetailedRegistrationStateContext *ctx;
GTask *task;
/* Setup context */
ctx = g_new0 (DetailedRegistrationStateContext, 1);
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, g_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_obj_dbg (self, "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_IFACE (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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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, self);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Check again in a few seconds. */
mm_obj_dbg (self, "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 *self = MM_BROADBAND_MODEM (_self);
RegisterInCdmaNetworkContext *ctx;
GTask *task;
/* (Try to) cancel previous registration request */
if (self->priv->modem_cdma_pending_registration_cancellable) {
g_cancellable_cancel (self->priv->modem_cdma_pending_registration_cancellable);
g_clear_object (&self->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 */
self->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 currently active channels (CellBroadcast interface) */
static GArray *
modem_cell_broadcast_load_channels_finish (MMIfaceModemCellBroadcast *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
cscb_channels_format_check_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
const gchar *response;
GError *error = NULL;
GArray *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;
}
/* Parse reply */
result = mm_3gpp_parse_cscb_response (response, &error);
if (!result) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_pointer (task,
result,
(GDestroyNotify)g_array_unref);
g_object_unref (task);
}
static void
modem_cell_broadcast_load_channels (MMIfaceModemCellBroadcast *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Load configured channels */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CSCB?",
3,
TRUE,
(GAsyncReadyCallback)cscb_channels_format_check_ready,
task);
}
/*****************************************************************************/
static gboolean
modem_cell_broadcast_setup_cleanup_unsolicited_events_finish (MMIfaceModemCellBroadcast *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
cbc_cbm_received (MMPortSerialAt *port,
GMatchInfo *info,
MMBroadbandModem *self)
{
g_autoptr(GError) error = NULL;
MMCbmPart *part;
guint length;
gchar *pdu;
mm_obj_dbg (self, "got new cell broadcast 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_cbm_part_new_from_pdu (pdu, self, &error);
if (!part) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error parsing PDU: %s", error->message);
} else {
mm_obj_dbg (self, "correctly parsed PDU");
if (!mm_iface_modem_cell_broadcast_take_part (MM_IFACE_MODEM_CELL_BROADCAST (self),
G_OBJECT (self),
part,
MM_CBM_STATE_RECEIVED,
&error)) {
/* Don't treat the error as critical */
mm_obj_dbg (self, "error adding CBM: %s", error->message);
}
}
}
static void
set_cell_broadcast_unsolicited_events_handlers (MMIfaceModemCellBroadcast *self,
gboolean enable,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMPortSerialAt *ports[2];
g_autoptr(GRegex) cbm_regex = NULL;
guint i;
GTask *task;
cbm_regex = mm_3gpp_cbm_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 cell broadcast unsolicited events handler for port primary and secondary */
for (i = 0; i < 2; i++) {
if (!ports[i])
continue;
/* Set/unset unsolicited CBM event handler */
mm_obj_dbg (self, "%s cell broadcast unsolicited events handlers in %s",
enable ? "setting" : "removing",
mm_port_get_device (MM_PORT (ports[i])));
mm_port_serial_at_add_unsolicited_msg_handler (
ports[i],
cbm_regex,
enable ? (MMPortSerialAtUnsolicitedMsgFn) cbc_cbm_received : NULL,
enable ? self : NULL,
NULL);
}
task = g_task_new (self, NULL, callback, user_data);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_cell_broadcast_setup_unsolicited_events (MMIfaceModemCellBroadcast *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
set_cell_broadcast_unsolicited_events_handlers (self, TRUE, callback, user_data);
}
static void
modem_cell_broadcast_cleanup_unsolicited_events (MMIfaceModemCellBroadcast *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
set_cell_broadcast_unsolicited_events_handlers (self, FALSE, callback, user_data);
}
/***********************************************************************************/
/* Get channels (CellBroadcast interface) */
static gboolean
modem_cell_broadcast_set_channels_finish (MMIfaceModemCellBroadcast *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
modem_cell_broadcast_set_channels_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_cell_broadcast_set_channels (MMIfaceModemCellBroadcast *self,
GArray *channels,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_autoptr (GString) cmd = g_string_new ("+CSCB=0,\"");
guint i;
task = g_task_new (self, NULL, callback, user_data);
for (i = 0; i < channels->len; i++) {
MMCellBroadcastChannels ch = g_array_index (channels, MMCellBroadcastChannels, i);
if (i > 0)
g_string_append_c (cmd, ',');
if (ch.start == ch.end)
g_string_append_printf (cmd, "%u", ch.start);
else
g_string_append_printf (cmd, "%u-%u", ch.start, ch.end);
}
g_string_append (cmd, "\",\"\"");
mm_obj_dbg (self, "Setting channels...");
mm_base_modem_at_command (
MM_BASE_MODEM (self),
cmd->str,
3,
FALSE,
(GAsyncReadyCallback)modem_cell_broadcast_set_channels_ready,
task);
}
/*********************************************************/
/* Check CellBroadcast support (CellBroadcast interface) */
static gboolean
modem_cell_broadcast_check_support_finish (MMIfaceModemCellBroadcast *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
cscb_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_cell_broadcast_check_support (MMIfaceModemCellBroadcast *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Check cell broadcast support */
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CSCB=?",
3,
TRUE,
(GAsyncReadyCallback)cscb_format_check_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)
{
MMIfacePortAt *port;
GError *error = NULL;
port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), &error);
if (!port) {
g_task_report_error (self, callback, user_data, modem_time_check_support, error);
return;
}
if (mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (MM_PORT (port)),
ID_MM_AT_NETWORK_TIME_BROKEN)) {
g_task_report_new_error (self,
callback,
user_data,
modem_time_check_support,
MM_MOBILE_EQUIPMENT_ERROR,
MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED,
"AT network time tagged as broken");
return;
}
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,
MMSignal **nr5g,
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, self, gsm, umts, lte, error))
return FALSE;
if (cdma)
*cdma = NULL;
if (evdo)
*evdo = NULL;
if (nr5g)
*nr5g = 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);
}
/*****************************************************************************/
/* Check support (3GPP profile management interface) */
static gboolean
modem_3gpp_profile_manager_check_support_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
gchar **index_field,
GError **error)
{
if (g_task_propagate_boolean (G_TASK (res), error)) {
*index_field = g_strdup ("profile-id");;
return TRUE;
}
return FALSE;
}
static void
profile_manager_cgdcont_test_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
mm_base_modem_at_command_finish (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_3gpp_profile_manager_check_support (MMIfaceModem3gppProfileManager *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
if (!mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) {
g_task_return_boolean (task, FALSE);
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)profile_manager_cgdcont_test_ready,
task);
}
/*****************************************************************************/
/* List profiles (3GPP profile management interface) */
typedef struct {
GList *profiles;
} ListProfilesContext;
static void
list_profiles_context_free (ListProfilesContext *ctx)
{
mm_3gpp_profile_list_free (ctx->profiles);
g_slice_free (ListProfilesContext, ctx);
}
static gboolean
modem_3gpp_profile_manager_list_profiles_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GList **out_profiles,
GError **error)
{
ListProfilesContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
ctx = g_task_get_task_data (G_TASK (res));
if (out_profiles)
*out_profiles = g_steal_pointer (&ctx->profiles);
return TRUE;
}
static void
profile_manager_cgdcont_query_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
ListProfilesContext *ctx;
const gchar *response;
GError *error = NULL;
GList *pdp_context_list;
response = mm_base_modem_at_command_finish (self, res, &error);
if (!response) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* may return NULL without error if response is empty */
pdp_context_list = mm_3gpp_parse_cgdcont_read_response (response, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
ctx = g_slice_new0 (ListProfilesContext);
g_task_set_task_data (task, ctx, (GDestroyNotify) list_profiles_context_free);
ctx->profiles = mm_3gpp_profile_list_new_from_pdp_context_list (pdp_context_list);
mm_3gpp_pdp_context_list_free (pdp_context_list);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_3gpp_profile_manager_list_profiles (MMIfaceModem3gppProfileManager *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* Query with CGDCONT? */
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+CGDCONT?",
3,
FALSE,
(GAsyncReadyCallback)profile_manager_cgdcont_query_ready,
task);
}
/*****************************************************************************/
/* Check format (3GPP profile management interface) */
typedef struct {
MMBearerIpFamily ip_type;
guint min_profile_id;
guint max_profile_id;
} CheckFormatContext;
static void
check_format_context_free (CheckFormatContext *ctx)
{
g_slice_free (CheckFormatContext, ctx);
}
static gboolean
modem_3gpp_profile_manager_check_format_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
gboolean *new_id,
gint *min_profile_id,
gint *max_profile_id,
GEqualFunc *apn_cmp,
MM3gppProfileCmpFlags *profile_cmp_flags,
GError **error)
{
CheckFormatContext *ctx;
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
ctx = g_task_get_task_data (G_TASK (res));
if (new_id)
*new_id = TRUE;
if (min_profile_id)
*min_profile_id = (gint) ctx->min_profile_id;
if (max_profile_id)
*max_profile_id = (gint) ctx->max_profile_id;
if (apn_cmp)
*apn_cmp = (GEqualFunc) mm_3gpp_cmp_apn_name;
if (profile_cmp_flags)
*profile_cmp_flags = (MM_3GPP_PROFILE_CMP_FLAGS_NO_AUTH |
MM_3GPP_PROFILE_CMP_FLAGS_NO_APN_TYPE |
MM_3GPP_PROFILE_CMP_FLAGS_NO_ACCESS_TYPE_PREFERENCE |
MM_3GPP_PROFILE_CMP_FLAGS_NO_ROAMING_ALLOWANCE |
MM_3GPP_PROFILE_CMP_FLAGS_NO_PROFILE_SOURCE);
return TRUE;
}
static void
check_format_cgdcont_test_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
CheckFormatContext *ctx;
const gchar *response;
GList *format_list = NULL;
g_autofree gchar *ip_family_str = NULL;
g_autoptr(GError) error = NULL;
gboolean checked = FALSE;
ctx = g_task_get_task_data (task);
ip_family_str = mm_bearer_ip_family_build_string_from_mask (ctx->ip_type);
response = mm_base_modem_at_command_full_finish (self, res, &error);
if (!response)
mm_obj_dbg (self, "failed checking context definition format: %s", error->message);
else {
format_list = mm_3gpp_parse_cgdcont_test_response (response, self, &error);
if (error)
mm_obj_dbg (self, "error parsing +CGDCONT test response: %s", error->message);
else if (mm_3gpp_pdp_context_format_list_find_range (format_list, ctx->ip_type,
&ctx->min_profile_id, &ctx->max_profile_id))
checked = TRUE;
}
if (!checked) {
ctx->min_profile_id = 1;
ctx->max_profile_id = G_MAXINT-1;
mm_obj_dbg (self, "unknown +CGDCONT format details for PDP type '%s', using defaults: minimum %d, maximum %d",
ip_family_str, ctx->min_profile_id, ctx->max_profile_id);
} else
mm_obj_dbg (self, "+CGDCONT format details for PDP type '%s': minimum %d, maximum %d",
ip_family_str, ctx->min_profile_id, ctx->max_profile_id);
mm_3gpp_pdp_context_format_list_free (format_list);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
modem_3gpp_profile_manager_check_format (MMIfaceModem3gppProfileManager *self,
MMBearerIpFamily ip_type,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
CheckFormatContext *ctx;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (CheckFormatContext);
ctx->ip_type = ip_type;
g_task_set_task_data (task, ctx, (GDestroyNotify)check_format_context_free);
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CGDCONT=?",
3,
TRUE, /* cached */
(GAsyncReadyCallback)check_format_cgdcont_test_ready,
task);
}
/*****************************************************************************/
/* Delete profile (3GPP profile management interface) */
static gboolean
modem_3gpp_profile_manager_delete_profile_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
profile_manager_cgdcont_reset_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
gint profile_id;
profile_id = GPOINTER_TO_INT (g_task_get_task_data (task));
if (!mm_base_modem_at_command_finish (self, res, &error)) {
mm_obj_dbg (self, "attempting to reset context with id '%d' failed: %s", profile_id, error->message);
g_task_return_error (task, error);
} else {
mm_obj_dbg (self, "reseted context with profile id '%d'", profile_id);
g_task_return_boolean (task, TRUE);
}
g_object_unref (task);
}
static void
profile_manager_cgdel_set_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
g_autoptr(GError) error = NULL;
g_autofree gchar *cmd = NULL;
gint profile_id;
profile_id = GPOINTER_TO_INT (g_task_get_task_data (task));
if (mm_base_modem_at_command_finish (self, res, &error)) {
mm_obj_dbg (self, "deleted context with profile id '%d'", profile_id);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
mm_obj_dbg (self, "attempting to delete context with id '%d' failed: %s", profile_id, error->message);
/* From 3GPP TS 27.007 (v16.3.0):
* A special form of the set command, +CGDCONT=<cid> causes the values for
* context number <cid> to become undefined.
*/
cmd = g_strdup_printf ("+CGDCONT=%d", profile_id);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
cmd,
3,
FALSE,
(GAsyncReadyCallback)profile_manager_cgdcont_reset_ready,
task);
}
static void
modem_3gpp_profile_manager_delete_profile (MMIfaceModem3gppProfileManager *self,
MM3gppProfile *profile,
const gchar *index_field,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autofree gchar *cmd = NULL;
GTask *task;
gint profile_id;
g_assert (g_strcmp0 (index_field, "profile-id") == 0);
task = g_task_new (self, NULL, callback, user_data);
profile_id = mm_3gpp_profile_get_profile_id (profile);
g_assert (profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
g_task_set_task_data (task, GINT_TO_POINTER (profile_id), NULL);
cmd = g_strdup_printf ("+CGDEL=%d", profile_id);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
cmd,
3,
FALSE,
(GAsyncReadyCallback)profile_manager_cgdel_set_ready,
task);
}
/*****************************************************************************/
/* Deactivate profile (3GPP profile management interface) */
static gboolean
modem_3gpp_profile_manager_check_activated_profile_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
gboolean *out_activated,
GError **error)
{
GError *inner_error = NULL;
gboolean result;
result = g_task_propagate_boolean (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
if (out_activated)
*out_activated = result;
return TRUE;
}
static void
check_activated_profile_cgact_query_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
MM3gppProfile *profile;
const gchar *response;
GError *error = NULL;
GList *pdp_context_active_list = NULL;
GList *l;
gint profile_id;
gboolean activated = FALSE;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (response)
pdp_context_active_list = mm_3gpp_parse_cgact_read_response (response, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
profile = g_task_get_task_data (task);
profile_id = mm_3gpp_profile_get_profile_id (profile);
for (l = pdp_context_active_list; l; l = g_list_next (l)) {
MM3gppPdpContextActive *iter = l->data;
if ((gint)iter->cid == profile_id) {
activated = iter->active;
break;
}
}
mm_3gpp_pdp_context_active_list_free (pdp_context_active_list);
if (!l)
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
"Profile '%d' not found in CGACT? response",
profile_id);
else
g_task_return_boolean (task, activated);
g_object_unref (task);
}
static void
modem_3gpp_profile_manager_check_activated_profile (MMIfaceModem3gppProfileManager *self,
MM3gppProfile *profile,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
gint profile_id;
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, g_object_ref (profile), g_object_unref);
profile_id = mm_3gpp_profile_get_profile_id (profile);
mm_obj_dbg (self, "checking if profile with id '%d' is already activated...", profile_id);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+CGACT?",
3,
FALSE,
(GAsyncReadyCallback)check_activated_profile_cgact_query_ready,
task);
}
/*****************************************************************************/
/* Deactivate profile (3GPP profile management interface) */
static gboolean
modem_3gpp_profile_manager_deactivate_profile_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
deactivate_profile_cgact_set_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_base_modem_at_command_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
modem_3gpp_profile_manager_deactivate_profile (MMIfaceModem3gppProfileManager *_self,
MM3gppProfile *profile,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
GTask *task;
gint profile_id;
g_autofree gchar *cmd = NULL;
task = g_task_new (self, NULL, callback, user_data);
profile_id = mm_3gpp_profile_get_profile_id (profile);
/* If the profile id for the initial EPS bearer is known (only applicable when
* the modem is LTE capable), do not deactivate it as it will likely unregister
* from the network altogether. */
if (self->priv->initial_eps_bearer_cid_support_checked &&
self->priv->initial_eps_bearer_cid == profile_id) {
mm_obj_dbg (self, "skipping profile deactivation (initial EPS bearer)");
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
mm_obj_dbg (self, "deactivating profile with id '%d'...", profile_id);
cmd = g_strdup_printf ("+CGACT=0,%d", profile_id);
mm_base_modem_at_command (
MM_BASE_MODEM (self),
cmd,
MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
FALSE,
(GAsyncReadyCallback)deactivate_profile_cgact_set_ready,
task);
}
/*****************************************************************************/
/* Store profile (3GPP profile management interface) */
static gboolean
modem_3gpp_profile_manager_store_profile_finish (MMIfaceModem3gppProfileManager *self,
GAsyncResult *res,
gint *out_profile_id,
MMBearerApnType *out_apn_type,
GError **error)
{
if (!g_task_propagate_boolean (G_TASK (res), error))
return FALSE;
if (out_profile_id)
*out_profile_id = GPOINTER_TO_INT (g_task_get_task_data (G_TASK (res)));
if (out_apn_type)
*out_apn_type = MM_BEARER_APN_TYPE_NONE;
return TRUE;
}
static void
store_profile_cgdcont_set_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
if (!mm_base_modem_at_command_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
modem_3gpp_profile_manager_store_profile (MMIfaceModem3gppProfileManager *self,
MM3gppProfile *profile,
const gchar *index_field,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
gint profile_id;
MMBearerIpFamily ip_type;
const gchar *apn;
const gchar *pdp_type;
g_autofree gchar *ip_type_str = NULL;
g_autofree gchar *quoted_apn = NULL;
g_autofree gchar *cmd = NULL;
g_assert (g_strcmp0 (index_field, "profile-id") == 0);
task = g_task_new (self, NULL, callback, user_data);
profile_id = mm_3gpp_profile_get_profile_id (profile);
g_assert (profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
g_task_set_task_data (task, GINT_TO_POINTER (profile_id), NULL);
ip_type = mm_3gpp_profile_get_ip_type (profile);
g_assert (ip_type != MM_BEARER_IP_FAMILY_NONE);
g_assert (ip_type != MM_BEARER_IP_FAMILY_ANY);
ip_type_str = mm_bearer_ip_family_build_string_from_mask (ip_type);
pdp_type = mm_3gpp_get_pdp_type_from_ip_family (ip_type);
g_assert (pdp_type);
apn = mm_3gpp_profile_get_apn (profile);
quoted_apn = mm_at_quote_string (apn);
mm_obj_dbg (self, "storing profile '%d': apn '%s', ip type '%s'",
profile_id, apn, ip_type_str);
cmd = g_strdup_printf ("+CGDCONT=%d,\"%s\",%s", profile_id, pdp_type, quoted_apn);
mm_base_modem_at_command (MM_BASE_MODEM (self),
cmd,
3,
FALSE,
(GAsyncReadyCallback) store_profile_cgdcont_set_ready,
task);
}
/*****************************************************************************/
/* Load update settings (Firmware interface) */
static MMFirmwareUpdateSettings *
modem_firmware_load_update_settings_finish (MMIfaceModemFirmware *self,
GAsyncResult *res,
GError **error)
{
return mm_iface_modem_firmware_load_update_settings_in_port_finish (self, res, error);
}
static void
modem_firmware_load_update_settings (MMIfaceModemFirmware *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_iface_modem_firmware_load_update_settings_in_port (
self,
MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self))),
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];
g_autoptr(GRegex) ciev_regex = NULL;
g_autoptr(GRegex) cmti_regex = NULL;
g_autoptr(GRegex) cusd_regex = NULL;
g_autoptr(GRegex) creg_regex = NULL;
guint i;
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 */
creg_regex = mm_3gpp_creg_regex_get (FALSE);
ciev_regex = mm_3gpp_ciev_regex_get ();
cmti_regex = mm_3gpp_cmti_regex_get ();
cusd_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]), creg_regex, NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), ciev_regex, NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), cmti_regex, NULL, NULL, NULL);
mm_port_serial_at_add_unsolicited_msg_handler (MM_PORT_SERIAL_AT (ports[i]), cusd_regex, NULL, NULL, NULL);
}
}
/*****************************************************************************/
/* 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 void
enabling_modem_init (MMBroadbandModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMPortSerialAt *primary;
primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
if (!primary) {
g_task_report_new_error (self, callback, user_data, enabling_modem_init,
MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Failed to run init command: primary port missing");
return;
}
/* 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_IFACE_PORT_AT (primary),
"Z",
6,
FALSE,
FALSE,
NULL, /* cancellable */
callback,
user_data);
}
static gboolean
enabling_modem_init_finish (MMBroadbandModem *self,
GAsyncResult *res,
GError **error)
{
if (g_async_result_is_tagged (res, enabling_modem_init))
return g_task_propagate_boolean (G_TASK (res), error);
return !!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, error);
}
/*****************************************************************************/
/* 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_obj_dbg (self, "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_obj_dbg (self, "running 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_obj_dbg (self, "skipping initialization: not first enabling");
else if (mm_base_modem_get_hotplugged (MM_BASE_MODEM (self))) {
self->priv->modem_init_run = TRUE;
mm_obj_dbg (self, "skipping 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_obj_dbg (self, "skipping 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_obj_dbg (self, "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_obj_warn (self, "initial 3GPP registration check failed: %s", error->message);
g_error_free (error);
return;
}
mm_obj_dbg (self, "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_obj_warn (self, "initial CDMA registration check failed: %s", error->message);
g_error_free (error);
return;
}
mm_obj_dbg (self, "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 {
/* When user requests a disable operation, the process starts here */
DISABLING_STEP_FIRST,
DISABLING_STEP_IFACE_SIMPLE_ABORT_ONGOING,
DISABLING_STEP_WAIT_FOR_FINAL_STATE,
DISABLING_STEP_DISCONNECT_BEARERS,
/* When the disabling is launched due to a failed enable, the process
* starts here */
DISABLING_STEP_FIRST_AFTER_ENABLE_FAILED,
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_PROFILE_MANAGER,
DISABLING_STEP_IFACE_3GPP,
DISABLING_STEP_IFACE_MODEM,
DISABLING_STEP_LAST,
} DisablingStep;
typedef struct {
gboolean state_updates;
DisablingStep step;
MMModemState previous_state;
GError *saved_error;
} DisablingContext;
static void
disabling_context_free (DisablingContext *ctx)
{
g_assert (!ctx->saved_error);
g_slice_free (DisablingContext, ctx);
}
static gboolean
common_disable_finish (MMBroadbandModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
disabling_complete (GTask *task)
{
MMBroadbandModem *self;
DisablingContext *ctx;
g_autoptr(GError) error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (MM_BROADBAND_MODEM_GET_CLASS (self)->disabling_stopped &&
!MM_BROADBAND_MODEM_GET_CLASS (self)->disabling_stopped (self, &error)) {
mm_obj_warn (self, "error when stopping the disabling sequence: %s", error->message);
}
/* Disable failed? */
if (ctx->saved_error) {
if (ctx->state_updates && (ctx->previous_state != MM_MODEM_STATE_DISABLED)) {
/* Fallback to previous state */
mm_iface_modem_update_state (MM_IFACE_MODEM (self),
ctx->previous_state,
MM_MODEM_STATE_CHANGE_REASON_UNKNOWN);
}
g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
g_object_unref (task);
return;
}
/* Disable succeeded */
if (ctx->state_updates)
mm_iface_modem_update_state (MM_IFACE_MODEM (self),
MM_MODEM_STATE_DISABLED,
MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void disabling_step (GTask *task);
#undef INTERFACE_DISABLE_READY_FN
#define INTERFACE_DISABLE_READY_FN(NAME,TYPE,WARN_ERRORS) \
static void \
NAME##_disable_ready (MMBroadbandModem *self, \
GAsyncResult *result, \
GTask *task) \
{ \
DisablingContext *ctx; \
g_autoptr(GError) error = NULL; \
\
if (!mm_##NAME##_disable_finish (TYPE (self), result, &error)) { \
if (WARN_ERRORS) \
mm_obj_warn (self, "couldn't disable interface: %s", error->message); \
else \
mm_obj_dbg (self, "couldn't disable interface: %s", error->message); \
} \
\
/* 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, FALSE)
INTERFACE_DISABLE_READY_FN (iface_modem_3gpp_profile_manager, MM_IFACE_MODEM_3GPP_PROFILE_MANAGER, FALSE)
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_bearers_ready (MMBearerList *list,
GAsyncResult *res,
GTask *task)
{
DisablingContext *ctx;
ctx = g_task_get_task_data (task);
g_assert (!ctx->saved_error);
if (!mm_bearer_list_disconnect_bearers_finish (list, res, &ctx->saved_error)) {
disabling_complete (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;
ctx = g_task_get_task_data (task);
g_assert (!ctx->saved_error);
ctx->previous_state = mm_iface_modem_wait_for_final_state_finish (self, res, &ctx->saved_error);
if (ctx->saved_error) {
disabling_complete (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. */
disabling_complete (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 */
g_assert (ctx->state_updates);
mm_iface_modem_update_state (MM_IFACE_MODEM (self),
MM_MODEM_STATE_DISABLING,
MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
ctx->step++;
disabling_step (task);
}
static void
disabling_step (GTask *task)
{
MMBroadbandModem *self;
DisablingContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
g_assert (!ctx->saved_error);
switch (ctx->step) {
case DISABLING_STEP_FIRST:
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_SIMPLE_ABORT_ONGOING:
/* Connection requests via the Simple interface must be aborted as soon
* as possible, because certain steps may be explicitly waiting for new
* state transitions and such. */
mm_iface_modem_simple_abort_ongoing (MM_IFACE_MODEM_SIMPLE (self));
ctx->step++;
/* fall through */
case DISABLING_STEP_WAIT_FOR_FINAL_STATE:
/* cancellability allowed at this point */
if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (task), &ctx->saved_error)) {
disabling_complete (task);
return;
}
mm_iface_modem_wait_for_final_state (MM_IFACE_MODEM (self),
MM_MODEM_STATE_UNKNOWN, /* just any */
(GAsyncReadyCallback)disabling_wait_for_final_state_ready,
task);
return;
case DISABLING_STEP_DISCONNECT_BEARERS:
/* cancellability allowed at this point */
if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (task), &ctx->saved_error)) {
disabling_complete (task);
return;
}
if (self->priv->modem_bearer_list) {
mm_bearer_list_disconnect_bearers (
self->priv->modem_bearer_list,
NULL, /* all bearers */
(GAsyncReadyCallback)bearer_list_disconnect_bearers_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_FIRST_AFTER_ENABLE_FAILED:
/* From this point onwards, the disabling sequence will NEVER fail, all
* errors will be treated as non-fatal, including a possible task
* cancellation. */
g_task_set_check_cancellable (task, FALSE);
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 (self->priv->modem_voice_dbus_skeleton) {
mm_obj_dbg (self, "modem has voice capabilities, disabling the Voice interface...");
mm_iface_modem_voice_disable (MM_IFACE_MODEM_VOICE (self),
(GAsyncReadyCallback)iface_modem_voice_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_SIGNAL:
if (self->priv->modem_signal_dbus_skeleton) {
mm_obj_dbg (self, "modem has extended signal reporting capabilities, disabling the Signal interface...");
mm_iface_modem_signal_disable (MM_IFACE_MODEM_SIGNAL (self),
(GAsyncReadyCallback)iface_modem_signal_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_OMA:
if (self->priv->modem_oma_dbus_skeleton) {
mm_obj_dbg (self, "modem has OMA capabilities, disabling the OMA interface...");
mm_iface_modem_oma_disable (MM_IFACE_MODEM_OMA (self),
(GAsyncReadyCallback)iface_modem_oma_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_TIME:
if (self->priv->modem_time_dbus_skeleton) {
mm_obj_dbg (self, "modem has time capabilities, disabling the Time interface...");
mm_iface_modem_time_disable (MM_IFACE_MODEM_TIME (self),
(GAsyncReadyCallback)iface_modem_time_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_MESSAGING:
if (self->priv->modem_messaging_dbus_skeleton) {
mm_obj_dbg (self, "modem has messaging capabilities, disabling the Messaging interface...");
mm_iface_modem_messaging_disable (MM_IFACE_MODEM_MESSAGING (self),
(GAsyncReadyCallback)iface_modem_messaging_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_LOCATION:
if (self->priv->modem_location_dbus_skeleton) {
mm_obj_dbg (self, "modem has location capabilities, disabling the Location interface...");
mm_iface_modem_location_disable (MM_IFACE_MODEM_LOCATION (self),
(GAsyncReadyCallback)iface_modem_location_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_CDMA:
if (self->priv->modem_cdma_dbus_skeleton) {
mm_obj_dbg (self, "modem has CDMA capabilities, disabling the Modem CDMA interface...");
mm_iface_modem_cdma_disable (MM_IFACE_MODEM_CDMA (self),
(GAsyncReadyCallback)iface_modem_cdma_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_3GPP_USSD:
if (self->priv->modem_3gpp_ussd_dbus_skeleton) {
mm_obj_dbg (self, "modem has 3GPP/USSD capabilities, disabling the Modem 3GPP/USSD interface...");
mm_iface_modem_3gpp_ussd_disable (MM_IFACE_MODEM_3GPP_USSD (self),
(GAsyncReadyCallback)iface_modem_3gpp_ussd_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_3GPP_PROFILE_MANAGER:
if (self->priv->modem_3gpp_profile_manager_dbus_skeleton) {
mm_obj_dbg (self, "modem has 3GPP profile management capabilities, disabling the Modem 3GPP Profile Manager interface...");
mm_iface_modem_3gpp_profile_manager_disable (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self),
(GAsyncReadyCallback)iface_modem_3gpp_profile_manager_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_IFACE_3GPP:
if (self->priv->modem_3gpp_dbus_skeleton) {
mm_obj_dbg (self, "modem has 3GPP capabilities, disabling the Modem 3GPP interface...");
mm_iface_modem_3gpp_disable (MM_IFACE_MODEM_3GPP (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 (self->priv->modem_dbus_skeleton) {
mm_obj_dbg (self, "disabling the Modem interface...");
mm_iface_modem_disable (MM_IFACE_MODEM (self),
(GAsyncReadyCallback)iface_modem_disable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case DISABLING_STEP_LAST:
/* All disabled without errors! */
disabling_complete (task);
return;
default:
break;
}
g_assert_not_reached ();
}
static void
common_disable (MMBroadbandModem *self,
gboolean state_updates,
DisablingStep first_step,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
DisablingContext *ctx;
GTask *task;
ctx = g_slice_new0 (DisablingContext);
ctx->state_updates = state_updates;
ctx->step = first_step;
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)disabling_context_free);
disabling_step (task);
}
/* Implicit disabling after failed enable */
static gboolean
enable_failed_finish (MMBroadbandModem *self,
GAsyncResult *res,
GError **error)
{
/* The implicit disabling should never ever fail */
common_disable_finish (self, res, NULL);
return TRUE;
}
static void
enable_failed (MMBroadbandModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_disable (self,
FALSE, /* don't perform state updates */
DISABLING_STEP_FIRST_AFTER_ENABLE_FAILED,
NULL, /* no cancellable */
callback,
user_data);
}
/* User-requested disable operation */
static gboolean
disable_finish (MMBaseModem *self,
GAsyncResult *res,
GError **error)
{
return common_disable_finish (MM_BROADBAND_MODEM (self), res, error);
}
static void
disable (MMBaseModem *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
common_disable (MM_BROADBAND_MODEM (self),
TRUE, /* perform state updates */
DISABLING_STEP_FIRST,
cancellable,
callback,
user_data);
}
/*****************************************************************************/
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_PROFILE_MANAGER,
ENABLING_STEP_IFACE_3GPP_USSD,
ENABLING_STEP_IFACE_CDMA,
ENABLING_STEP_IFACE_LOCATION,
ENABLING_STEP_IFACE_MESSAGING,
ENABLING_STEP_IFACE_TIME,
ENABLING_STEP_IFACE_CELL_BROADCAST,
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 {
EnablingStep step;
MMModemState previous_state;
GError *saved_error;
} EnablingContext;
static void
enabling_context_free (EnablingContext *ctx)
{
g_assert (!ctx->saved_error);
g_slice_free (EnablingContext, ctx);
}
static gboolean
enable_finish (MMBaseModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
enabling_complete (GTask *task)
{
MMBroadbandModem *self;
EnablingContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* Enable failed? */
if (ctx->saved_error) {
if (ctx->previous_state != MM_MODEM_STATE_ENABLED) {
/* Fallback to previous state */
mm_iface_modem_update_state (MM_IFACE_MODEM (self),
ctx->previous_state,
MM_MODEM_STATE_CHANGE_REASON_UNKNOWN);
}
g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
g_object_unref (task);
return;
}
/* Enable succeeded */
mm_iface_modem_update_state (MM_IFACE_MODEM (self),
MM_MODEM_STATE_ENABLED,
MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
enable_failed_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
EnablingContext *ctx;
/* The disabling run after a failed enable will never fail */
enable_failed_finish (self, res, NULL);
ctx = g_task_get_task_data (task);
g_assert (ctx->saved_error);
enabling_complete (task);
}
static void enabling_step (GTask *task);
#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; \
g_autoptr(GError) error = NULL; \
\
ctx = g_task_get_task_data (task); \
\
if (!mm_##NAME##_enable_finish (TYPE (self), result, &error)) { \
if (FATAL_ERRORS) { \
mm_obj_warn (self, "couldn't enable interface: '%s'", error->message); \
g_assert (!ctx->saved_error); \
ctx->saved_error = g_steal_pointer (&error); \
mm_obj_dbg (self, "running implicit disable after failed enable..."); \
enable_failed (self, (GAsyncReadyCallback) enable_failed_ready, task); \
return; \
} \
\
mm_obj_dbg (self, "couldn't enable interface: '%s'", error->message); \
} \
\
/* Go on to next step */ \
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_profile_manager, MM_IFACE_MODEM_3GPP_PROFILE_MANAGER, FALSE)
INTERFACE_ENABLE_READY_FN (iface_modem_3gpp_ussd, MM_IFACE_MODEM_3GPP_USSD, FALSE)
INTERFACE_ENABLE_READY_FN (iface_modem_cdma, MM_IFACE_MODEM_CDMA, TRUE)
INTERFACE_ENABLE_READY_FN (iface_modem_cell_broadcast, MM_IFACE_MODEM_CELL_BROADCAST, FALSE)
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;
ctx = g_task_get_task_data (task);
g_assert (!ctx->saved_error);
if (!MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_started_finish (self, result, &ctx->saved_error)) {
enabling_complete (task);
return;
}
/* Go on to next step */
ctx->step++;
enabling_step (task);
}
static void
enabling_wait_for_final_state_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
EnablingContext *ctx;
ctx = g_task_get_task_data (task);
g_assert (!ctx->saved_error);
ctx->previous_state = mm_iface_modem_wait_for_final_state_finish (self, res, &ctx->saved_error);
if (ctx->saved_error) {
enabling_complete (task);
return;
}
if (ctx->previous_state >= MM_MODEM_STATE_ENABLED) {
/* Just return success, don't relaunch enabling */
enabling_complete (task);
return;
}
/* We're in a final state now, go on */
mm_iface_modem_update_state (MM_IFACE_MODEM (self),
MM_MODEM_STATE_ENABLING,
MM_MODEM_STATE_CHANGE_REASON_USER_REQUESTED);
ctx->step++;
enabling_step (task);
}
static void
enabling_step (GTask *task)
{
MMBroadbandModem *self;
EnablingContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
g_assert (!ctx->saved_error);
/* Don't run new steps if we're cancelled */
if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (task), &ctx->saved_error)) {
enabling_complete (task);
return;
}
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 (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 (self)->enabling_started &&
MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_started_finish) {
MM_BROADBAND_MODEM_GET_CLASS (self)->enabling_started (self,
(GAsyncReadyCallback)enabling_started_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_MODEM:
/* From now on, the failure to enable one of the mandatory interfaces
* will trigger the implicit disabling process */
/* Enabling the Modem interface */
mm_iface_modem_enable (MM_IFACE_MODEM (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_enable_ready,
task);
return;
case ENABLING_STEP_IFACE_3GPP:
if (self->priv->modem_3gpp_dbus_skeleton) {
mm_obj_dbg (self, "modem has 3GPP capabilities, enabling the Modem 3GPP interface...");
/* Enabling the Modem 3GPP interface */
mm_iface_modem_3gpp_enable (MM_IFACE_MODEM_3GPP (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_3gpp_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_3GPP_PROFILE_MANAGER:
if (self->priv->modem_3gpp_profile_manager_dbus_skeleton) {
mm_obj_dbg (self, "modem has 3GPP profile management capabilities, enabling the Modem 3GPP Profile Manager interface...");
mm_iface_modem_3gpp_profile_manager_enable (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self),
(GAsyncReadyCallback)iface_modem_3gpp_profile_manager_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_3GPP_USSD:
if (self->priv->modem_3gpp_ussd_dbus_skeleton) {
mm_obj_dbg (self, "modem has 3GPP/USSD capabilities, enabling the Modem 3GPP/USSD interface...");
mm_iface_modem_3gpp_ussd_enable (MM_IFACE_MODEM_3GPP_USSD (self),
(GAsyncReadyCallback)iface_modem_3gpp_ussd_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_CDMA:
if (self->priv->modem_cdma_dbus_skeleton) {
mm_obj_dbg (self, "modem has CDMA capabilities, enabling the Modem CDMA interface...");
/* Enabling the Modem CDMA interface */
mm_iface_modem_cdma_enable (MM_IFACE_MODEM_CDMA (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_cdma_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_LOCATION:
if (self->priv->modem_location_dbus_skeleton) {
mm_obj_dbg (self, "modem has location capabilities, enabling the Location interface...");
/* Enabling the Modem Location interface */
mm_iface_modem_location_enable (MM_IFACE_MODEM_LOCATION (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_location_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_MESSAGING:
if (self->priv->modem_messaging_dbus_skeleton) {
mm_obj_dbg (self, "modem has messaging capabilities, enabling the Messaging interface...");
/* Enabling the Modem Messaging interface */
mm_iface_modem_messaging_enable (MM_IFACE_MODEM_MESSAGING (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_messaging_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_TIME:
if (self->priv->modem_time_dbus_skeleton) {
mm_obj_dbg (self, "modem has time capabilities, enabling the Time interface...");
/* Enabling the Modem Time interface */
mm_iface_modem_time_enable (MM_IFACE_MODEM_TIME (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_time_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_CELL_BROADCAST:
if (self->priv->modem_cell_broadcast_dbus_skeleton) {
mm_obj_dbg (self, "modem has cell broadcast capabilities, enabling the cell broadcast interface...");
/* Enabling the Modem CellBroadcast interface */
mm_iface_modem_cell_broadcast_enable (MM_IFACE_MODEM_CELL_BROADCAST (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_cell_broadcast_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_SIGNAL:
if (self->priv->modem_signal_dbus_skeleton) {
mm_obj_dbg (self, "modem has extended signal reporting capabilities, enabling the Signal interface...");
/* Enabling the Modem Signal interface */
mm_iface_modem_signal_enable (MM_IFACE_MODEM_SIGNAL (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_signal_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_OMA:
if (self->priv->modem_oma_dbus_skeleton) {
mm_obj_dbg (self, "modem has OMA capabilities, enabling the OMA interface...");
/* Enabling the Modem Oma interface */
mm_iface_modem_oma_enable (MM_IFACE_MODEM_OMA (self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_oma_enable_ready,
task);
return;
}
ctx->step++;
/* fall through */
case ENABLING_STEP_IFACE_VOICE:
if (self->priv->modem_voice_dbus_skeleton) {
mm_obj_dbg (self, "modem has voice capabilities, enabling the Voice interface...");
/* Enabling the Modem Voice interface */
mm_iface_modem_voice_enable (MM_IFACE_MODEM_VOICE (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:
/* 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 (self);
/* All enabled without errors! */
enabling_complete (task);
return;
default:
break;
}
g_assert_not_reached ();
}
static void
enabling_start (GTask *task)
{
EnablingContext *ctx;
ctx = g_slice_new0 (EnablingContext);
ctx->step = ENABLING_STEP_FIRST;
g_task_set_task_data (task, ctx, (GDestroyNotify)enabling_context_free);
enabling_step (task);
}
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 may have a UNKNOWN->ENABLED transition here if the request
* comes after having flagged the modem as invalid. Just error out
* gracefully. */
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Cannot enable modem: unknown state");
g_object_unref (task);
return;
case MM_MODEM_STATE_FAILED:
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Cannot enable modem: initialization failed");
g_object_unref (task);
return;
case MM_MODEM_STATE_LOCKED:
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Cannot enable modem: device locked");
g_object_unref (task);
return;
case MM_MODEM_STATE_INITIALIZING:
case MM_MODEM_STATE_DISABLED:
case MM_MODEM_STATE_DISABLING:
enabling_start (task);
return;
case MM_MODEM_STATE_ENABLING:
/* Enabling can be ongoing when another user enable request arrives,
* just error out gracefully. */
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Cannot enable modem (again): enabling ongoing");
g_object_unref (task);
return;
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);
g_object_unref (task);
return;
default:
g_assert_not_reached ();
}
}
/*****************************************************************************/
#if defined WITH_SUSPEND_RESUME
typedef enum {
SYNCING_STEP_FIRST,
SYNCING_STEP_NOTIFY,
SYNCING_STEP_IFACE_MODEM,
SYNCING_STEP_IFACE_3GPP,
SYNCING_STEP_IFACE_TIME,
SYNCING_STEP_LAST,
} SyncingStep;
typedef struct {
SyncingStep step;
} SyncingContext;
static void syncing_step (GTask *task);
static gboolean
synchronize_finish (MMBaseModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
iface_modem_time_sync_ready (MMIfaceModemTime *self,
GAsyncResult *res,
GTask *task)
{
SyncingContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
if (!mm_iface_modem_time_sync_finish (self, res, &error))
mm_obj_warn (self, "time interface synchronization failed: %s", error->message);
/* Go on to next step */
ctx->step++;
syncing_step (task);
}
static void
iface_modem_3gpp_sync_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
SyncingContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
if (!mm_iface_modem_3gpp_sync_finish (self, res, &error))
mm_obj_warn (self, "3GPP interface synchronization failed: %s", error->message);
/* Go on to next step */
ctx->step++;
syncing_step (task);
}
static void
iface_modem_sync_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
SyncingContext *ctx;
MMModemLock lock;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
if (!mm_iface_modem_sync_finish (self, res, &error))
mm_obj_warn (self, "modem interface synchronization failed: %s", error->message);
/* The synchronization logic only runs on modems that were enabled before
* the suspend/resume cycle, and therefore we should not get SIM-PIN locked
* at this point, unless the SIM was swapped. */
lock = mm_iface_modem_get_unlock_required (self);
if (lock == MM_MODEM_LOCK_UNKNOWN || lock == MM_MODEM_LOCK_SIM_PIN || lock == MM_MODEM_LOCK_SIM_PUK) {
/* Abort the sync() operation right away, and report a new SIM event that will
* disable the modem and trigger a full reprobe */
mm_obj_warn (self, "SIM is locked... synchronization aborted");
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"Locked SIM found during modem interface synchronization");
g_object_unref (task);
return;
}
/* Not locked, go on to next step */
mm_obj_dbg (self, "modem unlocked, continue synchronization");
ctx->step++;
syncing_step (task);
return;
}
static void
syncing_step (GTask *task)
{
MMBroadbandModem *self;
SyncingContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case SYNCING_STEP_FIRST:
ctx->step++;
/* fall through */
case SYNCING_STEP_NOTIFY:
g_signal_emit (self, signals[SIGNAL_SYNC_NEEDED], 0);
ctx->step++;
/* fall through */
case SYNCING_STEP_IFACE_MODEM:
/*
* Start interface Modem synchronization.
* We want to make sure that the SIM is unlocked and not swapped before
* synchronizing other interfaces.
*/
if (!self->priv->modem_dbus_skeleton) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"Synchronization aborted: no modem exposed in DBus");
g_object_unref (task);
return;
}
mm_obj_msg (self, "resume synchronization state (%d/%d): modem interface sync",
ctx->step, SYNCING_STEP_LAST);
mm_iface_modem_sync (MM_IFACE_MODEM (self),
(GAsyncReadyCallback)iface_modem_sync_ready,
task);
return;
case SYNCING_STEP_IFACE_3GPP:
/* Start 3GPP interface synchronization, only if modem was enabled.
* We hardly depend on the registration and bearer status,
* therefore we cannot continue with the other steps until
* this one is finished.
*/
if (self->priv->modem_3gpp_dbus_skeleton &&
(self->priv->modem_state >= MM_MODEM_STATE_ENABLED)) {
mm_obj_msg (self, "resume synchronization state (%d/%d): 3GPP interface sync",
ctx->step, SYNCING_STEP_LAST);
mm_iface_modem_3gpp_sync (MM_IFACE_MODEM_3GPP (self), (GAsyncReadyCallback)iface_modem_3gpp_sync_ready, task);
return;
}
ctx->step++;
/* fall through */
case SYNCING_STEP_IFACE_TIME:
/* Start Time interface synchronization, only if modem was enabled */
if (self->priv->modem_time_dbus_skeleton &&
(self->priv->modem_state >= MM_MODEM_STATE_ENABLED)) {
mm_obj_msg (self, "resume synchronization state (%d/%d): time interface sync",
ctx->step, SYNCING_STEP_LAST);
mm_iface_modem_time_sync (MM_IFACE_MODEM_TIME (self), (GAsyncReadyCallback)iface_modem_time_sync_ready, task);
return;
}
ctx->step++;
/* fall through */
case SYNCING_STEP_LAST:
mm_obj_msg (self, "resume synchronization state (%d/%d): all done",
ctx->step, SYNCING_STEP_LAST);
/* We are done without errors! */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
break;
}
g_assert_not_reached ();
}
/* 'sync' as function name conflicts with a declared function in unistd.h */
static void
synchronize (MMBaseModem *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SyncingContext *ctx;
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
/* Create SyncingContext */
ctx = g_new0 (SyncingContext, 1);
ctx->step = SYNCING_STEP_FIRST;
g_task_set_task_data (task, ctx, (GDestroyNotify)g_free);
syncing_step (task);
}
typedef enum {
TERSE_STEP_FIRST,
TERSE_STEP_DISABLE_UNSOLICITED_EVENTS_3GPP,
TERSE_STEP_LAST,
} TerseStep;
typedef struct {
TerseStep step;
} TerseContext;
static void terse_step (GTask *task);
static gboolean
terse_finish (MMBaseModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
iface_modem_3gpp_terse_ready (MMIfaceModem3gpp *self,
GAsyncResult *res,
GTask *task)
{
TerseContext *ctx;
g_autoptr(GError) error = NULL;
ctx = g_task_get_task_data (task);
if (!mm_iface_modem_3gpp_terse_finish (self, res, &error))
mm_obj_warn (self, "3GPP interface disable unsolicited events failed: %s", error->message);
/* Go on to next step */
ctx->step++;
terse_step (task);
}
static void
terse_step (GTask *task)
{
MMBroadbandModem *self;
TerseContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
switch (ctx->step) {
case TERSE_STEP_FIRST:
ctx->step++;
/* fall through */
case TERSE_STEP_DISABLE_UNSOLICITED_EVENTS_3GPP:
if (self->priv->modem_3gpp_dbus_skeleton &&
(self->priv->modem_state >= MM_MODEM_STATE_ENABLED)) {
mm_obj_msg (self, "terse state (%d/%d): 3GPP interface disable unsolicited events",
ctx->step, TERSE_STEP_LAST);
mm_iface_modem_3gpp_terse (MM_IFACE_MODEM_3GPP (self), (GAsyncReadyCallback)iface_modem_3gpp_terse_ready, task);
return;
}
ctx->step++;
/* fall through */
case TERSE_STEP_LAST:
mm_obj_msg (self, "setting terse state (%d/%d): all done",
ctx->step, TERSE_STEP_LAST);
/* We are done without errors! */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
default:
break;
}
g_assert_not_reached ();
}
static void
terse (MMBaseModem *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
TerseContext *ctx;
GTask *task;
task = g_task_new (self, cancellable, callback, user_data);
/* Create TerseContext */
ctx = g_new0 (TerseContext, 1);
ctx->step = TERSE_STEP_FIRST;
g_task_set_task_data (task, ctx, (GDestroyNotify)g_free);
terse_step (task);
}
#endif
/*****************************************************************************/
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_JUMP_TO_LIMITED,
INITIALIZE_STEP_IFACE_3GPP_PROFILE_MANAGER,
INITIALIZE_STEP_IFACE_3GPP_USSD,
INITIALIZE_STEP_IFACE_CDMA,
INITIALIZE_STEP_IFACE_MESSAGING,
INITIALIZE_STEP_IFACE_TIME,
INITIALIZE_STEP_IFACE_SIGNAL,
INITIALIZE_STEP_IFACE_OMA,
INITIALIZE_STEP_IFACE_SAR,
INITIALIZE_STEP_IFACE_CELL_BROADCAST,
INITIALIZE_STEP_FALLBACK_LIMITED,
INITIALIZE_STEP_IFACE_LOCATION,
INITIALIZE_STEP_IFACE_VOICE,
INITIALIZE_STEP_IFACE_FIRMWARE,
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_obj_warn (ctx->self, "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_obj_warn (self, "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_obj_warn (self, "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;
else if (g_error_matches (error,
MM_CORE_ERROR,
MM_CORE_ERROR_WRONG_SIM_STATE))
failed_reason = MM_MODEM_STATE_FAILED_REASON_ESIM_WITHOUT_PROFILES;
else if (g_error_matches (error,
MM_CORE_ERROR,
MM_CORE_ERROR_UNSUPPORTED))
failed_reason = MM_MODEM_STATE_FAILED_REASON_UNKNOWN_CAPABILITIES;
g_error_free (error);
mm_iface_modem_update_failed_state (MM_IFACE_MODEM (self), failed_reason);
} else {
/* bind simple properties */
mm_iface_modem_bind_simple_status (MM_IFACE_MODEM (self),
self->priv->modem_simple_status);
}
/* 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_obj_warn (self, "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_obj_dbg (self, "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_profile_manager, MM_IFACE_MODEM_3GPP_PROFILE_MANAGER, FALSE)
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)
INTERFACE_INIT_READY_FN (iface_modem_sar, MM_IFACE_MODEM_SAR, FALSE)
INTERFACE_INIT_READY_FN (iface_modem_cell_broadcast, MM_IFACE_MODEM_CELL_BROADCAST, 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_JUMP_TO_LIMITED:
if (ctx->self->priv->modem_state == MM_MODEM_STATE_LOCKED ||
ctx->self->priv->modem_state == MM_MODEM_STATE_FAILED) {
/* Jump to the fallback step when locked or failed, we will allow some additional
* interfaces even in locked or failed state. */
ctx->step = INITIALIZE_STEP_FALLBACK_LIMITED;
initialize_step (task);
return;
}
ctx->step++;
/* fall through */
case INITIALIZE_STEP_IFACE_3GPP_PROFILE_MANAGER:
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self))) {
/* Initialize the 3GPP Profile Manager interface */
mm_iface_modem_3gpp_profile_manager_initialize (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (ctx->self),
(GAsyncReadyCallback)iface_modem_3gpp_profile_manager_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_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_IFACE_SAR:
/* Initialize the SAR interface */
mm_iface_modem_sar_initialize (MM_IFACE_MODEM_SAR (ctx->self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_sar_initialize_ready,
task);
return;
case INITIALIZE_STEP_IFACE_CELL_BROADCAST:
/* Initialize the CellBroadcast interface */
mm_iface_modem_cell_broadcast_initialize (MM_IFACE_MODEM_CELL_BROADCAST (ctx->self),
g_task_get_cancellable (task),
(GAsyncReadyCallback)iface_modem_cell_broadcast_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_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_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_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 = NULL;
if (!ctx->self->priv->modem_dbus_skeleton) {
/* ABORTED here specifies an extremely fatal error that will make the modem
* not even exported in DBus */
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"Fatal error: modem is unusable");
} else {
/* Fatal SIM, firmware, or modem failure :-( */
MMModemStateFailedReason reason;
reason = mm_gdbus_modem_get_state_failed_reason (MM_GDBUS_MODEM (ctx->self->priv->modem_dbus_skeleton));
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"Modem in failed state: %s",
mm_modem_state_failed_reason_get_string (reason));
/* Ensure we only leave the Modem, Voice, Location 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, and the Location interface so that we can use
* GNSS without a SIM card
*/
mm_iface_modem_3gpp_profile_manager_shutdown (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (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_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 = MM_BROADBAND_MODEM (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);
}
/*****************************************************************************/
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,
GError **error)
{
gchar *device_identifier;
/* do nothing if device has gone already */
if (!self->priv->modem_dbus_skeleton) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Modem interface skeleton unavailable");
return NULL;
}
device_identifier = mm_create_device_identifier (
mm_base_modem_get_vendor_id (MM_BASE_MODEM (self)),
mm_base_modem_get_product_id (MM_BASE_MODEM (self)),
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)));
if (!device_identifier) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unable to generate a device identifier");
return NULL;
}
return device_identifier;
}
/*****************************************************************************/
MMBroadbandModem *
mm_broadband_modem_new (const gchar *device,
const gchar *physdev,
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_PHYSDEV, physdev,
MM_BASE_MODEM_DRIVERS, drivers,
MM_BASE_MODEM_PLUGIN, plugin,
MM_BASE_MODEM_VENDOR_ID, vendor_id,
MM_BASE_MODEM_PRODUCT_ID, product_id,
/* Generic bearer supports TTY only */
MM_BASE_MODEM_DATA_NET_SUPPORTED, FALSE,
MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
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_PROFILE_MANAGER_DBUS_SKELETON:
g_clear_object (&self->priv->modem_3gpp_profile_manager_dbus_skeleton);
self->priv->modem_3gpp_profile_manager_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_CELL_BROADCAST_DBUS_SKELETON:
g_clear_object (&self->priv->modem_cell_broadcast_dbus_skeleton);
self->priv->modem_cell_broadcast_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_SAR_DBUS_SKELETON:
g_clear_object (&self->priv->modem_sar_dbus_skeleton);
self->priv->modem_sar_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_SIM_SLOTS:
g_clear_pointer (&self->priv->modem_sim_slots, g_ptr_array_unref);
self->priv->modem_sim_slots = g_value_dup_boxed (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_5GS_NETWORK_SUPPORTED:
self->priv->modem_3gpp_5gs_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_3GPP_PACKET_SERVICE_STATE:
self->priv->modem_3gpp_packet_service_state = g_value_get_enum (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_CELL_BROADCAST_CBM_LIST:
g_clear_object (&self->priv->modem_cell_broadcast_cbm_list);
self->priv->modem_cell_broadcast_cbm_list = g_value_dup_object (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_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_INDICATION_CALL_LIST_RELOAD_ENABLED:
self->priv->indication_call_list_reload_enabled = 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_MODEM_FIRMWARE_IGNORE_CARRIER:
self->priv->modem_firmware_ignore_carrier = g_value_get_boolean (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_PROFILE_MANAGER_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_3gpp_profile_manager_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_CELL_BROADCAST_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_cell_broadcast_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_SAR_DBUS_SKELETON:
g_value_set_object (value, self->priv->modem_sar_dbus_skeleton);
break;
case PROP_MODEM_SIM:
g_value_set_object (value, self->priv->modem_sim);
break;
case PROP_MODEM_SIM_SLOTS:
g_value_set_boxed (value, self->priv->modem_sim_slots);
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_5GS_NETWORK_SUPPORTED:
g_value_set_boolean (value, self->priv->modem_3gpp_5gs_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_3GPP_PACKET_SERVICE_STATE:
g_value_set_enum (value, self->priv->modem_3gpp_packet_service_state);
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_CELL_BROADCAST_CBM_LIST:
g_value_set_object (value, self->priv->modem_cell_broadcast_cbm_list);
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_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_INDICATION_CALL_LIST_RELOAD_ENABLED:
g_value_set_boolean (value, self->priv->indication_call_list_reload_enabled);
break;
case PROP_MODEM_CARRIER_CONFIG_MAPPING:
g_value_set_string (value, self->priv->carrier_config_mapping);
break;
case PROP_MODEM_FIRMWARE_IGNORE_CARRIER:
g_value_set_boolean (value, self->priv->modem_firmware_ignore_carrier);
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_5gs_network_supported = FALSE;
self->priv->modem_3gpp_ignored_facility_locks = MM_MODEM_3GPP_FACILITY_NONE;
self->priv->modem_3gpp_packet_service_state = MM_MODEM_3GPP_PACKET_SERVICE_STATE_UNKNOWN;
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->indication_call_list_reload_enabled = 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->modem_cgerep_enable_mode = MM_3GPP_CGEREP_MODE_NONE;
self->priv->modem_cgerep_disable_mode = MM_3GPP_CGEREP_MODE_NONE;
self->priv->flow_control = MM_FLOW_CONTROL_NONE;
self->priv->initial_eps_bearer_cid = -1;
}
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)
g_regex_unref (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_profile_manager_dbus_skeleton) {
mm_iface_modem_3gpp_profile_manager_shutdown (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (object));
g_clear_object (&self->priv->modem_3gpp_profile_manager_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_cell_broadcast_dbus_skeleton) {
mm_iface_modem_cell_broadcast_shutdown (MM_IFACE_MODEM_CELL_BROADCAST (object));
g_clear_object (&self->priv->modem_cell_broadcast_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);
}
if (self->priv->modem_sar_dbus_skeleton) {
mm_iface_modem_sar_shutdown (MM_IFACE_MODEM_SAR (object));
g_clear_object (&self->priv->modem_sar_dbus_skeleton);
}
g_clear_object (&self->priv->modem_3gpp_initial_eps_bearer);
g_clear_object (&self->priv->modem_sim);
g_clear_pointer (&self->priv->modem_sim_slots, g_ptr_array_unref);
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_clear_object (&self->priv->modem_cell_broadcast_cbm_list);
if (self->priv->enabled_ports_ctx)
ports_context_dispose (self->priv->enabled_ports_ctx);
if (self->priv->sim_hot_swap_ports_ctx)
ports_context_dispose (self->priv->sim_hot_swap_ports_ctx);
if (self->priv->in_call_ports_ctx)
ports_context_dispose (self->priv->in_call_ports_ctx);
G_OBJECT_CLASS (mm_broadband_modem_parent_class)->dispose (object);
}
static void
iface_modem_init (MMIfaceModemInterface *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;
iface->create_bearer_list = modem_create_bearer_list;
/* 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 (MMIfaceModem3gppInterface *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;
iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings;
iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish;
/* Enabling steps */
iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
iface->enable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
iface->setup_unsolicited_registration_events = modem_3gpp_setup_unsolicited_registration_events;
iface->setup_unsolicited_registration_events_finish = modem_3gpp_setup_unsolicited_registration_events_finish;
iface->enable_unsolicited_registration_events = modem_3gpp_enable_unsolicited_registration_events;
iface->enable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish;
/* Disabling steps */
iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
iface->disable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish;
iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish;
iface->disable_unsolicited_registration_events = modem_3gpp_disable_unsolicited_registration_events;
iface->disable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish;
iface->cleanup_unsolicited_registration_events = modem_3gpp_cleanup_unsolicited_registration_events;
iface->cleanup_unsolicited_registration_events_finish = modem_3gpp_cleanup_unsolicited_registration_events_finish;
/* Additional actions */
iface->load_operator_code = modem_3gpp_load_operator_code;
iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish;
iface->load_operator_name = modem_3gpp_load_operator_name;
iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish;
iface->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer;
iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_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;
iface->set_packet_service_state = modem_3gpp_set_packet_service_state;
iface->set_packet_service_state_finish = modem_3gpp_set_packet_service_state_finish;
iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings;
iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish;
}
static void
iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManagerInterface *iface)
{
/* Initialization steps */
iface->check_support = modem_3gpp_profile_manager_check_support;
iface->check_support_finish = modem_3gpp_profile_manager_check_support_finish;
/* User actions */
iface->list_profiles = modem_3gpp_profile_manager_list_profiles;
iface->list_profiles_finish = modem_3gpp_profile_manager_list_profiles_finish;
iface->delete_profile = modem_3gpp_profile_manager_delete_profile;
iface->delete_profile_finish = modem_3gpp_profile_manager_delete_profile_finish;
iface->check_format = modem_3gpp_profile_manager_check_format;
iface->check_format_finish = modem_3gpp_profile_manager_check_format_finish;
iface->check_activated_profile = modem_3gpp_profile_manager_check_activated_profile;
iface->check_activated_profile_finish = modem_3gpp_profile_manager_check_activated_profile_finish;
iface->deactivate_profile = modem_3gpp_profile_manager_deactivate_profile;
iface->deactivate_profile_finish = modem_3gpp_profile_manager_deactivate_profile_finish;
iface->store_profile = modem_3gpp_profile_manager_store_profile;
iface->store_profile_finish = modem_3gpp_profile_manager_store_profile_finish;
}
static void
iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssdInterface *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 (MMIfaceModemCdmaInterface *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_cell_broadcast_init (MMIfaceModemCellBroadcastInterface *iface)
{
iface->check_support = modem_cell_broadcast_check_support;
iface->check_support_finish = modem_cell_broadcast_check_support_finish;
iface->setup_unsolicited_events = modem_cell_broadcast_setup_unsolicited_events;
iface->setup_unsolicited_events_finish = modem_cell_broadcast_setup_cleanup_unsolicited_events_finish;
iface->load_channels = modem_cell_broadcast_load_channels;
iface->load_channels_finish = modem_cell_broadcast_load_channels_finish;
iface->cleanup_unsolicited_events = modem_cell_broadcast_cleanup_unsolicited_events;
iface->cleanup_unsolicited_events_finish = modem_cell_broadcast_setup_cleanup_unsolicited_events_finish;
iface->set_channels = modem_cell_broadcast_set_channels;
iface->set_channels_finish = modem_cell_broadcast_set_channels_finish;
}
static void
iface_modem_simple_init (MMIfaceModemSimpleInterface *iface)
{
}
static void
iface_modem_location_init (MMIfaceModemLocationInterface *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 (MMIfaceModemMessagingInterface *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->init_current_storages = modem_messaging_init_current_storages;
iface->init_current_storages_finish = modem_messaging_init_current_storages_finish;
iface->lock_storages = modem_messaging_lock_storages;
iface->lock_storages_finish = modem_messaging_lock_storages_finish;
iface->unlock_storages = modem_messaging_unlock_storages;
}
static void
iface_modem_voice_init (MMIfaceModemVoiceInterface *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 (MMIfaceModemTimeInterface *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 (MMIfaceModemSignalInterface *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 (MMIfaceModemOmaInterface *iface)
{
}
static void
iface_modem_firmware_init (MMIfaceModemFirmwareInterface *iface)
{
iface->load_update_settings = modem_firmware_load_update_settings;
iface->load_update_settings_finish = modem_firmware_load_update_settings_finish;
}
static void
iface_modem_sar_init (MMIfaceModemSarInterface *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;
#if defined WITH_SUSPEND_RESUME
base_modem_class->sync = synchronize;
base_modem_class->sync_finish = synchronize_finish;
base_modem_class->terse = terse;
base_modem_class->terse_finish = terse_finish;
#endif
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;
klass->load_initial_eps_bearer_cid = load_initial_eps_bearer_cid;
klass->load_initial_eps_bearer_cid_finish = load_initial_eps_bearer_cid_finish;
klass->create_sms = modem_messaging_create_sms;
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_PROFILE_MANAGER_DBUS_SKELETON,
MM_IFACE_MODEM_3GPP_PROFILE_MANAGER_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_CELL_BROADCAST_DBUS_SKELETON,
MM_IFACE_MODEM_CELL_BROADCAST_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_SAR_DBUS_SKELETON,
MM_IFACE_MODEM_SAR_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_SIM_SLOTS,
MM_IFACE_MODEM_SIM_SLOTS);
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_5GS_NETWORK_SUPPORTED,
MM_IFACE_MODEM_3GPP_5GS_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_3GPP_PACKET_SERVICE_STATE,
MM_IFACE_MODEM_3GPP_PACKET_SERVICE_STATE);
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_CELL_BROADCAST_CBM_LIST,
MM_IFACE_MODEM_CELL_BROADCAST_CBM_LIST);
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_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_INDICATION_CALL_LIST_RELOAD_ENABLED,
MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED);
g_object_class_override_property (object_class,
PROP_MODEM_CARRIER_CONFIG_MAPPING,
MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING);
g_object_class_override_property (object_class,
PROP_MODEM_FIRMWARE_IGNORE_CARRIER,
MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER);
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]);
#if defined WITH_SUSPEND_RESUME
signals[SIGNAL_SYNC_NEEDED] =
g_signal_new (MM_BROADBAND_MODEM_SIGNAL_SYNC_NEEDED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (MMBroadbandModemClass, sync_needed),
NULL, NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE, 0);
#endif
}