blob: 0a51b4b534757abe2fe2af19ab80142300c8bb74 [file] [log] [blame]
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2012 Red Hat, Inc.
* Copyright (C) 2011 - 2012 Google, Inc.
* Copyright (C) 2015 - Marco Bascetta <marco.bascetta@sadel.it>
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-base-modem-at.h"
#include "mm-broadband-modem.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-3gpp-ussd.h"
#include "mm-iface-modem-cdma.h"
#include "mm-iface-modem-simple.h"
#include "mm-iface-modem-location.h"
#include "mm-iface-modem-messaging.h"
#include "mm-iface-modem-voice.h"
#include "mm-iface-modem-time.h"
#include "mm-iface-modem-firmware.h"
#include "mm-iface-modem-signal.h"
#include "mm-iface-modem-oma.h"
#include "mm-broadband-bearer.h"
#include "mm-bearer-list.h"
#include "mm-sms-list.h"
#include "mm-sms-part-3gpp.h"
#include "mm-call-list.h"
#include "mm-base-sim.h"
#include "mm-log.h"
#include "mm-modem-helpers.h"
#include "mm-error-helpers.h"
#include "mm-port-serial-qcdm.h"
#include "libqcdm/src/errors.h"
#include "libqcdm/src/commands.h"
#include "libqcdm/src/logs.h"
#include "libqcdm/src/log-items.h"
#include "mm-helper-enums-types.h"
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface);
static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
static void iface_modem_simple_init (MMIfaceModemSimple *iface);
static void iface_modem_location_init (MMIfaceModemLocation *iface);
static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
static void iface_modem_voice_init (MMIfaceModemVoice *iface);
static void iface_modem_time_init (MMIfaceModemTime *iface);
static void iface_modem_signal_init (MMIfaceModemSignal *iface);
static void iface_modem_oma_init (MMIfaceModemOma *iface);
static void iface_modem_firmware_init (MMIfaceModemFirmware *iface);
G_DEFINE_TYPE_EXTENDED (MMBroadbandModem, mm_broadband_modem, MM_TYPE_BASE_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIMPLE, iface_modem_simple_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_OMA, iface_modem_oma_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init))
enum {
PROP_0,
PROP_MODEM_DBUS_SKELETON,
PROP_MODEM_3GPP_DBUS_SKELETON,
PROP_MODEM_3GPP_USSD_DBUS_SKELETON,
PROP_MODEM_CDMA_DBUS_SKELETON,
PROP_MODEM_SIMPLE_DBUS_SKELETON,
PROP_MODEM_LOCATION_DBUS_SKELETON,
PROP_MODEM_MESSAGING_DBUS_SKELETON,
PROP_MODEM_VOICE_DBUS_SKELETON,
PROP_MODEM_TIME_DBUS_SKELETON,
PROP_MODEM_SIGNAL_DBUS_SKELETON,
PROP_MODEM_OMA_DBUS_SKELETON,
PROP_MODEM_FIRMWARE_DBUS_SKELETON,
PROP_MODEM_SIM,
PROP_MODEM_BEARER_LIST,
PROP_MODEM_STATE,
PROP_MODEM_3GPP_REGISTRATION_STATE,
PROP_MODEM_3GPP_CS_NETWORK_SUPPORTED,
PROP_MODEM_3GPP_PS_NETWORK_SUPPORTED,
PROP_MODEM_3GPP_EPS_NETWORK_SUPPORTED,
PROP_MODEM_3GPP_IGNORED_FACILITY_LOCKS,
PROP_MODEM_3GPP_INITIAL_EPS_BEARER,
PROP_MODEM_CDMA_CDMA1X_REGISTRATION_STATE,
PROP_MODEM_CDMA_EVDO_REGISTRATION_STATE,
PROP_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED,
PROP_MODEM_CDMA_EVDO_NETWORK_SUPPORTED,
PROP_MODEM_MESSAGING_SMS_LIST,
PROP_MODEM_MESSAGING_SMS_PDU_MODE,
PROP_MODEM_MESSAGING_SMS_DEFAULT_STORAGE,
PROP_MODEM_LOCATION_ALLOW_GPS_UNMANAGED_ALWAYS,
PROP_MODEM_VOICE_CALL_LIST,
PROP_MODEM_SIMPLE_STATUS,
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
PROP_MODEM_SIM_HOT_SWAP_CONFIGURED,
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
PROP_MODEM_CARRIER_CONFIG_MAPPING,
PROP_FLOW_CONTROL,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
/* When CIND is supported, invalid indicators are marked with this value */
#define CIND_INDICATOR_INVALID 255
#define CIND_INDICATOR_IS_VALID(u) (u != CIND_INDICATOR_INVALID)
typedef struct _PortsContext PortsContext;
struct _MMBroadbandModemPrivate {
/* Broadband modem specific implementation */
PortsContext *enabled_ports_ctx;
PortsContext *sim_hot_swap_ports_ctx;
gboolean modem_init_run;
gboolean sim_hot_swap_supported;
gboolean sim_hot_swap_configured;
gboolean periodic_signal_check_disabled;
/*<--- Modem interface --->*/
/* Properties */
GObject *modem_dbus_skeleton;
MMBaseSim *modem_sim;
MMBearerList *modem_bearer_list;
MMModemState modem_state;
gchar *carrier_config_mapping;
/* Implementation helpers */
MMModemCharset modem_current_charset;
gboolean modem_cind_support_checked;
gboolean modem_cind_supported;
guint modem_cind_indicator_signal_quality;
guint modem_cind_min_signal_quality;
guint modem_cind_max_signal_quality;
guint modem_cind_indicator_roaming;
guint modem_cind_indicator_service;
MM3gppCmerMode modem_cmer_enable_mode;
MM3gppCmerMode modem_cmer_disable_mode;
MM3gppCmerInd modem_cmer_ind;
gboolean modem_cgerep_support_checked;
gboolean modem_cgerep_supported;
MMFlowControl flow_control;
/*<--- Modem 3GPP interface --->*/
/* Properties */
GObject *modem_3gpp_dbus_skeleton;
MMModem3gppRegistrationState modem_3gpp_registration_state;
gboolean modem_3gpp_cs_network_supported;
gboolean modem_3gpp_ps_network_supported;
gboolean modem_3gpp_eps_network_supported;
/* Implementation helpers */
GPtrArray *modem_3gpp_registration_regex;
MMModem3gppFacility modem_3gpp_ignored_facility_locks;
MMBaseBearer *modem_3gpp_initial_eps_bearer;
/*<--- Modem 3GPP USSD interface --->*/
/* Properties */
GObject *modem_3gpp_ussd_dbus_skeleton;
/* Implementation helpers */
gboolean use_unencoded_ussd;
GTask *pending_ussd_action;
/*<--- Modem CDMA interface --->*/
/* Properties */
GObject *modem_cdma_dbus_skeleton;
MMModemCdmaRegistrationState modem_cdma_cdma1x_registration_state;
MMModemCdmaRegistrationState modem_cdma_evdo_registration_state;
gboolean modem_cdma_cdma1x_network_supported;
gboolean modem_cdma_evdo_network_supported;
GCancellable *modem_cdma_pending_registration_cancellable;
/* Implementation helpers */
gboolean checked_sprint_support;
gboolean has_spservice;
gboolean has_speri;
gint evdo_pilot_rssi;
/*<--- Modem Simple interface --->*/
/* Properties */
GObject *modem_simple_dbus_skeleton;
MMSimpleStatus *modem_simple_status;
/*<--- Modem Location interface --->*/
/* Properties */
GObject *modem_location_dbus_skeleton;
gboolean modem_location_allow_gps_unmanaged_always;
/*<--- Modem Messaging interface --->*/
/* Properties */
GObject *modem_messaging_dbus_skeleton;
MMSmsList *modem_messaging_sms_list;
gboolean modem_messaging_sms_pdu_mode;
MMSmsStorage modem_messaging_sms_default_storage;
/* Implementation helpers */
gboolean sms_supported_modes_checked;
gboolean mem1_storage_locked;
MMSmsStorage current_sms_mem1_storage;
gboolean mem2_storage_locked;
MMSmsStorage current_sms_mem2_storage;
/*<--- Modem Voice interface --->*/
/* Properties */
GObject *modem_voice_dbus_skeleton;
MMCallList *modem_voice_call_list;
/*<--- Modem Time interface --->*/
/* Properties */
GObject *modem_time_dbus_skeleton;
/*<--- Modem Signal interface --->*/
/* Properties */
GObject *modem_signal_dbus_skeleton;
/*<--- Modem OMA interface --->*/
/* Properties */
GObject *modem_oma_dbus_skeleton;
/*<--- Modem Firmware interface --->*/
/* Properties */
GObject *modem_firmware_dbus_skeleton;
};
/*****************************************************************************/
static gboolean
response_processor_string_ignore_at_errors (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
if (error) {
/* Ignore AT errors (ie, ERROR or CMx ERROR) */
if (error->domain != MM_MOBILE_EQUIPMENT_ERROR || last_command)
*result_error = g_error_copy (error);
return FALSE;
}
*result = g_variant_new_string (response);
return TRUE;
}
/*****************************************************************************/
/* Create Bearer (Modem interface) */
static MMBaseBearer *
modem_create_bearer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
broadband_bearer_new_ready (GObject *source,
GAsyncResult *res,
GTask *task)
{
MMBaseBearer *bearer = NULL;
GError *error = NULL;
bearer = mm_broadband_bearer_new_finish (res, &error);
if (!bearer)
g_task_return_error (task, error);
else
g_task_return_pointer (task, bearer, g_object_unref);
g_object_unref (task);
}
static void
modem_create_bearer (MMIfaceModem *self,
MMBearerProperties *properties,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* We just create a MMBroadbandBearer */
mm_dbg ("Creating Broadband bearer in broadband modem");
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
properties,
NULL, /* cancellable */
(GAsyncReadyCallback)broadband_bearer_new_ready,
task);
}
/*****************************************************************************/
/* Create SIM (Modem interface) */
static MMBaseSim *
modem_create_sim_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return mm_base_sim_new_finish (res, error);
}
static void
modem_create_sim (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
/* New generic SIM */
mm_base_sim_new (MM_BASE_MODEM (self),
NULL, /* cancellable */
callback,
user_data);
}
/*****************************************************************************/
/* Capabilities loading (Modem interface) */
typedef struct {
MMModemCapability caps;
MMPortSerialQcdm *qcdm_port;
} LoadCapabilitiesContext;
static void
load_capabilities_context_free (LoadCapabilitiesContext *ctx)
{
if (ctx->qcdm_port) {
mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm_port));
g_object_unref (ctx->qcdm_port);
}
g_slice_free (LoadCapabilitiesContext, ctx);
}
static MMModemCapability
modem_load_current_capabilities_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_MODEM_CAPABILITY_NONE;
}
return (MMModemCapability)value;
}
static void
current_capabilities_ws46_test_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
LoadCapabilitiesContext *ctx;
const gchar *response;
GArray *modes;
guint i;
ctx = g_task_get_task_data (task);
/* Completely ignore errors in AT+WS46=? */
response = mm_base_modem_at_command_finish (self, res, NULL);
if (!response)
goto out;
modes = mm_3gpp_parse_ws46_test_response (response, NULL);
if (!modes)
goto out;
/* Add LTE caps if any of the reported modes supports 4G */
for (i = 0; i < modes->len; i++) {
if (g_array_index (modes, MMModemMode, i) & MM_MODEM_MODE_4G) {
ctx->caps |= MM_MODEM_CAPABILITY_LTE;
break;
}
}
/* The +CGSM capability is saying that the modem is a 3GPP modem, but that
* doesn't necessarily mean it's a GSM/UMTS modem, it could be a LTE-only
* device. We did add the GSM_UMTS capability when +CGSM was found, so now
* we'll check if the device only reports 4G-only mode, and remove the
* capability if so.
*
* Note that we don't change the default +CGSM -> GSM/UMTS logic, we just
* fix it up.
*/
if ((modes->len == 1) && (g_array_index (modes, MMModemMode, 0) == MM_MODEM_MODE_4G)) {
g_assert (ctx->caps & MM_MODEM_CAPABILITY_LTE);
ctx->caps &= ~MM_MODEM_CAPABILITY_GSM_UMTS;
}
g_array_unref (modes);
out:
g_task_return_int (task, ctx->caps);
g_object_unref (task);
}
typedef struct {
gchar *name;
MMModemCapability bits;
} ModemCaps;
static const ModemCaps modem_caps[] = {
{ "+CGSM", MM_MODEM_CAPABILITY_GSM_UMTS },
{ "+CLTE2", MM_MODEM_CAPABILITY_LTE }, /* Novatel */
{ "+CLTE1", MM_MODEM_CAPABILITY_LTE }, /* Novatel */
{ "+CLTE", MM_MODEM_CAPABILITY_LTE },
{ "+CIS707-A", MM_MODEM_CAPABILITY_CDMA_EVDO },
{ "+CIS707A", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Cmotech */
{ "+CIS707", MM_MODEM_CAPABILITY_CDMA_EVDO },
{ "CIS707", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Qualcomm Gobi */
{ "+CIS707P", MM_MODEM_CAPABILITY_CDMA_EVDO },
{ "CIS-856", MM_MODEM_CAPABILITY_CDMA_EVDO },
{ "+IS-856", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Cmotech */
{ "CIS-856-A", MM_MODEM_CAPABILITY_CDMA_EVDO },
{ "CIS-856A", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Kyocera KPC680 */
{ "+WIRIDIUM", MM_MODEM_CAPABILITY_IRIDIUM }, /* Iridium satellite modems */
{ "CDMA 1x", MM_MODEM_CAPABILITY_CDMA_EVDO }, /* Huawei Data07, ATI reply */
/* TODO: FCLASS, MS, ES, DS? */
{ NULL }
};
static gboolean
parse_caps_gcap (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **variant,
GError **result_error)
{
const ModemCaps *cap = modem_caps;
guint32 ret = 0;
if (!response)
return FALSE;
/* Some modems (Huawei E160g) won't respond to +GCAP with no SIM, but
* will respond to ATI. Ignore the error and continue.
*/
if (strstr (response, "+CME ERROR:"))
return FALSE;
while (cap->name) {
if (strstr (response, cap->name))
ret |= cap->bits;
cap++;
}
/* No result built? */
if (ret == 0)
return FALSE;
*variant = g_variant_new_uint32 (ret);
return TRUE;
}
static gboolean
parse_caps_cpin (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
if (!response) {
if (error &&
(g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) ||
g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE) ||
g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_BUSY) ||
g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG))) {
/* At least, it's a GSM modem */
*result = g_variant_new_uint32 (MM_MODEM_CAPABILITY_GSM_UMTS);
return TRUE;
}
return FALSE;
}
if (strcasestr (response, "SIM PIN") ||
strcasestr (response, "SIM PUK") ||
strcasestr (response, "PH-SIM PIN") ||
strcasestr (response, "PH-FSIM PIN") ||
strcasestr (response, "PH-FSIM PUK") ||
strcasestr (response, "SIM PIN2") ||
strcasestr (response, "SIM PUK2") ||
strcasestr (response, "PH-NET PIN") ||
strcasestr (response, "PH-NET PUK") ||
strcasestr (response, "PH-NETSUB PIN") ||
strcasestr (response, "PH-NETSUB PUK") ||
strcasestr (response, "PH-SP PIN") ||
strcasestr (response, "PH-SP PUK") ||
strcasestr (response, "PH-CORP PIN") ||
strcasestr (response, "PH-CORP PUK") ||
strcasestr (response, "READY")) {
/* At least, it's a GSM modem */
*result = g_variant_new_uint32 (MM_MODEM_CAPABILITY_GSM_UMTS);
return TRUE;
}
return FALSE;
}
static gboolean
parse_caps_cgmm (MMBaseModem *self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
if (!response)
return FALSE;
/* This check detects some really old Motorola GPRS dongles and phones */
if (strstr (response, "GSM900") ||
strstr (response, "GSM1800") ||
strstr (response, "GSM1900") ||
strstr (response, "GSM850")) {
/* At least, it's a GSM modem */
*result = g_variant_new_uint32 (MM_MODEM_CAPABILITY_GSM_UMTS);
return TRUE;
}
return FALSE;
}
static const MMBaseModemAtCommand capabilities[] = {
{ "+GCAP", 2, TRUE, parse_caps_gcap },
{ "I", 1, TRUE, parse_caps_gcap }, /* yes, really parse as +GCAP */
{ "+CPIN?", 1, FALSE, parse_caps_cpin },
{ "+CGMM", 1, TRUE, parse_caps_cgmm },
{ NULL }
};
static void
capabilities_sequence_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
LoadCapabilitiesContext *ctx;
GError *error = NULL;
GVariant *result;
ctx = g_task_get_task_data (task);
result = mm_base_modem_at_sequence_finish (self, res, NULL, &error);
if (!result) {
if (error)
g_task_return_error (task, error);
else
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
"Failed to determine modem capabilities.");
g_object_unref (task);
return;
}
ctx->caps = (MMModemCapability)g_variant_get_uint32 (result);
/* Some modems (e.g. Sierra Wireless MC7710 or ZTE MF820D) won't report LTE
* capabilities even if they have them. So just run AT+WS46=? as well to see
* if the current supported modes includes any LTE-specific mode.
* This is not a big deal, as the AT+WS46=? command is a test command with a
* cache-able result.
*
* E.g.:
* AT+WS46=?
* +WS46: (12,22,25,28,29)
* OK
*
*/
if (ctx->caps & MM_MODEM_CAPABILITY_GSM_UMTS &&
!(ctx->caps & MM_MODEM_CAPABILITY_LTE)) {
mm_base_modem_at_command (
self,
"+WS46=?",
3,
TRUE, /* allow caching, it's a test command */
(GAsyncReadyCallback)current_capabilities_ws46_test_ready,
task);
return;
}
/* Otherwise, just set the already retrieved capabilities */
g_task_return_int (task, ctx->caps);
g_object_unref (task);
}
static void
load_current_capabilities_at (GTask *task)
{
MMBroadbandModem *self;
self = g_task_get_source_object (task);
/* Launch sequence, we will expect a "u" GVariant */
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
capabilities,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
(GAsyncReadyCallback)capabilities_sequence_ready,
task);
}
static void
mode_pref_qcdm_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
GTask *task)
{
LoadCapabilitiesContext *ctx;
QcdmResult *result;
gint err = QCDM_SUCCESS;
uint8_t pref = 0;
GError *error = NULL;
GByteArray *response;
ctx = g_task_get_task_data (task);
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
/* Fall back to AT checking */
mm_dbg ("Failed to load NV ModePref: %s", error->message);
g_error_free (error);
goto at_caps;
}
/* Parse the response */
result = qcdm_cmd_nv_get_mode_pref_result ((const gchar *)response->data,
response->len,
&err);
g_byte_array_unref (response);
if (!result) {
mm_dbg ("Failed to parse NV ModePref result: %d", err);
g_byte_array_unref (response);
goto at_caps;
}
err = qcdm_result_get_u8 (result, QCDM_CMD_NV_GET_MODE_PREF_ITEM_MODE_PREF, &pref);
qcdm_result_unref (result);
if (err) {
mm_dbg ("Failed to read NV ModePref: %d", err);
goto at_caps;
}
/* Only parse explicit modes; for 'auto' just fall back to whatever
* the AT current capabilities probing figures out.
*/
switch (pref) {
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_HDR_LTE_ONLY:
ctx->caps |= MM_MODEM_CAPABILITY_LTE;
/* Fall through */
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_ONLY:
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_HDR_ONLY:
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_1X_HDR_ONLY:
ctx->caps |= MM_MODEM_CAPABILITY_CDMA_EVDO;
break;
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_GSM_UMTS_LTE_ONLY:
ctx->caps |= MM_MODEM_CAPABILITY_LTE;
/* Fall through */
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_GPRS_ONLY:
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_UMTS_ONLY:
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_GSM_UMTS_ONLY:
ctx->caps |= MM_MODEM_CAPABILITY_GSM_UMTS;
break;
case QCDM_CMD_NV_MODE_PREF_ITEM_MODE_PREF_LTE_ONLY:
ctx->caps |= MM_MODEM_CAPABILITY_LTE;
break;
default:
break;
}
if (ctx->caps != MM_MODEM_CAPABILITY_NONE) {
g_task_return_int (task, ctx->caps);
g_object_unref (task);
return;
}
at_caps:
load_current_capabilities_at (task);
}
static void
load_current_capabilities_qcdm (GTask *task)
{
MMBroadbandModem *self;
LoadCapabilitiesContext *ctx;
GByteArray *cmd;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
ctx->qcdm_port = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
g_assert (ctx->qcdm_port);
if (!mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm_port), &error)) {
mm_dbg ("Failed to open QCDM port for NV ModePref request: %s",
error->message);
g_error_free (error);
ctx->qcdm_port = NULL;
load_current_capabilities_at (task);
return;
}
g_object_ref (ctx->qcdm_port);
cmd = g_byte_array_sized_new (300);
cmd->len = qcdm_cmd_nv_get_mode_pref_new ((char *) cmd->data, 300, 0);
g_assert (cmd->len);
mm_port_serial_qcdm_command (ctx->qcdm_port,
cmd,
3,
NULL,
(GAsyncReadyCallback)mode_pref_qcdm_ready,
task);
g_byte_array_unref (cmd);
}
static void
modem_load_current_capabilities (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
LoadCapabilitiesContext *ctx;
GTask *task;
mm_dbg ("loading current capabilities...");
ctx = g_slice_new0 (LoadCapabilitiesContext);
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)load_capabilities_context_free);
if (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self)))
load_current_capabilities_qcdm (task);
else
load_current_capabilities_at (task);
}
/*****************************************************************************/
/* Manufacturer loading (Modem interface) */
static gchar *
sanitize_info_reply (GVariant *v, const char *prefix)
{
const gchar *reply, *p;
gchar *sanitized;
/* Strip any leading command reply */
reply = g_variant_get_string (v, NULL);
p = strstr (reply, prefix);
if (p)
reply = p + strlen (prefix);
sanitized = g_strdup (reply);
return mm_strip_quotes (g_strstrip (sanitized));
}
static gchar *
modem_load_manufacturer_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GVariant *result;
gchar *manufacturer = NULL;
result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
if (result) {
manufacturer = sanitize_info_reply (result, "GMI:");
mm_dbg ("loaded manufacturer: %s", manufacturer);
}
return manufacturer;
}
static const MMBaseModemAtCommand manufacturers[] = {
{ "+CGMI", 3, TRUE, response_processor_string_ignore_at_errors },
{ "+GMI", 3, TRUE, response_processor_string_ignore_at_errors },
{ NULL }
};
static void
modem_load_manufacturer (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_dbg ("loading manufacturer...");
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
manufacturers,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
callback,
user_data);
}
/*****************************************************************************/
/* Model loading (Modem interface) */
static gchar *
modem_load_model_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GVariant *result;
gchar *model = NULL;
result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
if (result) {
model = sanitize_info_reply (result, "GMM:");
mm_dbg ("loaded model: %s", model);
}
return model;
}
static const MMBaseModemAtCommand models[] = {
{ "+CGMM", 3, TRUE, response_processor_string_ignore_at_errors },
{ "+GMM", 3, TRUE, response_processor_string_ignore_at_errors },
{ NULL }
};
static void
modem_load_model (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_dbg ("loading model...");
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
models,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
callback,
user_data);
}
/*****************************************************************************/
/* Revision loading */
static gchar *
modem_load_revision_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GVariant *result;
gchar *revision = NULL;
result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
if (result) {
revision = sanitize_info_reply (result, "GMR:");
mm_dbg ("loaded revision: %s", revision);
}
return revision;
}
static const MMBaseModemAtCommand revisions[] = {
{ "+CGMR", 3, TRUE, response_processor_string_ignore_at_errors },
{ "+GMR", 3, TRUE, response_processor_string_ignore_at_errors },
{ NULL }
};
static void
modem_load_revision (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_dbg ("loading revision...");
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
revisions,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
callback,
user_data);
}
/*****************************************************************************/
/* Equipment ID loading (Modem interface) */
static gchar *
modem_load_equipment_identifier_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GVariant *result;
gchar *equip_id = NULL, *esn = NULL, *meid = NULL, *imei = NULL;
result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
if (result) {
equip_id = sanitize_info_reply (result, "GSN:");
/* Modems put all sorts of things into the GSN response; sanitize it */
if (mm_parse_gsn (equip_id, &imei, &meid, &esn)) {
g_clear_pointer (&equip_id, g_free);
if (imei)
equip_id = g_strdup (imei);
else if (meid)
equip_id = g_strdup (meid);
else if (esn)
equip_id = g_strdup (esn);
else
g_assert_not_reached ();
g_free (esn);
g_free (meid);
g_free (imei);
} else {
/* Leave whatever the modem returned alone */
}
mm_dbg ("loaded equipment identifier: %s", equip_id);
}
return equip_id;
}
static const MMBaseModemAtCommand equipment_identifiers[] = {
{ "+CGSN", 3, TRUE, response_processor_string_ignore_at_errors },
{ "+GSN", 3, TRUE, response_processor_string_ignore_at_errors },
{ NULL }
};
static void
modem_load_equipment_identifier (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
const MMBaseModemAtCommand *commands = equipment_identifiers;
mm_dbg ("loading equipment identifier...");
/* On CDMA-only (non-3GPP) modems, just try +GSN */
if (mm_iface_modem_is_cdma_only (self))
commands++;
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
commands,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
callback,
user_data);
}
/*****************************************************************************/
/* Device identifier loading (Modem interface) */
typedef struct {
gchar *ati;
gchar *ati1;
} DeviceIdentifierContext;
static void
device_identifier_context_free (DeviceIdentifierContext *ctx)
{
g_free (ctx->ati);
g_free (ctx->ati1);
g_free (ctx);
}
static gchar *
modem_load_device_identifier_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gpointer ctx = NULL;
gchar *device_identifier;
mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, &ctx, &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return NULL;
}
g_assert (ctx != NULL);
device_identifier = (mm_broadband_modem_create_device_identifier (
MM_BROADBAND_MODEM (self),
((DeviceIdentifierContext *)ctx)->ati,
((DeviceIdentifierContext *)ctx)->ati1));
mm_dbg ("loaded device identifier: %s", device_identifier);
return device_identifier;
}
static gboolean
parse_ati_reply (MMBaseModem *self,
DeviceIdentifierContext *ctx,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
/* Store the proper string in the proper place */
if (!error) {
if (g_str_equal (command, "ATI1"))
ctx->ati1 = g_strdup (response);
else
ctx->ati = g_strdup (response);
}
/* Always keep on, this is a sequence where all the steps should be taken */
return TRUE;
}
static const MMBaseModemAtCommand device_identifier_steps[] = {
{ "ATI", 3, TRUE, (MMBaseModemAtResponseProcessor)parse_ati_reply },
{ "ATI1", 3, TRUE, (MMBaseModemAtResponseProcessor)parse_ati_reply },
{ NULL }
};
static void
modem_load_device_identifier (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
mm_dbg ("loading device identifier...");
mm_base_modem_at_sequence (
MM_BASE_MODEM (self),
device_identifier_steps,
g_new0 (DeviceIdentifierContext, 1),
(GDestroyNotify)device_identifier_context_free,
callback,
user_data);
}
/*****************************************************************************/
/* Load own numbers (Modem interface) */
typedef struct {
MMPortSerialQcdm *qcdm;
} OwnNumbersContext;
static void
own_numbers_context_free (OwnNumbersContext *ctx)
{
if (ctx->qcdm) {
mm_port_serial_close (MM_PORT_SERIAL (ctx->qcdm));
g_object_unref (ctx->qcdm);
}
g_free (ctx);
}
static GStrv
modem_load_own_numbers_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void
mdn_qcdm_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
GTask *task)
{
QcdmResult *result;
gint err = QCDM_SUCCESS;
const char *numbers[2] = { NULL, NULL };
GByteArray *response;
GError *error = NULL;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Parse the response */
result = qcdm_cmd_nv_get_mdn_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
if (!result) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse NV MDN command result: %d",
err);
g_object_unref (task);
return;
}
if (qcdm_result_get_string (result, QCDM_CMD_NV_GET_MDN_ITEM_MDN, &numbers[0]) >= 0) {
gboolean valid = TRUE;
const char *p = numbers[0];
/* Returned NV item data is read directly out of NV memory on the card,
* so minimally verify it.
*/
if (strlen (numbers[0]) < 6 || strlen (numbers[0]) > 15)
valid = FALSE;
/* MDN is always decimal digits; allow + for good measure */
while (p && *p && valid)
valid = g_ascii_isdigit (*p++) || (*p == '+');
if (valid) {
g_task_return_pointer (task,
g_strdupv ((gchar **) numbers),
(GDestroyNotify)g_strfreev);
} else {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
"MDN from NV memory appears invalid");
}
} else {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"%s",
"Failed retrieve MDN");
}
g_object_unref (task);
qcdm_result_unref (result);
}
static void
modem_load_own_numbers_done (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
OwnNumbersContext *ctx;
const gchar *result;
GError *error = NULL;
GStrv numbers;
ctx = g_task_get_task_data (task);
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (!result) {
/* try QCDM */
if (ctx->qcdm) {
GByteArray *mdn;
g_clear_error (&error);
mdn = g_byte_array_sized_new (200);
mdn->len = qcdm_cmd_nv_get_mdn_new ((char *) mdn->data, 200, 0);
g_assert (mdn->len);
mm_port_serial_qcdm_command (ctx->qcdm,
mdn,
3,
NULL,
(GAsyncReadyCallback)mdn_qcdm_ready,
task);
g_byte_array_unref (mdn);
return;
}
g_task_return_error (task, error);
} else {
numbers = mm_3gpp_parse_cnum_exec_response (result);
g_task_return_pointer (task, numbers, (GDestroyNotify)g_strfreev);
}
g_object_unref (task);
}
static void
modem_load_own_numbers (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
OwnNumbersContext *ctx;
GError *error = NULL;
GTask *task;
ctx = g_new0 (OwnNumbersContext, 1);
ctx->qcdm = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
if (ctx->qcdm) {
if (mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm), &error)) {
ctx->qcdm = g_object_ref (ctx->qcdm);
} else {
mm_dbg ("Couldn't open QCDM port: (%d) %s",
error ? error->code : -1,
error ? error->message : "(unknown)");
ctx->qcdm = NULL;
}
}
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)own_numbers_context_free);
mm_dbg ("loading own numbers...");
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CNUM",
3,
FALSE,
(GAsyncReadyCallback)modem_load_own_numbers_done,
task);
}
/*****************************************************************************/
/* Check if unlock required (Modem interface) */
typedef struct {
const gchar *result;
MMModemLock code;
} CPinResult;
static CPinResult unlock_results[] = {
/* Longer entries first so we catch the correct one with strcmp() */
{ "READY", MM_MODEM_LOCK_NONE },
{ "SIM PIN2", MM_MODEM_LOCK_SIM_PIN2 },
{ "SIM PUK2", MM_MODEM_LOCK_SIM_PUK2 },
{ "SIM PIN", MM_MODEM_LOCK_SIM_PIN },
{ "SIM PUK", MM_MODEM_LOCK_SIM_PUK },
{ "PH-NETSUB PIN", MM_MODEM_LOCK_PH_NETSUB_PIN },
{ "PH-NETSUB PUK", MM_MODEM_LOCK_PH_NETSUB_PUK },
{ "PH-FSIM PIN", MM_MODEM_LOCK_PH_FSIM_PIN },
{ "PH-FSIM PUK", MM_MODEM_LOCK_PH_FSIM_PUK },
{ "PH-CORP PIN", MM_MODEM_LOCK_PH_CORP_PIN },
{ "PH-CORP PUK", MM_MODEM_LOCK_PH_CORP_PUK },
{ "PH-SIM PIN", MM_MODEM_LOCK_PH_SIM_PIN },
{ "PH-NET PIN", MM_MODEM_LOCK_PH_NET_PIN },
{ "PH-NET PUK", MM_MODEM_LOCK_PH_NET_PUK },
{ "PH-SP PIN", MM_MODEM_LOCK_PH_SP_PIN },
{ "PH-SP PUK", MM_MODEM_LOCK_PH_SP_PUK },
{ NULL }
};
static MMModemLock
modem_load_unlock_required_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_MODEM_LOCK_UNKNOWN;
}
return (MMModemLock)value;
}
static void
cpin_query_ready (MMIfaceModem *self,
GAsyncResult *res,
GTask *task)
{
MMModemLock lock = MM_MODEM_LOCK_UNKNOWN;
const gchar *result;
GError *error = NULL;
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
if (result &&
strstr (result, "+CPIN:")) {
CPinResult *iter = &unlock_results[0];
const gchar *str;
str = strstr (result, "+CPIN:") + 6;
/* Skip possible whitespaces after '+CPIN:' and before the response */
while (*str == ' ')
str++;
/* Some phones (Motorola EZX models) seem to quote the response */
if (str[0] == '"')
str++;
/* Translate the reply */
while (iter->result) {
if (g_str_has_prefix (str, iter->result)) {
lock = iter->code;
break;
}
iter++;
}
}
g_task_return_int (task, lock);
g_object_unref (task);
}
static void
modem_load_unlock_required (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (self, NULL, callback, user_data);
/* CDMA-only modems don't need this */
if (mm_iface_modem_is_cdma_only (self)) {
mm_dbg ("Skipping unlock check in CDMA-only modem...");
g_task_return_int (task, MM_MODEM_LOCK_NONE);
g_object_unref (task);
return;
}
mm_dbg ("checking if unlock required...");
mm_base_modem_at_command (MM_BASE_MODEM (self),
"+CPIN?",
3,
FALSE,
(GAsyncReadyCallback)cpin_query_ready,
task);
}
/*****************************************************************************/
/* Supported modes loading (Modem interface) */
typedef struct {
MMUnlockRetries *retries;
guint i;
} LoadUnlockRetriesContext;
typedef struct {
MMModemLock lock;
const gchar *command;
} UnlockRetriesMap;
static const UnlockRetriesMap unlock_retries_map [] = {
{ MM_MODEM_LOCK_SIM_PIN, "+CSIM=10,\"0020000100\"" },
{ MM_MODEM_LOCK_SIM_PUK, "+CSIM=10,\"002C000100\"" },
{ MM_MODEM_LOCK_SIM_PIN2, "+CSIM=10,\"0020008100\"" },
{ MM_MODEM_LOCK_SIM_PUK2, "+CSIM=10,\"002C008100\"" },
};
static void
load_unlock_retries_context_free (LoadUnlockRetriesContext *ctx)
{
g_object_unref (ctx->retries);
g_slice_free (LoadUnlockRetriesContext, ctx);
}
static MMUnlockRetries *
load_unlock_retries_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_pointer (G_TASK (res), error);
}
static void load_unlock_retries_context_step (GTask *task);
static void
csim_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
LoadUnlockRetriesContext *ctx;
const gchar *response;
GError *error = NULL;
ctx = g_task_get_task_data (task);
response = mm_base_modem_at_command_finish (self, res, &error);
if (!response) {
mm_dbg ("Couldn't load retry count for lock '%s': %s",
mm_modem_lock_get_string (unlock_retries_map[ctx->i].lock),
error->message);
g_error_free (error);
} else {
gint val;
GError *error = NULL;
val = mm_parse_csim_response (response, &error);
if (val < 0) {
mm_warn ("Parse error in step %d: %s.", ctx->i, error->message);
mm_dbg ("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)
{
MMBroadbandModem *self;
LoadUnlockRetriesContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (ctx->i == G_N_ELEMENTS (unlock_retries_map)) {
g_task_return_pointer (task, g_object_ref (ctx->retries), g_object_unref);
g_object_unref (task);
return;
}
mm_base_modem_at_command (
MM_BASE_MODEM (self),
unlock_retries_map[ctx->i].command,
3,
FALSE,
(GAsyncReadyCallback)csim_ready,
task);
}
static void
load_unlock_retries (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
LoadUnlockRetriesContext *ctx;
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (LoadUnlockRetriesContext);
ctx->retries = mm_unlock_retries_new ();
ctx->i = 0;
g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_retries_context_free);
load_unlock_retries_context_step (task);
}
/*****************************************************************************/
/* Supported modes loading (Modem interface) */
typedef struct {
MMModemMode mode;
gboolean run_cnti;
gboolean run_ws46;
gboolean run_gcap;
} LoadSupportedModesContext;
static GArray *
modem_load_supported_modes_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
GArray *modes;
MMModemModeCombination mode;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return NULL;
}
/* Build a mask with all supported modes */
modes = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
mode.allowed = (MMModemMode) value;
mode.preferred = MM_MODEM_MODE_NONE;
g_array_append_val (modes, mode);
return modes;
}
static void load_supported_modes_step (GTask *task);
static void
supported_modes_gcap_ready (MMBaseModem *_self,
GAsyncResult *res,
GTask *task)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
LoadSupportedModesContext *ctx;
const gchar *response;
GError *error = NULL;
ctx = g_task_get_task_data (task);
response = mm_base_modem_at_command_finish (_self, res, &error);
if (!error) {
MMModemMode mode = MM_MODEM_MODE_NONE;
if (strstr (response, "IS")) {
/* IS-856 is the EV-DO family */
if (strstr (response, "856")) {
if (!self->priv->modem_cdma_evdo_network_supported) {
self->priv->modem_cdma_evdo_network_supported = TRUE;
g_object_notify (G_OBJECT (self), MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED);
}
mm_dbg ("Device allows (CDMA) 3G network mode");
mode |= MM_MODEM_MODE_3G;
}
/* IS-707 is the 1xRTT family, which we consider as 2G */
if (strstr (response, "707")) {
if (!self->priv->modem_cdma_cdma1x_network_supported) {
self->priv->modem_cdma_cdma1x_network_supported = TRUE;
g_object_notify (G_OBJECT (self), MM_IFACE_MODEM_CDMA_CDMA1X_NETWORK_SUPPORTED);
}
mm_dbg ("Device allows (CDMA) 2G network mode");
mode |= MM_MODEM_MODE_2G;
}
}
/* If no expected mode found, error */
if (mode == MM_MODEM_MODE_NONE) {
/* This should really never happen in the default implementation. */
error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't find specific CDMA mode in capabilities string: '%s'",
response);
} else {
/* Keep our results */
ctx->mode |= mode;
}
}
if (error) {
mm_dbg ("Generic query of supported CDMA networks failed: '%s'", error->message);
g_error_free (error);
/* Use defaults */
if (self->priv->modem_cdma_cdma1x_network_supported) {
mm_dbg ("Assuming device allows (CDMA) 2G network mode");
ctx->mode |= MM_MODEM_MODE_2G;
}
if (self->priv->modem_cdma_evdo_network_supported) {
mm_dbg ("Assuming device allows (CDMA) 3G network mode");
ctx->mode |= MM_MODEM_MODE_3G;
}
}
/* Now keep on with the loading, we're probably finishing now */
ctx->run_gcap = FALSE;
load_supported_modes_step (task);
}
static void
supported_modes_ws46_test_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
LoadSupportedModesContext *ctx;
const gchar *response;
GArray *modes;
GError *error = NULL;
guint i;
ctx = g_task_get_task_data (task);
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error) {
mm_dbg ("Generic query of supported 3GPP networks with WS46=? failed: '%s'", error->message);
g_error_free (error);
goto out;
}
modes = mm_3gpp_parse_ws46_test_response (response, &error);
if (!modes) {
mm_dbg ("Parsing WS46=? response failed: '%s'", error->message);
g_error_free (error);
goto out;
}
for (i = 0; i < modes->len; i++) {
MMModemMode mode;
gchar *str;
mode = g_array_index (modes, MMModemMode, i);
ctx->mode |= mode;
str = mm_modem_mode_build_string_from_mask (mode);
mm_dbg ("Device allows (3GPP) mode combination: %s", str);
g_free (str);
}
g_array_unref (modes);
out:
/* Now keep on with the loading, we may need CDMA-specific checks */
ctx->run_ws46 = FALSE;
load_supported_modes_step (task);
}
static void
supported_modes_cnti_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
LoadSupportedModesContext *ctx;
const gchar *response;
GError *error = NULL;
ctx = g_task_get_task_data (task);
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (!error) {
MMModemMode mode = MM_MODEM_MODE_NONE;
gchar *lower;
lower = g_ascii_strdown (response, -1);
if (g_strstr_len (lower, -1, "gsm") ||
g_strstr_len (lower, -1, "gprs") ||
g_strstr_len (lower, -1, "edge")) {
mm_dbg ("Device allows (3GPP) 2G networks");
mode |= MM_MODEM_MODE_2G;
}
if (g_strstr_len (lower, -1, "umts") ||
g_strstr_len (lower, -1, "hsdpa") ||
g_strstr_len (lower, -1, "hsupa") ||
g_strstr_len (lower, -1, "hspa+")) {
mm_dbg ("Device allows (3GPP) 3G networks");
mode |= MM_MODEM_MODE_3G;
}
if (g_strstr_len (lower, -1, "lte")) {
mm_dbg ("Device allows (3GPP) 4G networks");
mode |= MM_MODEM_MODE_4G;
}
g_free (lower);
/* If no expected ID found, log error */
if (mode == MM_MODEM_MODE_NONE)
mm_dbg ("Invalid list of supported networks reported by *CNTI: '%s'", response);
else
ctx->mode |= mode;
} else {
mm_dbg ("Generic query of supported 3GPP networks with *CNTI failed: '%s'", error->message);
g_error_free (error);
}
/* Now keep on with the loading */
ctx->run_cnti = FALSE;
load_supported_modes_step (task);
}
static void
load_supported_modes_step (GTask *task)
{
MMBroadbandModem *self;
LoadSupportedModesContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
if (ctx->run_cnti) {
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"*CNTI=2",
3,
FALSE,
(GAsyncReadyCallback)supported_modes_cnti_ready,
task);
return;
}
if (ctx->run_ws46) {
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+WS46=?",
3,
TRUE, /* allow caching, it's a test command */
(GAsyncReadyCallback)supported_modes_ws46_test_ready,
task);
return;
}
if (ctx->run_gcap) {
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+GCAP",
3,
TRUE, /* allow caching */
(GAsyncReadyCallback)supported_modes_gcap_ready,
task);
return;
}
/* All done.
* If no mode found, error */
if (ctx->mode == MM_MODEM_MODE_NONE)
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't retrieve supported modes");
else
g_task_return_int (task, ctx->mode);
g_object_unref (task);
}
static void
modem_load_supported_modes (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
LoadSupportedModesContext *ctx;
GTask *task;
mm_dbg ("loading supported modes...");
ctx = g_new0 (LoadSupportedModesContext, 1);
ctx->mode = MM_MODEM_MODE_NONE;
if (mm_iface_modem_is_3gpp (self)) {
/* Run +WS46=? and *CNTI=2 */
ctx->run_ws46 = TRUE;
ctx->run_cnti = TRUE;
}
if (mm_iface_modem_is_cdma (self)) {
/* Run +GCAP in order to know if the modem is CDMA1x only or CDMA1x/EV-DO */
ctx->run_gcap = TRUE;
}
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, g_free);
load_supported_modes_step (task);
}
/*****************************************************************************/
/* Supported IP families loading (Modem interface) */
static MMBearerIpFamily
modem_load_supported_ip_families_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
GError *inner_error = NULL;
gssize value;
value = g_task_propagate_int (G_TASK (res), &inner_error);
if (inner_error) {
g_propagate_error (error, inner_error);
return MM_BEARER_IP_FAMILY_NONE;
}
return (MMBearerIpFamily)value;
}
static void
supported_ip_families_cgdcont_test_ready (MMBaseModem *self,
GAsyncResult *res,
GTask *task)
{
const gchar *response;
GError *error = NULL;
MMBearerIpFamily mask = MM_BEARER_IP_FAMILY_NONE;
response = mm_base_modem_at_command_finish (self, res, &error);
if (response) {
GList *formats, *l;
formats = mm_3gpp_parse_cgdcont_test_response (response, &error);
for (l = formats; l; l = g_list_next (l))
mask |= ((MM3gppPdpContextFormat *)(l->data))->pdp_type;
mm_3gpp_pdp_context_format_list_free (formats);
}
if (error)
g_task_return_error (task, error);
else
g_task_return_int (task, mask);
g_object_unref (task);
}
static void
modem_load_supported_ip_families (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
mm_dbg ("loading supported IP families...");
task = g_task_new (self, NULL, callback, user_data);
if (mm_iface_modem_is_cdma_only (self)) {
g_task_return_int (task, MM_BEARER_IP_FAMILY_IPV4);
g_object_unref (task);
return;
}
/* Query with CGDCONT=? */
mm_base_modem_at_command (
MM_BASE_MODEM (self),
"+CGDCONT=?",
3,
TRUE, /* allow caching, it's a test command */
(GAsyncReadyCallback)supported_ip_families_cgdcont_test_ready,
task);
}
/*****************************************************************************/
/* Signal quality loading (Modem interface) */
static void
qcdm_evdo_pilot_sets_log_handle (MMPortSerialQcdm *port,
GByteArray *log_buffer,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (user_data);
QcdmResult *result;
uint32_t num_active = 0;
uint32_t pilot_pn = 0;
uint32_t pilot_energy = 0;
int32_t rssi_dbm = 0;
result = qcdm_log_item_evdo_pilot_sets_v2_new ((const char *) log_buffer->data,
log_buffer->len,
NULL);
if (!result)
return;
if (!qcdm_log_item_evdo_pilot_sets_v2_get_num (result,
QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE,
&num_active)) {
qcdm_result_unref (result);
return;
}
if (num_active > 0 &&
qcdm_log_item_evdo_pilot_sets_v2_get_pilot (result,
QCDM_LOG_ITEM_EVDO_PILOT_SETS_V2_TYPE_ACTIVE,
0,
&pilot_pn,
&pilot_energy,
&rssi_dbm)) {
mm_dbg ("EVDO active pilot RSSI: %ddBm", rssi_dbm);
self->priv->evdo_pilot_rssi = rssi_dbm;
}
qcdm_result_unref (result);
}
typedef struct {
MMPortSerial *at_port;
MMPortSerial *qcdm_port;
} SignalQualityContext;
static void
signal_quality_context_free (SignalQualityContext *ctx)
{
g_clear_object (&ctx->at_port);
if (ctx->qcdm_port) {
mm_port_serial_close (ctx->qcdm_port);
g_object_unref (ctx->qcdm_port);
}
g_free (ctx);
}
static guint
modem_load_signal_quality_finish (MMIfaceModem *self,
GAsyncResult *res,
GError **error)
{
gssize value;
value = g_task_propagate_int (G_TASK (res), error);
return value < 0 ? 0 : value;
}
static guint
signal_quality_evdo_pilot_sets (MMBroadbandModem *self)
{
gint dbm;
if (self->priv->modem_cdma_evdo_registration_state == MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
return 0;
if (self->priv->evdo_pilot_rssi >= 0)
return 0;
dbm = CLAMP (self->priv->evdo_pilot_rssi, -113, -51);
return 100 - ((dbm + 51) * 100 / (-113 + 51));
}
static void
signal_quality_csq_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
GError *error = NULL;
GVariant *result;
const gchar *result_str;
result = mm_base_modem_at_sequence_full_finish (MM_BASE_MODEM (self), res, NULL, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
result_str = g_variant_get_string (result, NULL);
if (result_str) {
/* Got valid reply */
int quality;
int ber;
result_str = mm_strip_tag (result_str, "+CSQ:");
if (sscanf (result_str, "%d, %d", &quality, &ber)) {
if (quality == 99) {
/* 99 can mean unknown, no service, etc. But the modem may
* also only report CDMA 1x quality in CSQ, so try EVDO via
* QCDM log messages too.
*/
quality = signal_quality_evdo_pilot_sets (self);
} else {
/* Normalize the quality */
quality = CLAMP (quality, 0, 31) * 100 / 31;
}
g_task_return_int (task, quality);
g_object_unref (task);
return;
}
}
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Could not parse signal quality results");
g_object_unref (task);
}
/* Some modems want +CSQ, others want +CSQ?, and some of both types
* will return ERROR if they don't get the command they want. So
* try the other command if the first one fails.
*/
static const MMBaseModemAtCommand signal_quality_csq_sequence[] = {
{ "+CSQ", 3, TRUE, response_processor_string_ignore_at_errors },
{ "+CSQ?", 3, TRUE, response_processor_string_ignore_at_errors },
{ NULL }
};
static void
signal_quality_csq (GTask *task)
{
MMBroadbandModem *self;
SignalQualityContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
mm_base_modem_at_sequence_full (
MM_BASE_MODEM (self),
MM_PORT_SERIAL_AT (ctx->at_port),
signal_quality_csq_sequence,
NULL, /* response_processor_context */
NULL, /* response_processor_context_free */
NULL, /* cancellable */
(GAsyncReadyCallback)signal_quality_csq_ready,
task);
}
static guint
normalize_ciev_cind_signal_quality (guint quality,
guint min,
guint max)
{
if (!max) {
/* If we didn't get a max, assume it was 5. Note that we do allow
* 0, meaning no signal at all. */
return (quality <= 5) ? (quality * 20) : 100;
}
if (quality >= min &&
quality <= max)
return ((100 * (quality - min)) / (max - min));
/* Value out of range, assume no signal here. Some modems (Cinterion
* for example) will send out-of-range values when they cannot get
* the signal strength. */
return 0;
}
static void
signal_quality_cind_ready (MMBroadbandModem *self,
GAsyncResult *res,
GTask *task)
{
SignalQualityContext *ctx;
GError *error = NULL;
const gchar *result;
GByteArray *indicators;
guint quality = 0;
ctx = g_task_get_task_data (task);
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error) {
g_clear_error (&error);
goto try_csq;
}
indicators = mm_3gpp_parse_cind_read_response (result, &error);
if (!indicators) {
mm_dbg ("(%s) Could not parse CIND signal quality results: %s",
mm_port_get_device (MM_PORT (ctx->at_port)),
error->message);
g_clear_error (&error);
goto try_csq;
}
if (indicators->len < self->priv->modem_cind_indicator_signal_quality) {
mm_dbg ("(%s) Could not parse CIND signal quality results; signal "
"index (%u) outside received range (0-%u)",
mm_port_get_device (MM_PORT (ctx->at_port)),
self->priv->modem_cind_indicator_signal_quality,
indicators->len);
} else {
quality = g_array_index (indicators,
guint8,
self->priv->modem_cind_indicator_signal_quality);
quality = normalize_ciev_cind_signal_quality (quality,
self->priv->modem_cind_min_signal_quality,
self->priv->modem_cind_max_signal_quality);
}
g_byte_array_free (indicators, TRUE);
if (quality > 0) {
/* +CIND success */
g_task_return_int (task, quality);
g_object_unref (task);
return;
}
try_csq:
/* Always fall back to +CSQ if for whatever reason +CIND failed. Also,
* some QMI-based devices say they support signal via CIND, but always
* report zero even though they have signal. So if we get zero signal
* from +CIND, try CSQ too. (bgo #636040)
*/
signal_quality_csq (task);
}
static void
signal_quality_cind (GTask *task)
{
MMBroadbandModem *self;
SignalQualityContext *ctx;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
MM_PORT_SERIAL_AT (ctx->at_port),
"+CIND?",
5,
FALSE,
FALSE, /* raw */
NULL, /* cancellable */
(GAsyncReadyCallback)signal_quality_cind_ready,
task);
}
static void
signal_quality_qcdm_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
GTask *task)
{
QcdmResult *result;
guint32 num = 0, quality = 0, i;
gfloat best_db = -28;
gint err = QCDM_SUCCESS;
GByteArray *response;
GError *error = NULL;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Parse the response */
result = qcdm_cmd_pilot_sets_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
if (!result) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse pilot sets command result: %d",
err);
g_object_unref (task);
return;
}
qcdm_cmd_pilot_sets_result_get_num (result, QCDM_CMD_PILOT_SETS_TYPE_ACTIVE, &num);
for (i = 0; i < num; i++) {
guint32 pn_offset = 0, ecio = 0;
gfloat db = 0;
qcdm_cmd_pilot_sets_result_get_pilot (result,
QCDM_CMD_PILOT_SETS_TYPE_ACTIVE,
i,
&pn_offset,
&ecio,
&db);
best_db = MAX (db, best_db);
}
qcdm_result_unref (result);
if (num > 0) {
#define BEST_ECIO 3
#define WORST_ECIO 25
/* EC/IO dB ranges from roughly 0 to -31 dB. Lower == worse. We
* really only care about -3 to -25 dB though, since that's about what
* you'll see in real-world usage.
*/
best_db = CLAMP (ABS (best_db), BEST_ECIO, WORST_ECIO) - BEST_ECIO;
quality = (guint32) (100 - (best_db * 100 / (WORST_ECIO - BEST_ECIO)));
}
g_task_return_int (task, quality);
g_object_unref (task);
}
static void
signal_quality_qcdm (GTask *task)
{
MMBroadbandModem *self;
SignalQualityContext *ctx;
GByteArray *pilot_sets;
guint quality;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
/* If EVDO is active try that signal strength first */
quality = signal_quality_evdo_pilot_sets (self);
if (quality > 0) {
g_task_return_int (task, quality);
g_object_unref (task);
return;
}
/* Use CDMA1x pilot EC/IO if we can */
pilot_sets = g_byte_array_sized_new (25);
pilot_sets->len = qcdm_cmd_pilot_sets_new ((char *) pilot_sets->data, 25);
g_assert (pilot_sets->len);
mm_port_serial_qcdm_command (MM_PORT_SERIAL_QCDM (ctx->qcdm_port),
pilot_sets,
3,
NULL,
(GAsyncReadyCallback)signal_quality_qcdm_ready,
task);
g_byte_array_unref (pilot_sets);
}
static void
modem_load_signal_quality (MMIfaceModem *_self,
GAsyncReadyCallback callback,
gpointer user_data)
{
MMBroadbandModem *self = MM_BROADBAND_MODEM (_self);
SignalQualityContext *ctx;
GError *error = NULL;
GTask *task;
mm_dbg ("loading signal quality...");
ctx = g_new0 (SignalQualityContext, 1);
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)signal_quality_context_free);
/* Check whether we can get a non-connected AT port */
ctx->at_port = (MMPortSerial *)mm_base_modem_get_best_at_port (MM_BASE_MODEM (self), &error);
if (ctx->at_port) {
if (self->priv->modem_cind_supported &&
CIND_INDICATOR_IS_VALID (self->priv->modem_cind_indicator_signal_quality))
signal_quality_cind (task);
else
signal_quality_csq (task);
return;
}
/* If no best AT port available (all connected), try with QCDM ports */
ctx->qcdm_port = (MMPortSerial *)mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
if (ctx->qcdm_port) {
g_clear_error (&error);
/* Need to open QCDM port as it may be closed/blocked */
if (mm_port_serial_open (MM_PORT_SERIAL (ctx->qcdm_port), &error)) {
g_object_ref (ctx->qcdm_port);
signal_quality_qcdm (task);
return;
}
ctx->qcdm_port = NULL;
mm_dbg ("Couldn't open QCDM port: %s", error->message);
}
/* Return the error we got when getting best AT port */
g_task_return_error (task, error);
g_object_unref (task);
}
/*****************************************************************************/
/* Load access technology (Modem interface) */
typedef struct {
MMModemAccessTechnology access_technologies;
guint mask;
} AccessTechAndMask;
static gboolean
modem_load_access_technologies_finish (MMIfaceModem *self,
GAsyncResult *res,
MMModemAccessTechnology *access_technologies,
guint *mask,
GError **error)
{
AccessTechAndMask *tech;
tech = g_task_propagate_pointer (G_TASK (res), error);
if (!tech)
return FALSE;
*access_technologies = tech->access_technologies;
*mask = tech->mask;
g_free (tech);
return TRUE;
}
typedef struct {
MMPortSerialQcdm *port;
guint32 opmode;
guint32 sysmode;
gboolean hybrid;
gboolean wcdma_open;
gboolean evdo_open;
MMModemAccessTechnology fallback_act;
guint fallback_mask;
} AccessTechContext;
static void
access_tech_context_free (AccessTechContext *ctx)
{
if (ctx->port) {
mm_port_serial_close (MM_PORT_SERIAL (ctx->port));
g_object_unref (ctx->port);
}
g_free (ctx);
}
static AccessTechAndMask *
access_tech_and_mask_new (AccessTechContext *ctx)
{
AccessTechAndMask *tech;
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
guint mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
if (ctx->fallback_mask) {
mm_dbg ("Fallback access technology: 0x%08x", ctx->fallback_act);
act = ctx->fallback_act;
mask = ctx->fallback_mask;
goto done;
}
mm_dbg ("QCDM operating mode: %d", ctx->opmode);
mm_dbg ("QCDM system mode: %d", ctx->sysmode);
mm_dbg ("QCDM hybrid pref: %d", ctx->hybrid);
mm_dbg ("QCDM WCDMA open: %d", ctx->wcdma_open);
mm_dbg ("QCDM EVDO open: %d", ctx->evdo_open);
if (ctx->opmode == QCDM_CMD_CM_SUBSYS_STATE_INFO_OPERATING_MODE_ONLINE) {
switch (ctx->sysmode) {
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_CDMA:
if (!ctx->hybrid || !ctx->evdo_open) {
act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
break;
}
/* Fall through */
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_HDR:
/* Assume EVDOr0; can't yet determine r0 vs. rA with QCDM */
if (ctx->evdo_open)
act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
break;
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_GSM:
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_GW:
if (ctx->wcdma_open) {
/* Assume UMTS; can't yet determine UMTS/HSxPA/HSPA+ with QCDM */
act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
} else {
/* Assume GPRS; can't yet determine GSM/GPRS/EDGE with QCDM */
act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
}
mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
break;
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_WCDMA:
if (ctx->wcdma_open) {
/* Assume UMTS; can't yet determine UMTS/HSxPA/HSPA+ with QCDM */
act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
}
mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
break;
case QCDM_CMD_CM_SUBSYS_STATE_INFO_SYSTEM_MODE_LTE:
act = MM_MODEM_ACCESS_TECHNOLOGY_LTE;
mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
break;
}
}
done:
tech = g_new0 (AccessTechAndMask, 1);
tech->access_technologies = act;
tech->mask = mask;
return tech;
}
static void
access_tech_qcdm_wcdma_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
GTask *task)
{
AccessTechContext *ctx;
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint8 l1;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
ctx = g_task_get_task_data (task);
/* Parse the response */
result = qcdm_cmd_wcdma_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
if (result) {
qcdm_result_get_u8 (result, QCDM_CMD_WCDMA_SUBSYS_STATE_INFO_ITEM_L1_STATE, &l1);
qcdm_result_unref (result);
if (l1 == QCDM_WCDMA_L1_STATE_PCH ||
l1 == QCDM_WCDMA_L1_STATE_FACH ||
l1 == QCDM_WCDMA_L1_STATE_DCH ||
l1 == QCDM_WCDMA_L1_STATE_PCH_SLEEP)
ctx->wcdma_open = TRUE;
}
g_task_return_pointer (task, access_tech_and_mask_new (ctx), g_free);
g_object_unref (task);
}
static void
access_tech_qcdm_gsm_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
GTask *task)
{
AccessTechContext *ctx;
GByteArray *cmd;
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint8 opmode = 0;
guint8 sysmode = 0;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Parse the response */
result = qcdm_cmd_gsm_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
if (!result) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse GSM subsys command result: %d",
err);
g_object_unref (task);
return;
}
ctx = g_task_get_task_data (task);
qcdm_result_get_u8 (result, QCDM_CMD_GSM_SUBSYS_STATE_INFO_ITEM_CM_OP_MODE, &opmode);
qcdm_result_get_u8 (result, QCDM_CMD_GSM_SUBSYS_STATE_INFO_ITEM_CM_SYS_MODE, &sysmode);
qcdm_result_unref (result);
ctx->opmode = opmode;
ctx->sysmode = sysmode;
/* WCDMA subsystem state */
cmd = g_byte_array_sized_new (50);
cmd->len = qcdm_cmd_wcdma_subsys_state_info_new ((char *) cmd->data, 50);
g_assert (cmd->len);
mm_port_serial_qcdm_command (port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_wcdma_ready,
task);
g_byte_array_unref (cmd);
}
static void
access_tech_qcdm_hdr_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
GTask *task)
{
AccessTechContext *ctx;
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint8 session = 0;
guint8 almp = 0;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
ctx = g_task_get_task_data (task);
/* Parse the response */
result = qcdm_cmd_hdr_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
if (result) {
qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_SESSION_STATE, &session);
qcdm_result_get_u8 (result, QCDM_CMD_HDR_SUBSYS_STATE_INFO_ITEM_ALMP_STATE, &almp);
qcdm_result_unref (result);
if (session == QCDM_CMD_HDR_SUBSYS_STATE_INFO_SESSION_STATE_OPEN &&
(almp == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_IDLE ||
almp == QCDM_CMD_HDR_SUBSYS_STATE_INFO_ALMP_STATE_CONNECTED))
ctx->evdo_open = TRUE;
}
g_task_return_pointer (task, access_tech_and_mask_new (ctx), g_free);
g_object_unref (task);
}
static void
access_tech_qcdm_cdma_ready (MMPortSerialQcdm *port,
GAsyncResult *res,
GTask *task)
{
AccessTechContext *ctx;
GByteArray *cmd;
QcdmResult *result;
gint err = QCDM_SUCCESS;
guint32 hybrid;
GError *error = NULL;
GByteArray *response;
response = mm_port_serial_qcdm_command_finish (port, res, &error);
if (error) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* Parse the response */
result = qcdm_cmd_cm_subsys_state_info_result ((const gchar *) response->data,
response->len,
&err);
g_byte_array_unref (response);
if (!result) {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse CM subsys command result: %d",
err);
g_object_unref (task);
return;
}
ctx = g_task_get_task_data (task);
qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_OPERATING_MODE, &ctx->opmode);
qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_SYSTEM_MODE, &ctx->sysmode);
qcdm_result_get_u32 (result, QCDM_CMD_CM_SUBSYS_STATE_INFO_ITEM_HYBRID_PREF, &hybrid);
qcdm_result_unref (result);
ctx->hybrid = !!hybrid;
/* HDR subsystem state */
cmd = g_byte_array_sized_new (50);
cmd->len = qcdm_cmd_hdr_subsys_state_info_new ((char *) cmd->data, 50);
g_assert (cmd->len);
mm_port_serial_qcdm_command (port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_hdr_ready,
task);
g_byte_array_unref (cmd);
}
static void
access_tech_from_cdma_registration_state (MMBroadbandModem *self,
AccessTechContext *ctx)
{
gboolean cdma1x_registered = FALSE;
gboolean evdo_registered = FALSE;
if (self->priv->modem_cdma_evdo_registration_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
evdo_registered = TRUE;
if (self->priv->modem_cdma_cdma1x_registration_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
cdma1x_registered = TRUE;
if (self->priv->modem_cdma_evdo_network_supported && evdo_registered) {
ctx->fallback_act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
ctx->fallback_mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
} else if (self->priv->modem_cdma_cdma1x_network_supported && cdma1x_registered) {
ctx->fallback_act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
ctx->fallback_mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
}
mm_dbg ("EVDO registration: %d", self->priv->modem_cdma_evdo_registration_state);
mm_dbg ("CDMA1x registration: %d", self->priv->modem_cdma_cdma1x_registration_state);
mm_dbg ("Fallback access tech: 0x%08x", ctx->fallback_act);
}
static void
modem_load_access_technologies (MMIfaceModem *self,
GAsyncReadyCallback callback,
gpointer user_data)
{
AccessTechContext *ctx;
GTask *task;
GByteArray *cmd;
GError *error = NULL;
/* For modems where only QCDM provides detailed information, try to
* get access technologies via the various QCDM subsystems or from
* registration state
*/
ctx = g_new0 (AccessTechContext, 1);
ctx->port = mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self));
task = g_task_new (self, NULL, callback, user_data);
g_task_set_task_data (task, ctx, (GDestroyNotify)access_tech_context_free);
if (ctx->port) {
/* Need to open QCDM port as it may be closed/blocked */
if (mm_port_serial_open (MM_PORT_SERIAL (ctx->port), &error)) {
g_object_ref (ctx->port);
mm_dbg ("loading access technologies via QCDM...");
/* FIXME: we may want to run both the CDMA and 3GPP in sequence to ensure
* that a multi-mode device that's in CDMA-mode but still has 3GPP capabilities
* will get the correct access tech, since the 3GPP check is run first.
*/
if (mm_iface_modem_is_3gpp (self)) {
cmd = g_byte_array_sized_new (50);
cmd->len = qcdm_cmd_gsm_subsys_state_info_new ((char *) cmd->data, 50);
g_assert (cmd->len);
mm_port_serial_qcdm_command (ctx->port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_gsm_ready,
task);
g_byte_array_unref (cmd);
return;
}
if (mm_iface_modem_is_cdma (self)) {
cmd = g_byte_array_sized_new (50);
cmd->len = qcdm_cmd_cm_subsys_state_info_new ((char *) cmd->data, 50);
g_assert (cmd->len);
mm_port_serial_qcdm_command (ctx->port,
cmd,
3,
NULL,
(GAsyncReadyCallback)access_tech_qcdm_cdma_ready,
task);
g_byte_array_unref (cmd);
return;
}
g_assert_not_reached ();
}
ctx->port = NULL;
mm_dbg ("Couldn't open QCDM port: %s", error->message);
g_clear_error (&error);
}
/* Fall back if we don't have a QCDM port or it couldn't be opened */
if (mm_iface_modem_is_cdma (self)) {
/* If we don't have a QCDM port but the modem is CDMA-only, then
* guess access technologies from the registration information.
*/
access_tech_from_cdma_registration_state (MM_BROADBAND_MODEM (self), ctx);
g_task_return_pointer (task, access_tech_and_mask_new (ctx), g_free);
} else {
g_task_return_new_error (task,
MM_CORE_ERROR,
MM_CORE_ERROR_UNSUPPORTED,
"Cannot get 3GPP access technology without a QCDM port");
}
g_object_unref (task);
}
/*****************************************************************************/
/* Setup/Cleanup unsolicited events (3GPP interface) */
static gboolean
modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
return g_task_propagate_boolean (G_TASK (res), error);
}
static void
bearer_report_disconnected (MMBaseBearer *bearer,
gpointer user_data)
{
guint cid;
cid = GPOINTER_TO_UINT (user_data);
/* If we're told to disconnect a single context and this is not the
* bearer associated to that context, ignore operation */
if (cid > 0 &&
MM_IS_BROADBAND_BEARER (bearer) &&
mm_broadband_bearer_get_3gpp_cid (MM_BROADBAND_BEARER (bearer)) != cid)
return;
/* If already disconnected, ignore operation */
if (mm_base_bearer_get_status (bearer) == MM_BEARER_STATUS_DISCONNECTED)
return;
mm_info ("Bearer %s: explicitly disconnected", mm_base_bearer_get_path (bearer));
mm_base_bearer_report_connection_status (bearer, MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
}
static void
bearer_list_report_disconnections (MMBroadbandModem *self,
guint cid)
{
MMBearerList *list = NULL;
g_object_get (self,
MM_IFACE_MODEM_BEARER_LIST, &list,
NULL);
/* If empty bearer list, nothing else to do */
if (!list)
return;
mm_bearer_list_foreach (list, (MMBearerListForeachFunc)bearer_report_disconnected, GUINT_TO_POINTER (cid));
g_object_unref (list);
}
static void
cgev_process_detach (MMBroadbandModem *self,
MM3gppCgev type)
{
switch (type) {
case MM_3GPP_CGEV_NW_DETACH:
mm_info ("network forced PS detach: all contexts have been deactivated");
bearer_list_report_disconnections (self, 0);
break;
case MM_3GPP_CGEV_ME_DETACH:
mm_info ("mobile equipment forced PS detach: all contexts have been deactivated");
bearer_list_report_disconnections (self, 0);
break;
default:
g_assert_not_reached ();
}
}
static void
cgev_process_primary (MMBroadbandModem *self,
MM3gppCgev type,
const gchar *str)
{
GError *error = NULL;
guint cid = 0;
if (!mm_3gpp_parse_cgev_indication_primary (str, type, &cid, &error)) {
mm_warn ("couldn't parse cid info from +CGEV indication '%s': %s", str, error->message);
g_error_free (error);
return;
}
switch (type) {
case MM_3GPP_CGEV_NW_ACT_PRIMARY:
mm_info ("network request to activate context (cid %u)", cid);
break;
case MM_3GPP_CGEV_ME_ACT_PRIMARY:
mm_info ("mobile equipment request to activate context (cid %u)", cid);
break;
case MM_3GPP_CGEV_NW_DEACT_PRIMARY:
mm_info ("network request to deactivate context (cid %u)", cid);
bearer_list_report_disconnections (self, cid);
break;
case MM_3GPP_CGEV_ME_DEACT_PRIMARY:
mm_info ("mobile equipment request to deactivate context (cid %u)", cid);
bearer_list_report_disconnections (self, cid);
break;
default:
g_assert_not_reached ();
break;
}
}
static void
cgev_process_secondary (MMBroadbandModem *self,
MM3gppCgev type,
const gchar *str)
{
GError *error = NULL;
guint p_cid = 0;
guint cid = 0;
if (!mm_3gpp_parse_cgev_indication_secondary (str, type, &p_cid, &cid, NULL, &error)) {
mm_warn ("couldn't parse p_cid/cid info from +CGEV indication '%s': %s", str, error->message);
g_error_free (error);
return;
}
switch (type) {
case MM_3GPP_CGEV_NW_ACT_SECONDARY:
mm_info ("network request to activate secondary context (cid %u, primary cid %u)", cid, p_cid);
break;
case MM_3GPP_CGEV_ME_ACT_SECONDARY:
mm_info ("mobile equipment request to activate secondary context (cid %u, primary cid %u)", cid, p_cid);
break;
case MM_3GPP_CGEV_NW_DEACT_SECONDARY:
mm_info ("network request to deactivate secondary context (cid %u, primary cid %u)", cid, p_cid);
bearer_list_report_disconnections (self, cid);
break;
case MM_3GPP_CGEV_ME_DEACT_SECONDARY:
mm_info ("mobile equipment request to deactivate secondary context (cid %u, primary cid %u)", cid, p_cid);
bearer_list_report_disconnections (self, cid);
break;
default:
g_assert_not_reached ();
break;
}
}
static void
cgev_process_pdp (MMBroadbandModem *self,
MM3gppCgev type,
const gchar *str)
{
GError *error = NULL;
gchar *pdp_type = NULL;
gchar *pdp_addr = NULL;
guint cid = 0;
if (!mm_3gpp_parse_cgev_indication_pdp (str, type, &pdp_type, &pdp_addr, &cid, &error)) {
mm_warn ("couldn't parse PDP info from +CGEV indication '%s': %s", str, error->message);
g_error_free (error);
return;
}
switch (type) {
case MM_3GPP_CGEV_REJECT:
mm_info ("network request to activate context (type %s, address %s) has been automatically rejected", pdp_type, pdp_addr);
break;
case MM_3GPP_CGEV_NW_REACT:
/* NOTE: we don't currently notify about automatic reconnections like this one */
if (cid)
mm_info ("network request to reactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
else
mm_info ("network request to reactivate context (type %s, address %s, cid unknown)", pdp_type, pdp_addr);
break;
case MM_3GPP_CGEV_NW_DEACT_PDP:
if (cid) {
mm_info ("network request to deactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
bearer_list_report_disconnections (self, cid);
} else
mm_info ("network request to deactivate context (type %s, address %s, cid unknown)", pdp_type, pdp_addr);
break;
case MM_3GPP_CGEV_ME_DEACT_PDP:
if (cid) {
mm_info ("mobile equipment request to deactivate context (type %s, address %s, cid %u)", pdp_type, pdp_addr, cid);
bearer_list_report_disconnections (self, cid);
} else