diff options
Diffstat (limited to 'src/plugins/cinterion/mm-broadband-modem-cinterion.c')
-rw-r--r-- | src/plugins/cinterion/mm-broadband-modem-cinterion.c | 3356 |
1 files changed, 3356 insertions, 0 deletions
diff --git a/src/plugins/cinterion/mm-broadband-modem-cinterion.c b/src/plugins/cinterion/mm-broadband-modem-cinterion.c new file mode 100644 index 00000000..b063d454 --- /dev/null +++ b/src/plugins/cinterion/mm-broadband-modem-cinterion.c @@ -0,0 +1,3356 @@ +/* -*- 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) 2011 Ammonit Measurement GmbH + * Copyright (C) 2011 Google Inc. + * Copyright (C) 2016 Trimble Navigation Limited + * Author: Aleksander Morgado <aleksander@lanedo.com> + * Contributor: Matthew Stanger <matthew_stanger@trimble.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "ModemManager.h" +#include "mm-modem-helpers.h" +#include "mm-serial-parsers.h" +#include "mm-log-object.h" +#include "mm-errors-types.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-messaging.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-voice.h" +#include "mm-base-modem-at.h" +#include "mm-broadband-modem-cinterion.h" +#include "mm-modem-helpers-cinterion.h" +#include "mm-shared-cinterion.h" +#include "mm-broadband-bearer-cinterion.h" +#include "mm-iface-modem-signal.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_voice_init (MMIfaceModemVoice *iface); +static void iface_modem_time_init (MMIfaceModemTime *iface); +static void iface_modem_signal_init (MMIfaceModemSignal *iface); +static void shared_cinterion_init (MMSharedCinterion *iface); + +static MMIfaceModem *iface_modem_parent; +static MMIfaceModem3gpp *iface_modem_3gpp_parent; +static MMIfaceModemLocation *iface_modem_location_parent; +static MMIfaceModemVoice *iface_modem_voice_parent; +static MMIfaceModemTime *iface_modem_time_parent; +static MMIfaceModemSignal *iface_modem_signal_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemCinterion, mm_broadband_modem_cinterion, MM_TYPE_BROADBAND_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_MESSAGING, iface_modem_messaging_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_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_SHARED_CINTERION, shared_cinterion_init)) + +typedef enum { + FEATURE_SUPPORT_UNKNOWN, + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, +} FeatureSupport; + +struct _MMBroadbandModemCinterionPrivate { + /* Command to go into sleep mode */ + gchar *sleep_mode_cmd; + + /* Cached supported bands in Cinterion format */ + guint supported_bands[MM_CINTERION_RB_BLOCK_N]; + + /* Cached supported modes for SMS setup */ + GArray *cnmi_supported_mode; + GArray *cnmi_supported_mt; + GArray *cnmi_supported_bm; + GArray *cnmi_supported_ds; + GArray *cnmi_supported_bfr; + + /* Cached supported rats for SXRAT */ + GArray *sxrat_supported_rat; + GArray *sxrat_supported_pref1; + + /* ignore regex */ + GRegex *sysstart_regex; + /* +CIEV indications as configured via AT^SIND */ + GRegex *ciev_regex; + /* Ignore SIM hotswap SCKS msg, until ready */ + GRegex *scks_regex; + + /* Flags for feature support checks */ + FeatureSupport swwan_support; + FeatureSupport sind_psinfo_support; + FeatureSupport smoni_support; + FeatureSupport sind_simstatus_support; + FeatureSupport sxrat_support; + + /* Mode combination to apply if "any" requested */ + MMModemMode any_allowed; + + /* Flags for model-based behaviors */ + MMCinterionModemFamily modem_family; + MMCinterionRadioBandFormat rb_format; + + /* Initial EPS bearer context number */ + gint initial_eps_bearer_cid; +}; + +/*****************************************************************************/ + +MMCinterionModemFamily +mm_broadband_modem_cinterion_get_family (MMBroadbandModemCinterion *self) +{ + return self->priv->modem_family; +} + +/*****************************************************************************/ +/* Check support (Signal interface) */ + +static gboolean +signal_check_support_finish (MMIfaceModemSignal *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_signal_check_support_ready (MMIfaceModemSignal *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_signal_parent->check_support_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +check_smoni_support (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + + /* Fetch the result to the SMONI test. If no response given (error triggered), assume unsupported */ + if (mm_base_modem_at_command_finish (_self, res, NULL)) { + mm_obj_dbg (self, "SMONI supported"); + self->priv->smoni_support = FEATURE_SUPPORTED; + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "SMONI unsupported"); + self->priv->smoni_support = FEATURE_NOT_SUPPORTED; + + /* Otherwise, check if the parent CESQ-based implementation works */ + g_assert (iface_modem_signal_parent->check_support && iface_modem_signal_parent->check_support_finish); + iface_modem_signal_parent->check_support (MM_IFACE_MODEM_SIGNAL (self), + (GAsyncReadyCallback) parent_signal_check_support_ready, + task); +} + +static void +signal_check_support (MMIfaceModemSignal *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), + "^SMONI=?", + 3, + TRUE, + (GAsyncReadyCallback) check_smoni_support, + task); +} + +/*****************************************************************************/ +/* Load extended signal information (Signal interface) */ + +static gboolean +signal_load_values_finish (MMIfaceModemSignal *_self, + GAsyncResult *res, + MMSignal **cdma, + MMSignal **evdo, + MMSignal **gsm, + MMSignal **umts, + MMSignal **lte, + MMSignal **nr5g, + GError **error) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + + if (self->priv->smoni_support == FEATURE_NOT_SUPPORTED) + return iface_modem_signal_parent->load_values_finish (_self, res, cdma, evdo, gsm, umts, lte, nr5g, error); + + response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error); + if (!response || !mm_cinterion_smoni_response_to_signal_info (response, gsm, umts, lte, error)) + return FALSE; + + if (cdma) + *cdma = NULL; + if (evdo) + *evdo = NULL; + if (nr5g) + *nr5g = NULL; + + return TRUE; +} + +static void +signal_load_values (MMIfaceModemSignal *_self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + + if (self->priv->smoni_support == FEATURE_SUPPORTED) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SMONI", + 3, + FALSE, + callback, + user_data); + return; + } + + /* ^SMONI not supported, fallback to the parent */ + iface_modem_signal_parent->load_values (_self, cancellable, callback, user_data); +} + +/*****************************************************************************/ +/* Enable unsolicited events (SMS indications) (Messaging interface) */ + +static gboolean +messaging_enable_unsolicited_events_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cnmi_test_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 gboolean +value_supported (const GArray *array, + const guint value) +{ + guint i; + + if (!array) + return FALSE; + + for (i = 0; i < array->len; i++) { + if (g_array_index (array, guint, i) == value) + return TRUE; + } + return FALSE; +} + +static void +messaging_enable_unsolicited_events (MMIfaceModemMessaging *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GString *cmd; + GError *error = NULL; + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* AT+CNMI=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]] */ + cmd = g_string_new ("+CNMI="); + + /* Mode 2 or 1 */ + if (value_supported (self->priv->cnmi_supported_mode, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_mode, 1)) + g_string_append_printf (cmd, "%u,", 1); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,1] <mode>"); + goto out; + } + + /* mt 2 or 1 */ + if (value_supported (self->priv->cnmi_supported_mt, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_mt, 1)) + g_string_append_printf (cmd, "%u,", 1); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,1] <mt>"); + goto out; + } + + /* bm 2 or 0 */ + if (value_supported (self->priv->cnmi_supported_bm, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_bm, 0)) + g_string_append_printf (cmd, "%u,", 0); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,0] <bm>"); + goto out; + } + + /* ds 2, 1 or 0 */ + if (value_supported (self->priv->cnmi_supported_ds, 2)) + g_string_append_printf (cmd, "%u,", 2); + else if (value_supported (self->priv->cnmi_supported_ds, 1)) + g_string_append_printf (cmd, "%u,", 1); + else if (value_supported (self->priv->cnmi_supported_ds, 0)) + g_string_append_printf (cmd, "%u,", 0); + else { + error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "SMS settings don't accept [2,1,0] <ds>"); + goto out; + } + + /* bfr 1 */ + if (value_supported (self->priv->cnmi_supported_bfr, 1)) + g_string_append_printf (cmd, "%u", 1); + /* otherwise, skip setting it */ + +out: + /* Early error report */ + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + g_string_free (cmd, TRUE); + return; + } + + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd->str, + 3, + FALSE, + (GAsyncReadyCallback)cnmi_test_ready, + task); + g_string_free (cmd, TRUE); +} + +/*****************************************************************************/ +/* Check if Messaging supported (Messaging interface) */ + +static gboolean +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 (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GError *error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Parse */ + if (!mm_cinterion_parse_cnmi_test (response, + &self->priv->cnmi_supported_mode, + &self->priv->cnmi_supported_mt, + &self->priv->cnmi_supported_bm, + &self->priv->cnmi_supported_ds, + &self->priv->cnmi_supported_bfr, + &error)) { + mm_obj_warn (self, "error reading SMS setup: %s", error->message); + g_error_free (error); + } + + /* CNMI command is supported; assume we have full messaging capabilities */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +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); +} + +/*****************************************************************************/ +/* Power down */ + +static gboolean +modem_power_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +sleep_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error)) + mm_obj_dbg (self, "couldn't send power down command: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +send_sleep_mode_command (GTask *task) +{ + MMBroadbandModemCinterion *self; + + self = g_task_get_source_object (task); + + if (self->priv->sleep_mode_cmd && self->priv->sleep_mode_cmd[0]) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + self->priv->sleep_mode_cmd, + 5, + FALSE, + (GAsyncReadyCallback)sleep_ready, + task); + return; + } + + /* No default command; just finish without sending anything */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +supported_functionality_status_query_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + g_autoptr(GError) error = NULL; + + g_assert (self->priv->sleep_mode_cmd == NULL); + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) { + mm_obj_warn (self, "couldn't query supported functionality status: %s", error->message); + self->priv->sleep_mode_cmd = g_strdup (""); + } else { + /* We need to get which power-off command to use to put the modem in low + * power mode (with serial port open for AT commands, but with RF switched + * off). According to the documentation of various Cinterion modems, some + * support AT+CFUN=4 (HC25) and those which don't support it can use + * AT+CFUN=7 (CYCLIC SLEEP mode with 2s timeout after last character + * received in the serial port). + * + * So, just look for '4' in the reply; if not found, look for '7', and if + * not found, report warning and don't use any. + */ + if (strstr (response, "4") != NULL) { + mm_obj_dbg (self, "device supports CFUN=4 sleep mode"); + self->priv->sleep_mode_cmd = g_strdup ("+CFUN=4"); + } else if (strstr (response, "7") != NULL) { + mm_obj_dbg (self, "device supports CFUN=7 sleep mode"); + self->priv->sleep_mode_cmd = g_strdup ("+CFUN=7"); + } else { + mm_obj_warn (self, "unknown functionality mode to go into sleep mode"); + self->priv->sleep_mode_cmd = g_strdup (""); + } + } + + send_sleep_mode_command (task); +} + +static void +modem_power_down (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* If sleep command already decided, use it. */ + if (self->priv->sleep_mode_cmd) + send_sleep_mode_command (task); + else + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=?", + 3, + FALSE, + (GAsyncReadyCallback)supported_functionality_status_query_ready, + task); +} + +/*****************************************************************************/ +/* Modem Power Off */ + +#define MAX_POWER_OFF_WAIT_TIME_SECS 20 + +typedef struct { + MMPortSerialAt *port; + GRegex *shutdown_regex; + gboolean shutdown_received; + gboolean smso_replied; + gboolean serial_open; + guint timeout_id; +} PowerOffContext; + +static void +power_off_context_free (PowerOffContext *ctx) +{ + if (ctx->serial_open) + mm_port_serial_close (MM_PORT_SERIAL (ctx->port)); + if (ctx->timeout_id) + g_source_remove (ctx->timeout_id); + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL); + g_object_unref (ctx->port); + g_regex_unref (ctx->shutdown_regex); + g_slice_free (PowerOffContext, ctx); +} + +static gboolean +modem_power_off_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +complete_power_off (GTask *task) +{ + PowerOffContext *ctx; + + ctx = g_task_get_task_data (task); + + if (!ctx->shutdown_received || !ctx->smso_replied) + return; + + /* remove timeout right away */ + g_assert (ctx->timeout_id); + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +smso_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + PowerOffContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + if (!mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Set as replied and see if we can complete */ + ctx->smso_replied = TRUE; + complete_power_off (task); +} + +static void +shutdown_received (MMPortSerialAt *port, + GMatchInfo *match_info, + GTask *task) +{ + PowerOffContext *ctx; + + ctx = g_task_get_task_data (task); + + /* Cleanup handler right away, we don't want it called any more */ + mm_port_serial_at_add_unsolicited_msg_handler (port, ctx->shutdown_regex, NULL, NULL, NULL); + + /* Set as received and see if we can complete */ + ctx->shutdown_received = TRUE; + complete_power_off (task); +} + +static gboolean +power_off_timeout_cb (GTask *task) +{ + PowerOffContext *ctx; + + ctx = g_task_get_task_data (task); + + ctx->timeout_id = 0; + + /* The SMSO reply should have come earlier */ + g_warn_if_fail (ctx->smso_replied == TRUE); + + /* Cleanup handler right away, we no longer want to receive it */ + mm_port_serial_at_add_unsolicited_msg_handler (ctx->port, ctx->shutdown_regex, NULL, NULL, NULL); + + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Power off operation timed out"); + g_object_unref (task); + + return G_SOURCE_REMOVE; +} + +static void +modem_power_off (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + PowerOffContext *ctx; + GError *error = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + ctx = g_slice_new0 (PowerOffContext); + ctx->port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); + ctx->shutdown_regex = g_regex_new ("\\r\\n\\^SHUTDOWN\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + ctx->timeout_id = g_timeout_add_seconds (MAX_POWER_OFF_WAIT_TIME_SECS, + (GSourceFunc)power_off_timeout_cb, + task); + g_task_set_task_data (task, ctx, (GDestroyNotify) power_off_context_free); + + /* We'll need to wait for a ^SHUTDOWN before returning the action, which is + * when the modem tells us that it is ready to be shutdown */ + mm_port_serial_at_add_unsolicited_msg_handler ( + ctx->port, + ctx->shutdown_regex, + (MMPortSerialAtUnsolicitedMsgFn)shutdown_received, + task, + NULL); + + /* In order to get the ^SHUTDOWN notification, we must keep the port open + * during the wait time */ + ctx->serial_open = mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error); + if (G_UNLIKELY (error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Note: we'll use a timeout < MAX_POWER_OFF_WAIT_TIME_SECS for the AT command, + * so we're sure that the AT command reply will always come before the timeout + * fires */ + g_assert (MAX_POWER_OFF_WAIT_TIME_SECS > 5); + mm_base_modem_at_command_full (MM_BASE_MODEM (self), + ctx->port, + "^SMSO", + 5, + FALSE, /* allow_cached */ + FALSE, /* is_raw */ + NULL, /* cancellable */ + (GAsyncReadyCallback)smso_ready, + task); +} + +/*****************************************************************************/ +/* Access technologies polling */ + +static gboolean +load_access_technologies_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemAccessTechnology *access_technologies, + guint *mask, + GError **error) +{ + GError *inner_error = NULL; + gssize val; + + val = g_task_propagate_int (G_TASK (res), &inner_error); + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + *access_technologies = (MMModemAccessTechnology) val; + *mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY; + return TRUE; +} + +static void +smong_query_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + const gchar *response; + GError *error = NULL; + MMModemAccessTechnology access_tech; + + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || !mm_cinterion_parse_smong_response (response, &access_tech, &error)) + g_task_return_error (task, error); + else + g_task_return_int (task, (gssize) access_tech); + g_object_unref (task); +} + +static void +load_access_technologies (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Abort access technology polling if ^SIND psinfo URCs are enabled */ + if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "No need to poll access technologies"); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "^SMONG", + 3, + FALSE, + (GAsyncReadyCallback)smong_query_ready, + task); +} + +/*****************************************************************************/ +/* Disable unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +parent_disable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!iface_modem_3gpp_parent->disable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't disable parent 3GPP unsolicited events: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_disable_unsolicited_messages (GTask *task) +{ + /* Chain up parent's disable */ + iface_modem_3gpp_parent->disable_unsolicited_events ( + MM_IFACE_MODEM_3GPP (g_task_get_source_object (task)), + (GAsyncReadyCallback)parent_disable_unsolicited_events_ready, + task); +} + +static void +sind_psinfo_disable_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + mm_obj_warn (self, "Couldn't disable ^SIND psinfo notifications: %s", error->message); + + parent_disable_unsolicited_messages (task); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self; + GTask *task; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) { + /* Disable access technology update reporting */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SIND=\"psinfo\",0", + 3, + FALSE, + (GAsyncReadyCallback)sind_psinfo_disable_ready, + task); + return; + } + + parent_disable_unsolicited_messages (task); +} + +/*****************************************************************************/ +/* Enable unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +sind_psinfo_enable_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self; + g_autoptr(GError) error = NULL; + const gchar *response; + guint mode; + guint val; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) { + /* something went wrong, disable indicator */ + self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED; + mm_obj_warn (self, "couldn't enable ^SIND psinfo notifications: %s", error->message); + } else if (!mm_cinterion_parse_sind_response (response, NULL, &mode, &val, &error)) { + /* problem with parsing, disable indicator */ + self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED; + mm_obj_warn (self, "couldn't parse ^SIND psinfo response: %s", error->message); + } else { + /* Report initial access technology gathered right away */ + mm_obj_dbg (self, "reporting initial access technologies..."); + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + mm_cinterion_get_access_technology_from_sind_psinfo (val, self), + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +set_urc_dest_port_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self; + g_autoptr(GError) error = NULL; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + + if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, &error)) + mm_obj_dbg (self, "couldn't guarantee unsolicited events are sent to the correct port: %s", error->message); + + if (self->priv->sind_psinfo_support == FEATURE_SUPPORTED) { + /* Enable access technology update reporting */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SIND=\"psinfo\",1", + 3, + FALSE, + (GAsyncReadyCallback)sind_psinfo_enable_ready, + task); + return; + } + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + + if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) + mm_obj_warn (self, "couldn't enable parent 3GPP unsolicited events: %s", error->message); + + /* Make sure unsolicited events are sent to an AT port (PLS9 can default to DATA port) */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SCFG=\"URC/DstIfc\",\"app\"", + 5, + FALSE, + (GAsyncReadyCallback)set_urc_dest_port_ready, + task); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Chain up parent's enable */ + iface_modem_3gpp_parent->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_enable_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited events (3GPP interface) */ + +static void +sind_ciev_received (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemCinterion *self) +{ + guint val = 0; + gchar *indicator; + + indicator = mm_get_string_unquoted_from_match_info (match_info, 1); + if (!mm_get_uint_from_match_info (match_info, 2, &val)) + mm_obj_dbg (self, "couldn't parse indicator '%s' value", indicator); + else { + mm_obj_dbg (self, "received indicator '%s' update: %u", indicator, val); + if (g_strcmp0 (indicator, "psinfo") == 0) { + mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), + mm_cinterion_get_access_technology_from_sind_psinfo (val, self), + MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK); + } + } + g_free (indicator); +} + +static void +set_unsolicited_events_handlers (MMBroadbandModemCinterion *self, + gboolean enable) +{ + MMPortSerialAt *ports[2]; + 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)); + + /* Enable unsolicited events in given port */ + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->ciev_regex, + enable ? (MMPortSerialAtUnsolicitedMsgFn)sind_ciev_received : NULL, + enable ? self : NULL, + NULL); + } +} + +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 +parent_setup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->setup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else { + /* Our own setup now */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), TRUE); + g_task_return_boolean (task, TRUE); + } + g_object_unref (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); + + /* Chain up parent's setup */ + iface_modem_3gpp_parent->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_setup_unsolicited_events_ready, + task); +} + +static void +parent_cleanup_unsolicited_events_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!iface_modem_3gpp_parent->cleanup_unsolicited_events_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* Our own cleanup first */ + set_unsolicited_events_handlers (MM_BROADBAND_MODEM_CINTERION (self), FALSE); + + /* And now chain up parent's cleanup */ + iface_modem_3gpp_parent->cleanup_unsolicited_events ( + self, + (GAsyncReadyCallback)parent_cleanup_unsolicited_events_ready, + task); +} + +/*****************************************************************************/ +/* Common operation to load expected CID for the initial EPS bearer */ + +static gboolean +load_initial_eps_bearer_cid_finish (MMBroadbandModemCinterion *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +scfg_prov_cfg_query_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + g_autoptr(GError) error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) + mm_obj_dbg (self, "couldn't query MNO profiles: %s", error->message); + + else if (!mm_cinterion_provcfg_response_to_cid (response, + MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + self, + &self->priv->initial_eps_bearer_cid, + &error)) + mm_obj_dbg (self, "failed processing list of MNO profiles: %s", error->message); + + if (self->priv->initial_eps_bearer_cid < 0) { + mm_obj_dbg (self, "using default EPS bearer context id: 1"); + self->priv->initial_eps_bearer_cid = 1; + } else + mm_obj_dbg (self, "loaded EPS bearer context id from list of MNO profiles: %d", self->priv->initial_eps_bearer_cid); + + /* This operation really never fails */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +load_initial_eps_bearer_cid (MMBroadbandModemCinterion *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + g_assert (self->priv->initial_eps_bearer_cid < 0); + + task = g_task_new (self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SCFG=\"MEopMode/Prov/Cfg\"", + 20, + FALSE, + (GAsyncReadyCallback)scfg_prov_cfg_query_ready, + task); +} + +/*****************************************************************************/ +/* Set initial EPS bearer settings */ + +typedef enum { + SET_INITIAL_EPS_STEP_FIRST = 0, + SET_INITIAL_EPS_STEP_CHECK_MODE, + SET_INITIAL_EPS_STEP_RF_OFF, + SET_INITIAL_EPS_STEP_APN, + SET_INITIAL_EPS_STEP_AUTH, + SET_INITIAL_EPS_STEP_RF_ON, + SET_INITIAL_EPS_STEP_LAST, +} SetInitialEpsStep; + +typedef struct { + MMBearerProperties *properties; + SetInitialEpsStep step; + guint initial_cfun_mode; + GError *saved_error; +} SetInitialEpsContext; + +static void +set_initial_eps_context_free (SetInitialEpsContext *ctx) +{ + g_assert (!ctx->saved_error); + g_object_unref (ctx->properties); + g_slice_free (SetInitialEpsContext, ctx); +} + +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_step (GTask *task); + +static void +set_initial_eps_rf_on_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + g_autoptr(GError) error = NULL; + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't set RF back on: %s", error->message); + if (!ctx->saved_error) + ctx->saved_error = g_steal_pointer (&error); + } + + /* Go to next step */ + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_auth_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) { + mm_obj_warn (self, "couldn't configure context %d auth settings: %s", + self->priv->initial_eps_bearer_cid, ctx->saved_error->message); + /* Fallback to recover RF before returning the error */ + ctx->step = SET_INITIAL_EPS_STEP_RF_ON; + } else { + /* Go to next step */ + ctx->step++; + } + set_initial_eps_step (task); +} + +static void +set_initial_eps_cgdcont_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (_self, res, &ctx->saved_error)) { + mm_obj_warn (self, "couldn't configure context %d settings: %s", + self->priv->initial_eps_bearer_cid, ctx->saved_error->message); + /* Fallback to recover RF before returning the error */ + ctx->step = SET_INITIAL_EPS_STEP_RF_ON; + } else { + /* Go to next step */ + ctx->step++; + } + set_initial_eps_step (task); +} + +static void +set_initial_eps_rf_off_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + SetInitialEpsContext *ctx; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + + if (!mm_base_modem_at_command_finish (self, res, &error)) { + mm_obj_warn (self, "couldn't set RF off: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go to next step */ + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_cfun_mode_load_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + const gchar *response; + SetInitialEpsContext *ctx; + guint mode; + + ctx = (SetInitialEpsContext *) g_task_get_task_data (task); + response = mm_base_modem_at_command_finish (self, res, &error); + if (!response || !mm_3gpp_parse_cfun_query_response (response, &mode, &error)) { + mm_obj_warn (self, "couldn't load initial functionality mode: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "current functionality mode: %u", mode); + if (mode != 1 && mode != 4) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "cannot setup the default LTE bearer settings: " + "the SIM must be powered"); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->initial_cfun_mode = mode; + ctx->step++; + set_initial_eps_step (task); +} + +static void +set_initial_eps_step (GTask *task) +{ + MMBroadbandModemCinterion *self; + SetInitialEpsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case SET_INITIAL_EPS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_STEP_CHECK_MODE: + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN?", + 5, + FALSE, + (GAsyncReadyCallback)set_initial_eps_cfun_mode_load_ready, + task); + return; + + case SET_INITIAL_EPS_STEP_RF_OFF: + if (ctx->initial_cfun_mode != 4) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=4", + 5, + FALSE, + (GAsyncReadyCallback)set_initial_eps_rf_off_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_STEP_APN: { + const gchar *apn; + g_autofree gchar *quoted_apn = NULL; + g_autofree gchar *apn_cmd = NULL; + const gchar *ip_family_str; + MMBearerIpFamily ip_family; + + ip_family = mm_bearer_properties_get_ip_type (ctx->properties); + if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY) + ip_family = MM_BEARER_IP_FAMILY_IPV4; + + ip_family_str = mm_3gpp_get_pdp_type_from_ip_family (ip_family); + apn = mm_bearer_properties_get_apn (ctx->properties); + mm_obj_dbg (self, "context %d with APN '%s' and PDP type '%s'", + self->priv->initial_eps_bearer_cid, apn, ip_family_str); + quoted_apn = mm_port_serial_at_quote_string (apn); + apn_cmd = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s", + self->priv->initial_eps_bearer_cid, ip_family_str, quoted_apn); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + apn_cmd, + 20, + FALSE, + (GAsyncReadyCallback)set_initial_eps_cgdcont_ready, + task); + return; + } + + case SET_INITIAL_EPS_STEP_AUTH: { + g_autofree gchar *auth_cmd = NULL; + + auth_cmd = mm_cinterion_build_auth_string (self, + MM_BROADBAND_MODEM_CINTERION (self)->priv->modem_family, + ctx->properties, + self->priv->initial_eps_bearer_cid); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + auth_cmd, + 20, + FALSE, + (GAsyncReadyCallback)set_initial_eps_auth_ready, + task); + return; + } + + case SET_INITIAL_EPS_STEP_RF_ON: + if (ctx->initial_cfun_mode == 1) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CFUN=1", + 5, + FALSE, + (GAsyncReadyCallback)set_initial_eps_rf_on_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case SET_INITIAL_EPS_STEP_LAST: + if (ctx->saved_error) + g_task_return_error (task, g_steal_pointer (&ctx->saved_error)); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + SetInitialEpsContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + /* The initial EPS bearer settings should have already been loaded */ + g_assert (MM_BROADBAND_MODEM_CINTERION (self)->priv->initial_eps_bearer_cid >= 0); + + /* Setup context */ + ctx = g_slice_new0 (SetInitialEpsContext); + ctx->properties = g_object_ref (properties); + ctx->step = SET_INITIAL_EPS_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) set_initial_eps_context_free); + + set_initial_eps_step (task); +} + +/*****************************************************************************/ +/* Common initial EPS bearer info loading for both: + * - runtime status + * - configuration settings + */ + +typedef enum { + COMMON_LOAD_INITIAL_EPS_STEP_FIRST = 0, + COMMON_LOAD_INITIAL_EPS_STEP_PROFILE, + COMMON_LOAD_INITIAL_EPS_STEP_APN, + COMMON_LOAD_INITIAL_EPS_STEP_AUTH, + COMMON_LOAD_INITIAL_EPS_STEP_LAST, +} CommonLoadInitialEpsStep; + +typedef struct { + MMBearerProperties *properties; + CommonLoadInitialEpsStep step; + gboolean runtime; +} CommonLoadInitialEpsContext; + +static void +common_load_initial_eps_context_free (CommonLoadInitialEpsContext *ctx) +{ + g_clear_object (&ctx->properties); + g_slice_free (CommonLoadInitialEpsContext, ctx); +} + +static MMBearerProperties * +common_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 common_load_initial_eps_step (GTask *task); + +static void +common_load_initial_eps_auth_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + CommonLoadInitialEpsContext *ctx; + g_autoptr(GError) error = NULL; + MMBearerAllowedAuth auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN; + g_autofree gchar *username = NULL; + + ctx = (CommonLoadInitialEpsContext *) 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 context %d auth settings: %s", + self->priv->initial_eps_bearer_cid, error->message); + else if (!mm_cinterion_parse_sgauth_response (response, self->priv->initial_eps_bearer_cid, &auth, &username, &error)) + mm_obj_dbg (self, "couldn't parse context %d auth settings: %s", self->priv->initial_eps_bearer_cid, error->message); + else { + mm_bearer_properties_set_allowed_auth (ctx->properties, auth); + mm_bearer_properties_set_user (ctx->properties, username); + } + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_load_cid_ready (MMBroadbandModemCinterion *self, + GAsyncResult *res, + GTask *task) +{ + CommonLoadInitialEpsContext *ctx; + + ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task); + + load_initial_eps_bearer_cid_finish (self, res, NULL); + g_assert (self->priv->initial_eps_bearer_cid >= 0); + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_cgcontrdp_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + CommonLoadInitialEpsContext *ctx; + g_autofree gchar *apn = NULL; + g_autoptr(GError) error = NULL; + + ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task); + + /* errors aren't fatal */ + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) + mm_obj_dbg (self, "couldn't load context %d settings: %s", + self->priv->initial_eps_bearer_cid, error->message); + else if (!mm_3gpp_parse_cgcontrdp_response (response, NULL, NULL, &apn, NULL, NULL, NULL, NULL, NULL, &error)) + mm_obj_dbg (self, "couldn't parse CGDCONTRDP response: %s", error->message); + else + mm_bearer_properties_set_apn (ctx->properties, apn); + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_cgdcont_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + CommonLoadInitialEpsContext *ctx; + g_autoptr(GError) error = NULL; + + ctx = (CommonLoadInitialEpsContext *) g_task_get_task_data (task); + + /* errors aren't fatal */ + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response) + mm_obj_dbg (self, "couldn't load context %d status: %s", + self->priv->initial_eps_bearer_cid, error->message); + else { + GList *context_list; + + context_list = mm_3gpp_parse_cgdcont_read_response (response, &error); + if (!context_list) + mm_obj_dbg (self, "couldn't parse CGDCONT response: %s", error->message); + else { + GList *l; + + for (l = context_list; l; l = g_list_next (l)) { + MM3gppPdpContext *pdp = l->data; + + if (pdp->cid == (guint) self->priv->initial_eps_bearer_cid) { + mm_bearer_properties_set_ip_type (ctx->properties, pdp->pdp_type); + mm_bearer_properties_set_apn (ctx->properties, pdp->apn ? pdp->apn : ""); + break; + } + } + if (!l) + mm_obj_dbg (self, "no status reported for context %d", self->priv->initial_eps_bearer_cid); + mm_3gpp_pdp_context_list_free (context_list); + } + } + + /* Go to next step */ + ctx->step++; + common_load_initial_eps_step (task); +} + +static void +common_load_initial_eps_step (GTask *task) +{ + MMBroadbandModemCinterion *self; + CommonLoadInitialEpsContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case COMMON_LOAD_INITIAL_EPS_STEP_FIRST: + ctx->step++; + /* fall through */ + + case COMMON_LOAD_INITIAL_EPS_STEP_PROFILE: + /* Initial EPS bearer CID initialization run once only */ + if (G_UNLIKELY (self->priv->initial_eps_bearer_cid < 0)) { + load_initial_eps_bearer_cid ( + self, + (GAsyncReadyCallback)common_load_initial_eps_load_cid_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case COMMON_LOAD_INITIAL_EPS_STEP_APN: + if (ctx->runtime) { + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CGDCONT?", + 20, + FALSE, + (GAsyncReadyCallback)common_load_initial_eps_cgdcont_ready, + task); + } else { + g_autofree gchar *cmd = NULL; + + cmd = g_strdup_printf ("+CGCONTRDP=%u", self->priv->initial_eps_bearer_cid); + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "+CGCONTRDP", + 20, + FALSE, + (GAsyncReadyCallback)common_load_initial_eps_cgcontrdp_ready, + task); + } + return; + + case COMMON_LOAD_INITIAL_EPS_STEP_AUTH: + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + "^SGAUTH?", + 20, + FALSE, + (GAsyncReadyCallback)common_load_initial_eps_auth_ready, + task); + return; + + case COMMON_LOAD_INITIAL_EPS_STEP_LAST: + g_task_return_pointer (task, g_steal_pointer (&ctx->properties), g_object_unref); + g_object_unref (task); + return; + + default: + g_assert_not_reached (); + } +} + +static void +common_load_initial_eps_bearer (MMIfaceModem3gpp *self, + gboolean runtime, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + CommonLoadInitialEpsContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + + /* Setup context */ + ctx = g_slice_new0 (CommonLoadInitialEpsContext); + ctx->runtime = runtime; + ctx->properties = mm_bearer_properties_new (); + ctx->step = COMMON_LOAD_INITIAL_EPS_STEP_FIRST; + g_task_set_task_data (task, ctx, (GDestroyNotify) common_load_initial_eps_context_free); + + common_load_initial_eps_step (task); +} + +/*****************************************************************************/ +/* Initial EPS bearer runtime status loading */ + +static MMBearerProperties * +modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return common_load_initial_eps_bearer_finish (self, res, error); +} + +static void +modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_load_initial_eps_bearer (self, TRUE, callback, user_data); +} + +/*****************************************************************************/ +/* Initial EPS bearer settings loading -> set configuration */ + +static MMBearerProperties * +modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return common_load_initial_eps_bearer_finish (self, res, error); +} + +static void +modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_load_initial_eps_bearer (self, FALSE, callback, user_data); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +parent_load_supported_modes_ready (MMIfaceModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + GArray *all; + GArray *combinations; + GArray *filtered; + MMModemModeCombination mode; + + all = iface_modem_parent->load_supported_modes_finish (self, res, &error); + if (!all) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + if (mm_iface_modem_is_4g (self)) { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G, 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } else { + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + + /* Filter out those unsupported modes */ + filtered = mm_filter_supported_modes (all, combinations, self); + g_array_unref (all); + g_array_unref (combinations); + + g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +sxrat_load_supported_modes_ready (MMBroadbandModemCinterion *self, + GTask *task) +{ + GArray *combinations; + MMModemModeCombination mode; + + g_assert (self->priv->sxrat_supported_rat); + g_assert (self->priv->sxrat_supported_pref1); + + /* Build list of combinations */ + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 3); + + if (value_supported (self->priv->sxrat_supported_rat, 0)) { + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_rat, 1)) { + /* 2G+3G with none preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 0)) { + /* 2G preferred */ + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 2)) { + /* 3G preferred */ + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } + } + if (value_supported (self->priv->sxrat_supported_rat, 2)) { + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_rat, 3)) { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_rat, 4)) { + /* 3G+4G with none preferred */ + mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 2)) { + /* 3G preferred */ + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 3)) { + /* 4G preferred */ + mode.preferred = MM_MODEM_MODE_4G; + g_array_append_val (combinations, mode); + } + } + if (value_supported (self->priv->sxrat_supported_rat, 5)) { + /* 2G+4G with none preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 0)) { + /* 2G preferred */ + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 3)) { + /* 4G preferred */ + mode.preferred = MM_MODEM_MODE_4G; + g_array_append_val (combinations, mode); + } + } + if (value_supported (self->priv->sxrat_supported_rat, 6)) { + /* 2G+3G+4G with none preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + + self->priv->any_allowed = mode.allowed; + + if (value_supported (self->priv->sxrat_supported_pref1, 0)) { + /* 2G preferred */ + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 2)) { + /* 3G preferred */ + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + } + if (value_supported (self->priv->sxrat_supported_pref1, 3)) { + /* 4G preferred */ + mode.preferred = MM_MODEM_MODE_4G; + g_array_append_val (combinations, mode); + } + } + + g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +sxrat_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + g_autoptr(GError) error = NULL; + const gchar *response; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!error) { + mm_cinterion_parse_sxrat_test (response, + &self->priv->sxrat_supported_rat, + &self->priv->sxrat_supported_pref1, + NULL, + &error); + if (!error) { + self->priv->sxrat_support = FEATURE_SUPPORTED; + sxrat_load_supported_modes_ready (self, task); + return; + } + mm_obj_warn (self, "error reading SXRAT response: %s", error->message); + } + + self->priv->sxrat_support = FEATURE_NOT_SUPPORTED; + + /* Run parent's loading in case SXRAT is not supported */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + task); +} + +static void +load_supported_modes (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* First check SXRAT support, if not already done */ + if (self->priv->sxrat_support == FEATURE_SUPPORT_UNKNOWN) { + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SXRAT=?", + 3, + TRUE, + (GAsyncReadyCallback)sxrat_test_ready, + task); + return; + } + + if (self->priv->sxrat_support == FEATURE_SUPPORTED) { + sxrat_load_supported_modes_ready (self, task); + return; + } + + /* Run parent's loading */ + iface_modem_parent->load_supported_modes ( + MM_IFACE_MODEM (self), + (GAsyncReadyCallback)parent_load_supported_modes_ready, + task); +} + +/*****************************************************************************/ +/* Set current modes (Modem interface) */ + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +set_current_modes_reregister_in_network_ready (MMIfaceModem3gpp *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_iface_modem_3gpp_reregister_in_network_finish (self, res, &error)) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +allowed_access_technology_update_ready (MMBroadbandModemCinterion *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 +cops_set_current_modes (MMBroadbandModemCinterion *self, + MMModemMode allowed, + MMModemMode preferred, + GTask *task) +{ + gchar *command; + + g_assert (preferred == MM_MODEM_MODE_NONE); + + /* We will try to simulate the possible allowed modes here. The + * Cinterion devices do not seem to allow setting preferred access + * technology in devices, but they allow restricting to a given + * one: + * - 2G-only is forced by forcing GERAN RAT (AcT=0) + * - 3G-only is forced by forcing UTRAN RAT (AcT=2) + * - 4G-only is forced by forcing E-UTRAN RAT (AcT=7) + * - for the remaining ones, we default to automatic selection of RAT, + * which is based on the quality of the connection. + */ + + if (mm_iface_modem_is_4g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_4G) + command = g_strdup ("+COPS=,,,7"); + else if (mm_iface_modem_is_3g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_3G) + command = g_strdup ("+COPS=,,,2"); + else if (mm_iface_modem_is_2g (MM_IFACE_MODEM (self)) && allowed == MM_MODEM_MODE_2G) + command = g_strdup ("+COPS=,,,0"); + else { + /* For any other combination (e.g. ANY or no AcT given, defaults to Auto. For this case, we cannot provide + * AT+COPS=,,, (i.e. just without a last value). Instead, we need to + * re-run the last manual/automatic selection command which succeeded, + * (or auto by default if none was launched) */ + mm_iface_modem_3gpp_reregister_in_network (MM_IFACE_MODEM_3GPP (self), + (GAsyncReadyCallback) set_current_modes_reregister_in_network_ready, + task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 20, + FALSE, + (GAsyncReadyCallback)allowed_access_technology_update_ready, + task); + + g_free (command); +} + +static void +sxrat_set_current_modes (MMBroadbandModemCinterion *self, + MMModemMode allowed, + MMModemMode preferred, + GTask *task) +{ + gchar *command; + GError *error = NULL; + + g_assert (self->priv->any_allowed != MM_MODEM_MODE_NONE); + + /* Handle ANY */ + if (allowed == MM_MODEM_MODE_ANY) + allowed = self->priv->any_allowed; + + command = mm_cinterion_build_sxrat_set_command (allowed, preferred, &error); + + if (!command) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + mm_base_modem_at_command ( + MM_BASE_MODEM (self), + command, + 30, + FALSE, + (GAsyncReadyCallback)allowed_access_technology_update_ready, + task); + + g_free (command); +} + +static void +set_current_modes (MMIfaceModem *_self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + if (self->priv->sxrat_support == FEATURE_SUPPORTED) + sxrat_set_current_modes (self, allowed, preferred, task); + else if (self->priv->sxrat_support == FEATURE_NOT_SUPPORTED) + cops_set_current_modes (self, allowed, preferred, task); + else + g_assert_not_reached (); +} + +/*****************************************************************************/ +/* Supported bands (Modem interface) */ + +static GArray * +load_supported_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +scfg_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + GError *error = NULL; + GArray *bands; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response || + !mm_cinterion_parse_scfg_test (response, + self->priv->modem_family, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + &bands, + &self->priv->rb_format, + &error)) + g_task_return_error (task, error); + else { + if (!mm_cinterion_build_band (bands, + NULL, + FALSE, + self->priv->rb_format, + self->priv->modem_family, + self->priv->supported_bands, + &error)) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bands, (GDestroyNotify)g_array_unref); + } + g_object_unref (task); +} + +static void +load_supported_bands (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + MMPort *primary; + MMKernelDevice *port; + const gchar *family = NULL; + + /* Lookup for the tag specifying which modem family the current device belongs */ + primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self))); + port = mm_port_peek_kernel_device (primary); + family = mm_kernel_device_get_global_property (port, "ID_MM_CINTERION_MODEM_FAMILY"); + + /* if the property is not set, default family */ + self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_DEFAULT; + + /* set used family also in the string for mm_obj_dbg */ + if (!family) + family = "default"; + + if (g_ascii_strcasecmp (family, "imt") == 0) + self->priv->modem_family = MM_CINTERION_MODEM_FAMILY_IMT; + else if (g_ascii_strcasecmp (family, "default") != 0) { + mm_obj_dbg (self, "cinterion modem family '%s' unknown", family); + family = "default"; + } + + mm_obj_dbg (self, "Using cinterion %s modem family", family); + + task = g_task_new (_self, NULL, callback, user_data); + mm_base_modem_at_command (MM_BASE_MODEM (_self), + "AT^SCFG=?", + 3, + FALSE, + (GAsyncReadyCallback)scfg_test_ready, + task); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +get_band_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + const gchar *response; + GError *error = NULL; + GArray *bands = NULL; + + response = mm_base_modem_at_command_finish (_self, res, &error); + if (!response || + !mm_cinterion_parse_scfg_response (response, + self->priv->modem_family, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + &bands, + self->priv->rb_format, + &error)) + g_task_return_error (task, error); + else + g_task_return_pointer (task, bands, (GDestroyNotify) g_array_unref); + g_object_unref (task); +} + +static void +load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* The timeout in this command is extremely large, because there are some + * modules like the EGS5 that build the response based on the current network + * registration, and that implies the module needs to be registered. If for + * any reason there is no serving network where to register, the response + * comes after a very long time, up to 100s. */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SCFG?", + 120, + FALSE, + (GAsyncReadyCallback)get_band_ready, + task); +} + +/*****************************************************************************/ +/* Set current bands (Modem interface) */ + +typedef struct { + MMBaseModemAtCommandAlloc *cmds; +} SetCurrentBandsContext; + +static void +set_current_bands_context_free (SetCurrentBandsContext *ctx) +{ + if (ctx->cmds) { + guint i; + + for (i = 0; ctx->cmds[i].command; i++) + mm_base_modem_at_command_alloc_clear (&ctx->cmds[i]); + g_free (ctx->cmds); + } + g_slice_free (SetCurrentBandsContext, ctx); +} + +static gboolean +set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +scfg_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 +scfg_set_ready_sequence (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + mm_base_modem_at_sequence_finish (self, res, NULL, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +set_bands_3g (GTask *task, + GArray *bands_array) +{ + MMBroadbandModemCinterion *self; + GError *error = NULL; + guint band[MM_CINTERION_RB_BLOCK_N] = { 0 }; + + self = g_task_get_source_object (task); + + if (!mm_cinterion_build_band (bands_array, + self->priv->supported_bands, + FALSE, /* 2G and 3G */ + self->priv->rb_format, + self->priv->modem_family, + band, + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) { + g_autofree gchar *cmd = NULL; + + /* Following the setup: + * AT^SCFG="Radion/Band",<rba> + * We will set the preferred band equal to the allowed band, so that we force + * the modem to connect at that specific frequency only. Note that we will be + * passing a number here! + * + * The optional <rbe> field is set to 1, so that changes take effect + * immediately. + */ + cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",%u,1", band[MM_CINTERION_RB_BLOCK_LEGACY]); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 15, + FALSE, + (GAsyncReadyCallback)scfg_set_ready, + task); + return; + } + + if (self->priv->rb_format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) { + SetCurrentBandsContext *ctx; + + ctx = g_slice_new0 (SetCurrentBandsContext); + g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_bands_context_free); + + if (self->priv->modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + g_autofree gchar *bandstr2G = NULL; + g_autofree gchar *bandstr3G = NULL; + g_autofree gchar *bandstr4G = NULL; + g_autofree gchar *bandstr2G_enc = NULL; + g_autofree gchar *bandstr3G_enc = NULL; + g_autofree gchar *bandstr4G_enc = NULL; + + bandstr2G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_GSM]); + bandstr3G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_UMTS]); + bandstr4G = g_strdup_printf ("0x%08X", band[MM_CINTERION_RB_BLOCK_LTE_LOW]); + + bandstr2G_enc = mm_modem_charset_str_from_utf8 (bandstr2G, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr2G_enc) { + g_prefix_error (&error, "Couldn't convert 2G band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + bandstr3G_enc = mm_modem_charset_str_from_utf8 (bandstr3G, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr3G_enc) { + g_prefix_error (&error, "Couldn't convert 3G band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + bandstr4G_enc = mm_modem_charset_str_from_utf8 (bandstr4G, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr4G_enc) { + g_prefix_error (&error, "Couldn't convert 4G band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1); + ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%s\"", bandstr2G_enc); + ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%s\"", bandstr3G_enc); + ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%s\"", bandstr4G_enc); + ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 60; + } else { + ctx->cmds = g_new0 (MMBaseModemAtCommandAlloc, 3 + 1); + ctx->cmds[0].command = g_strdup_printf ("^SCFG=\"Radio/Band/2G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_GSM]); + ctx->cmds[1].command = g_strdup_printf ("^SCFG=\"Radio/Band/3G\",\"%08x\",,1", band[MM_CINTERION_RB_BLOCK_UMTS]); + ctx->cmds[2].command = g_strdup_printf ("^SCFG=\"Radio/Band/4G\",\"%08x\",\"%08x\",1", band[MM_CINTERION_RB_BLOCK_LTE_LOW], band[MM_CINTERION_RB_BLOCK_LTE_HIGH]); + ctx->cmds[0].timeout = ctx->cmds[1].timeout = ctx->cmds[2].timeout = 15; + } + + mm_base_modem_at_sequence (MM_BASE_MODEM (self), + (const MMBaseModemAtCommand *)ctx->cmds, + NULL, + NULL, + (GAsyncReadyCallback)scfg_set_ready_sequence, + task); + return; + } + + g_assert_not_reached (); +} + +static void +set_bands_2g (GTask *task, + GArray *bands_array) +{ + MMBroadbandModemCinterion *self; + GError *error = NULL; + guint band[MM_CINTERION_RB_BLOCK_N] = { 0 }; + g_autofree gchar *cmd = NULL; + g_autofree gchar *bandstr = NULL; + g_autofree gchar *bandstr_enc = NULL; + + self = g_task_get_source_object (task); + + if (!mm_cinterion_build_band (bands_array, + self->priv->supported_bands, + TRUE, /* 2G only */ + MM_CINTERION_RADIO_BAND_FORMAT_SINGLE, + 0, + band, + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Build string with the value, in the proper charset */ + bandstr = g_strdup_printf ("%u", band[MM_CINTERION_RB_BLOCK_LEGACY]); + bandstr_enc = mm_modem_charset_str_from_utf8 (bandstr, + mm_broadband_modem_get_current_charset (MM_BROADBAND_MODEM (self)), + FALSE, + &error); + if (!bandstr_enc) { + g_prefix_error (&error, "Couldn't convert band string to current charset: "); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Following the setup: + * AT^SCFG="Radion/Band",<rbp>,<rba> + * We will set the preferred band equal to the allowed band, so that we force + * the modem to connect at that specific frequency only. Note that we will be + * passing double-quote enclosed strings here! + */ + cmd = g_strdup_printf ("^SCFG=\"Radio/Band\",\"%s\",\"%s\"", bandstr_enc, bandstr_enc); + mm_base_modem_at_command (MM_BASE_MODEM (self), + cmd, + 15, + FALSE, + (GAsyncReadyCallback)scfg_set_ready, + task); +} + +static void +set_current_bands (MMIfaceModem *self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + /* The bands that we get here are previously validated by the interface, and + * that means that ALL the bands given here were also given in the list of + * supported bands. BUT BUT, that doesn't mean that the exact list of bands + * will end up being valid, as not all combinations are possible. E.g, + * Cinterion modems supporting only 2G have specific combinations allowed. + */ + task = g_task_new (self, NULL, callback, user_data); + if (mm_iface_modem_is_3g (self)) + set_bands_3g (task, bands_array); + else + set_bands_2g (task, bands_array); +} + +/*****************************************************************************/ +/* Flow control */ + +static gboolean +setup_flow_control_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +setup_flow_control_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + GError *error = NULL; + + if (!mm_base_modem_at_command_finish (self, res, &error)) + /* Let the error be critical. We DO need RTS/CTS in order to have + * proper modem disabling. */ + g_task_return_error (task, error); + else + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +setup_flow_control (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + + /* We need to enable RTS/CTS so that CYCLIC SLEEP mode works */ + g_object_set (self, MM_BROADBAND_MODEM_FLOW_CONTROL, MM_FLOW_CONTROL_RTS_CTS, NULL); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "\\Q3", + 3, + FALSE, + (GAsyncReadyCallback)setup_flow_control_ready, + task); +} + +/*****************************************************************************/ +/* Load unlock retries (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, "^SPIC=\"SC\"" }, + { MM_MODEM_LOCK_SIM_PUK, "^SPIC=\"SC\",1" }, + { MM_MODEM_LOCK_SIM_PIN2, "^SPIC=\"P2\"" }, + { MM_MODEM_LOCK_SIM_PUK2, "^SPIC=\"P2\",1" }, + { MM_MODEM_LOCK_PH_FSIM_PIN, "^SPIC=\"PS\"" }, + { MM_MODEM_LOCK_PH_FSIM_PUK, "^SPIC=\"PS\",1" }, + { MM_MODEM_LOCK_PH_NET_PIN, "^SPIC=\"PN\"" }, + { MM_MODEM_LOCK_PH_NET_PUK, "^SPIC=\"PN\",1" }, +}; + +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 +spic_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + LoadUnlockRetriesContext *ctx; + const gchar *response; + g_autoptr(GError) error = NULL; + + 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); + } else { + guint val; + + response = mm_strip_tag (response, "^SPIC:"); + if (!mm_get_uint_from_str (response, &val)) + mm_obj_dbg (self, "couldn't parse retry count value for lock '%s'", + mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock)); + else + mm_unlock_retries_set (ctx->retries, unlock_retries_map[ctx->i].lock, val); + } + + /* Go to next lock value */ + ctx->i++; + load_unlock_retries_context_step (task); +} + +static void +load_unlock_retries_context_step (GTask *task) +{ + MMBroadbandModemCinterion *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)spic_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); +} + +/*****************************************************************************/ +/* After SIM unlock (Modem interface) */ + +#define MAX_AFTER_SIM_UNLOCK_RETRIES 15 + +typedef enum { + CINTERION_SIM_STATUS_REMOVED = 0, + CINTERION_SIM_STATUS_INSERTED = 1, + CINTERION_SIM_STATUS_INIT_COMPLETED = 5, +} CinterionSimStatus; + +typedef struct { + guint retries; + guint timeout_id; +} AfterSimUnlockContext; + +static gboolean +after_sim_unlock_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void after_sim_unlock_context_step (GTask *task); + +static gboolean +simstatus_timeout_cb (GTask *task) +{ + AfterSimUnlockContext *ctx; + + ctx = g_task_get_task_data (task); + ctx->timeout_id = 0; + after_sim_unlock_context_step (task); + return G_SOURCE_REMOVE; +} + +static void +simstatus_check_ready (MMBaseModem *self, + GAsyncResult *res, + GTask *task) +{ + AfterSimUnlockContext *ctx; + const gchar *response; + + response = mm_base_modem_at_command_finish (self, res, NULL); + if (response) { + gchar *descr = NULL; + guint val = 0; + + if (mm_cinterion_parse_sind_response (response, &descr, NULL, &val, NULL) && + g_str_equal (descr, "simstatus") && + val == CINTERION_SIM_STATUS_INIT_COMPLETED) { + /* SIM ready! */ + g_free (descr); + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + g_free (descr); + } + + /* Need to retry after 1 sec */ + ctx = g_task_get_task_data (task); + g_assert (ctx->timeout_id == 0); + ctx->timeout_id = g_timeout_add_seconds (1, (GSourceFunc)simstatus_timeout_cb, task); +} + +static void +after_sim_unlock_context_step (GTask *task) +{ + MMBroadbandModemCinterion *self; + AfterSimUnlockContext *ctx; + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + /* if not supported or too much wait, skip */ + if (self->priv->sind_simstatus_support != FEATURE_SUPPORTED || ctx->retries == 0) { + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + } + + /* Recheck */ + ctx->retries--; + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SIND=\"simstatus\",2", + 3, + FALSE, + (GAsyncReadyCallback)simstatus_check_ready, + task); +} + +static void +sind_indicators_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self; + g_autoptr(GError) error = NULL; + const gchar *response; + + self = MM_BROADBAND_MODEM_CINTERION (_self); + if (!(response = mm_base_modem_at_command_finish (_self, res, &error))) { + self->priv->sind_psinfo_support = FEATURE_NOT_SUPPORTED; + mm_obj_dbg (self, "psinfo support? no"); + + self->priv->sind_simstatus_support = FEATURE_NOT_SUPPORTED; + mm_obj_dbg (self, "simstatus support? no"); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + + return; + } + + if (g_regex_match_simple ("\\(\\s*psinfo\\s*,", response, 0, 0)) + self->priv->sind_psinfo_support = FEATURE_SUPPORTED; + mm_obj_dbg (self, "psinfo support? %s", self->priv->sind_psinfo_support == FEATURE_SUPPORTED ? "yes":"no"); + + if (g_regex_match_simple ("\\(\\s*simstatus\\s*,", response, 0, 0)) + self->priv->sind_simstatus_support = FEATURE_SUPPORTED; + mm_obj_dbg (self, "simstatus support? %s", self->priv->sind_simstatus_support == FEATURE_SUPPORTED ? "yes":"no"); + + after_sim_unlock_context_step (task); +} + +static void +after_sim_unlock (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + AfterSimUnlockContext *ctx; + + task = g_task_new (self, NULL, callback, user_data); + ctx = g_new0 (AfterSimUnlockContext, 1); + ctx->retries = MAX_AFTER_SIM_UNLOCK_RETRIES; + g_task_set_task_data (task, ctx, g_free); + + /* check which indicators are available */ + mm_base_modem_at_command (MM_BASE_MODEM (self), + "AT^SIND=?", + 3, + FALSE, + (GAsyncReadyCallback)sind_indicators_ready, + task); +} + +/*****************************************************************************/ +/* Setup SIM hot swap (Modem interface) */ + +static void +cinterion_scks_unsolicited_handler (MMPortSerialAt *port, + GMatchInfo *match_info, + MMBroadbandModemCinterion *self) +{ + guint scks; + + if (!mm_get_uint_from_match_info (match_info, 1, &scks)) + return; + + switch (scks) { + case 0: + mm_obj_msg (self, "SIM removal detected"); + break; + case 1: + mm_obj_msg (self, "SIM insertion detected"); + break; + case 2: + mm_obj_msg (self, "SIM interface hardware deactivated (potentially non-electrically compatible SIM inserted)"); + break; + case 3: + mm_obj_msg (self, "SIM interface hardware deactivated (technical problem, no precise diagnosis)"); + break; + default: + g_assert_not_reached (); + break; + } + + mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self)); +} + +static gboolean +modem_setup_sim_hot_swap_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +cinterion_hot_swap_init_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + g_autoptr(GError) error = NULL; + MMPortSerialAt *primary; + MMPortSerialAt *secondary; + + if (!mm_base_modem_at_command_finish (_self, res, &error)) { + g_prefix_error (&error, "Could not enable SCKS: "); + g_task_return_error (task, g_steal_pointer (&error)); + g_object_unref (task); + return; + } + + mm_obj_dbg (self, "SIM hot swap detect successfully enabled"); + + primary = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + mm_port_serial_at_add_unsolicited_msg_handler ( + primary, + self->priv->scks_regex, + (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler, + self, + NULL); + + secondary = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + if (secondary) + mm_port_serial_at_add_unsolicited_msg_handler ( + secondary, + self->priv->scks_regex, + (MMPortSerialAtUnsolicitedMsgFn) cinterion_scks_unsolicited_handler, + self, + NULL); + + if (!mm_broadband_modem_sim_hot_swap_ports_context_init (MM_BROADBAND_MODEM (self), &error)) + mm_obj_warn (self, "failed to initialize SIM hot swap ports context: %s", error->message); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +static void +modem_setup_sim_hot_swap (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task; + + mm_obj_dbg (self, "Enabling SCKS URCs for SIM hot swap detection"); + + task = g_task_new (self, NULL, callback, user_data); + + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SCKS=1", + 3, + FALSE, + (GAsyncReadyCallback) cinterion_hot_swap_init_ready, + task); +} + +/*****************************************************************************/ +/* SIM hot swap cleanup (Modem interface) */ + +static void +modem_cleanup_sim_hot_swap (MMIfaceModem *self) +{ + mm_broadband_modem_sim_hot_swap_ports_context_reset (MM_BROADBAND_MODEM (self)); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBaseBearer * +cinterion_modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void +broadband_bearer_cinterion_new_ready (GObject *unused, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer; + GError *error = NULL; + + bearer = mm_broadband_bearer_cinterion_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 +broadband_bearer_new_ready (GObject *unused, + GAsyncResult *res, + GTask *task) +{ + MMBaseBearer *bearer; + 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 +common_create_bearer (GTask *task) +{ + MMBroadbandModemCinterion *self; + + self = g_task_get_source_object (task); + + switch (self->priv->swwan_support) { + case FEATURE_NOT_SUPPORTED: + mm_obj_dbg (self, "^SWWAN not supported, creating default bearer..."); + mm_broadband_bearer_new (MM_BROADBAND_MODEM (self), + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_new_ready, + task); + return; + case FEATURE_SUPPORTED: + mm_obj_dbg (self, "^SWWAN supported, creating cinterion bearer..."); + mm_broadband_bearer_cinterion_new (MM_BROADBAND_MODEM_CINTERION (self), + g_task_get_task_data (task), + NULL, /* cancellable */ + (GAsyncReadyCallback)broadband_bearer_cinterion_new_ready, + task); + return; + case FEATURE_SUPPORT_UNKNOWN: + default: + g_assert_not_reached (); + } +} + +static void +swwan_test_ready (MMBaseModem *_self, + GAsyncResult *res, + GTask *task) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + + /* Fetch the result to the SWWAN test. If no response given (error triggered), + * assume unsupported */ + if (!mm_base_modem_at_command_finish (_self, res, NULL)) { + mm_obj_dbg (self, "SWWAN unsupported"); + self->priv->swwan_support = FEATURE_NOT_SUPPORTED; + } else { + mm_obj_dbg (self, "SWWAN supported"); + self->priv->swwan_support = FEATURE_SUPPORTED; + } + + /* Go on and create the bearer */ + common_create_bearer (task); +} + +static void +cinterion_modem_create_bearer (MMIfaceModem *_self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (_self); + GTask *task; + + task = g_task_new (self, NULL, callback, user_data); + g_task_set_task_data (task, g_object_ref (properties), g_object_unref); + + /* Newer Cinterion modems may support SWWAN, which is the same as WWAN. + * Check to see if current modem supports it.*/ + if (self->priv->swwan_support != FEATURE_SUPPORT_UNKNOWN) { + common_create_bearer (task); + return; + } + + /* If we don't have a data port, don't even bother checking for ^SWWAN + * support. */ + if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) { + mm_obj_dbg (self, "skipping ^SWWAN check as no data port is available"); + self->priv->swwan_support = FEATURE_NOT_SUPPORTED; + common_create_bearer (task); + return; + } + + mm_obj_dbg (self, "checking ^SWWAN support..."); + mm_base_modem_at_command (MM_BASE_MODEM (self), + "^SWWAN=?", + 6, + TRUE, /* may be cached */ + (GAsyncReadyCallback) swwan_test_ready, + task); +} + +/*****************************************************************************/ + +static void +setup_ports (MMBroadbandModem *_self) +{ + MMBroadbandModemCinterion *self = (MM_BROADBAND_MODEM_CINTERION (_self)); + MMPortSerialAt *ports[2]; + guint i; + + /* Call parent's setup ports first always */ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_cinterion_parent_class)->setup_ports (_self); + + ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)); + ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)); + + for (i = 0; i < G_N_ELEMENTS (ports); i++) { + if (!ports[i]) + continue; + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->sysstart_regex, + NULL, NULL, NULL); + mm_port_serial_at_add_unsolicited_msg_handler ( + ports[i], + self->priv->scks_regex, + NULL, NULL, NULL); + } +} + +/*****************************************************************************/ + +MMBroadbandModemCinterion * +mm_broadband_modem_cinterion_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_CINTERION, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + /* Generic bearer (TTY) or Cinterion bearer (NET) supported */ + MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE, + MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE, + MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE, + NULL); +} + +static void +mm_broadband_modem_cinterion_init (MMBroadbandModemCinterion *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_CINTERION, + MMBroadbandModemCinterionPrivate); + + /* Initialize private variables */ + self->priv->initial_eps_bearer_cid = -1; + self->priv->sind_psinfo_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->swwan_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->smoni_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->sind_simstatus_support = FEATURE_SUPPORT_UNKNOWN; + self->priv->sxrat_support = FEATURE_SUPPORT_UNKNOWN; + + self->priv->ciev_regex = g_regex_new ("\\r\\n\\+CIEV:\\s*([a-z]+),(\\d+)\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->sysstart_regex = g_regex_new ("\\r\\n\\^SYSSTART.*\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + self->priv->scks_regex = g_regex_new ("\\^SCKS:\\s*([0-3])\\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + + self->priv->any_allowed = MM_MODEM_MODE_NONE; +} + +static void +finalize (GObject *object) +{ + MMBroadbandModemCinterion *self = MM_BROADBAND_MODEM_CINTERION (object); + + g_free (self->priv->sleep_mode_cmd); + + if (self->priv->cnmi_supported_mode) + g_array_unref (self->priv->cnmi_supported_mode); + if (self->priv->cnmi_supported_mt) + g_array_unref (self->priv->cnmi_supported_mt); + if (self->priv->cnmi_supported_bm) + g_array_unref (self->priv->cnmi_supported_bm); + if (self->priv->cnmi_supported_ds) + g_array_unref (self->priv->cnmi_supported_ds); + if (self->priv->cnmi_supported_bfr) + g_array_unref (self->priv->cnmi_supported_bfr); + if (self->priv->sxrat_supported_rat) + g_array_unref (self->priv->sxrat_supported_rat); + if (self->priv->sxrat_supported_pref1) + g_array_unref (self->priv->sxrat_supported_pref1); + + g_regex_unref (self->priv->ciev_regex); + g_regex_unref (self->priv->sysstart_regex); + g_regex_unref (self->priv->scks_regex); + + G_OBJECT_CLASS (mm_broadband_modem_cinterion_parent_class)->finalize (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + iface_modem_parent = g_type_interface_peek_parent (iface); + + iface->create_bearer = cinterion_modem_create_bearer; + iface->create_bearer_finish = cinterion_modem_create_bearer_finish; + iface->load_supported_modes = load_supported_modes; + iface->load_supported_modes_finish = load_supported_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_supported_bands = load_supported_bands; + iface->load_supported_bands_finish = load_supported_bands_finish; + iface->load_current_bands = load_current_bands; + iface->load_current_bands_finish = load_current_bands_finish; + iface->set_current_bands = set_current_bands; + iface->set_current_bands_finish = set_current_bands_finish; + iface->load_access_technologies = load_access_technologies; + iface->load_access_technologies_finish = load_access_technologies_finish; + iface->setup_flow_control = setup_flow_control; + iface->setup_flow_control_finish = setup_flow_control_finish; + iface->modem_after_sim_unlock = after_sim_unlock; + iface->modem_after_sim_unlock_finish = after_sim_unlock_finish; + iface->load_unlock_retries = load_unlock_retries; + iface->load_unlock_retries_finish = load_unlock_retries_finish; + iface->reset = mm_shared_cinterion_modem_reset; + iface->reset_finish = mm_shared_cinterion_modem_reset_finish; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_down_finish; + iface->modem_power_off = modem_power_off; + iface->modem_power_off_finish = modem_power_off_finish; + iface->setup_sim_hot_swap = modem_setup_sim_hot_swap; + iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish; + iface->cleanup_sim_hot_swap = modem_cleanup_sim_hot_swap; +} + +static MMIfaceModem * +peek_parent_interface (MMSharedCinterion *self) +{ + return iface_modem_parent; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + iface_modem_3gpp_parent = g_type_interface_peek_parent (iface); + + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_disable_unsolicited_events_finish; + + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_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->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer; + iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_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; + 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_messaging_init (MMIfaceModemMessaging *iface) +{ + iface->check_support = messaging_check_support; + iface->check_support_finish = messaging_check_support_finish; + iface->enable_unsolicited_events = messaging_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = messaging_enable_unsolicited_events_finish; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = mm_shared_cinterion_location_load_capabilities; + iface->load_capabilities_finish = mm_shared_cinterion_location_load_capabilities_finish; + iface->enable_location_gathering = mm_shared_cinterion_enable_location_gathering; + iface->enable_location_gathering_finish = mm_shared_cinterion_enable_location_gathering_finish; + iface->disable_location_gathering = mm_shared_cinterion_disable_location_gathering; + iface->disable_location_gathering_finish = mm_shared_cinterion_disable_location_gathering_finish; +} + +static MMIfaceModemLocation * +peek_parent_location_interface (MMSharedCinterion *self) +{ + return iface_modem_location_parent; +} + +static void +iface_modem_voice_init (MMIfaceModemVoice *iface) +{ + iface_modem_voice_parent = g_type_interface_peek_parent (iface); + + iface->create_call = mm_shared_cinterion_create_call; + + iface->check_support = mm_shared_cinterion_voice_check_support; + iface->check_support_finish = mm_shared_cinterion_voice_check_support_finish; + iface->enable_unsolicited_events = mm_shared_cinterion_voice_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = mm_shared_cinterion_voice_enable_unsolicited_events_finish; + iface->disable_unsolicited_events = mm_shared_cinterion_voice_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = mm_shared_cinterion_voice_disable_unsolicited_events_finish; + iface->setup_unsolicited_events = mm_shared_cinterion_voice_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_voice_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_voice_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_voice_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemVoice * +peek_parent_voice_interface (MMSharedCinterion *self) +{ + return iface_modem_voice_parent; +} + +static void +iface_modem_time_init (MMIfaceModemTime *iface) +{ + iface_modem_time_parent = g_type_interface_peek_parent (iface); + + iface->setup_unsolicited_events = mm_shared_cinterion_time_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = mm_shared_cinterion_time_setup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = mm_shared_cinterion_time_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = mm_shared_cinterion_time_cleanup_unsolicited_events_finish; +} + +static MMIfaceModemTime * +peek_parent_time_interface (MMSharedCinterion *self) +{ + return iface_modem_time_parent; +} + +static void +shared_cinterion_init (MMSharedCinterion *iface) +{ + iface->peek_parent_interface = peek_parent_interface; + iface->peek_parent_location_interface = peek_parent_location_interface; + iface->peek_parent_voice_interface = peek_parent_voice_interface; + iface->peek_parent_time_interface = peek_parent_time_interface; +} + +static void +iface_modem_signal_init (MMIfaceModemSignal *iface) +{ + iface_modem_signal_parent = g_type_interface_peek_parent (iface); + + iface->check_support = signal_check_support; + iface->check_support_finish = signal_check_support_finish; + iface->load_values = signal_load_values; + iface->load_values_finish = signal_load_values_finish; +} + +static void +mm_broadband_modem_cinterion_class_init (MMBroadbandModemCinterionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemCinterionPrivate)); + + /* Virtual methods */ + object_class->finalize = finalize; + broadband_modem_class->setup_ports = setup_ports; +} |