The behavior of GRegex changed in 2.73.2 once it was ported from pcre1 to pcre2. In some cases it was made more strict, which is fine, in other cases it exposed some change in how it behaves on certain matches that is not extremely clear whether it's ok or not. See https://gitlab.gnome.org/GNOME/glib/-/issues/2729 See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/601 See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/621 Either way, one thing that was assumed was that initializing all GRegex/GMatchInfo variables to NULL and making sure they're NULL before they're initialized by glib (especially the GMatchInfo) was a good and safer approach. So, whenever possible, g_autoptr() is used to cleanup the allocated GMatchInfo/GRegex variables, and otherwise, g_clear_pointer() is used to ensure that no free/unref is attempted unless the given variable is not NULL, and also so that the variable is reseted to NULL after being disposed.
4733 lines
162 KiB
C
4733 lines
162 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details:
|
|
*
|
|
* Copyright (C) 2008 - 2009 Novell, Inc.
|
|
* Copyright (C) 2009 - 2012 Red Hat, Inc.
|
|
* Copyright (C) 2011 - 2012 Google Inc.
|
|
* Copyright (C) 2012 Huawei Technologies Co., Ltd
|
|
* Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
|
|
* Copyright (C) 2012 - 2019 Aleksander Morgado <aleksander@aleksander.es>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
|
|
#include <ModemManager.h>
|
|
#define _LIBMM_INSIDE_MM
|
|
#include <libmm-glib.h>
|
|
|
|
#include "mm-log-object.h"
|
|
#include "mm-errors-types.h"
|
|
#include "mm-modem-helpers.h"
|
|
#include "mm-modem-helpers-huawei.h"
|
|
#include "mm-base-modem-at.h"
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-iface-modem-3gpp.h"
|
|
#include "mm-iface-modem-3gpp-ussd.h"
|
|
#include "mm-iface-modem-location.h"
|
|
#include "mm-iface-modem-time.h"
|
|
#include "mm-iface-modem-cdma.h"
|
|
#include "mm-iface-modem-signal.h"
|
|
#include "mm-iface-modem-voice.h"
|
|
#include "mm-broadband-modem-huawei.h"
|
|
#include "mm-broadband-bearer-huawei.h"
|
|
#include "mm-broadband-bearer.h"
|
|
#include "mm-bearer-list.h"
|
|
#include "mm-sim-huawei.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_location_init (MMIfaceModemLocation *iface);
|
|
static void iface_modem_cdma_init (MMIfaceModemCdma *iface);
|
|
static void iface_modem_time_init (MMIfaceModemTime *iface);
|
|
static void iface_modem_voice_init (MMIfaceModemVoice *iface);
|
|
static void iface_modem_signal_init (MMIfaceModemSignal *iface);
|
|
|
|
static MMIfaceModem *iface_modem_parent;
|
|
static MMIfaceModem3gpp *iface_modem_3gpp_parent;
|
|
static MMIfaceModemLocation *iface_modem_location_parent;
|
|
static MMIfaceModemCdma *iface_modem_cdma_parent;
|
|
static MMIfaceModemVoice *iface_modem_voice_parent;
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemHuawei, mm_broadband_modem_huawei, 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_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_LOCATION, iface_modem_location_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_TIME, iface_modem_time_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init))
|
|
|
|
typedef enum {
|
|
FEATURE_SUPPORT_UNKNOWN,
|
|
FEATURE_NOT_SUPPORTED,
|
|
FEATURE_SUPPORTED
|
|
} FeatureSupport;
|
|
|
|
typedef struct {
|
|
MMSignal *cdma;
|
|
MMSignal *evdo;
|
|
MMSignal *gsm;
|
|
MMSignal *umts;
|
|
MMSignal *lte;
|
|
MMSignal *nr5g;
|
|
} DetailedSignal;
|
|
|
|
struct _MMBroadbandModemHuaweiPrivate {
|
|
/* Regex for signal quality related notifications */
|
|
GRegex *rssi_regex;
|
|
GRegex *rssilvl_regex;
|
|
GRegex *hrssilvl_regex;
|
|
|
|
/* Regex for access-technology related notifications */
|
|
GRegex *mode_regex;
|
|
|
|
/* Regex for connection status related notifications */
|
|
GRegex *dsflowrpt_regex;
|
|
GRegex *ndisstat_regex;
|
|
|
|
/* Regex for voice management notifications */
|
|
GRegex *orig_regex;
|
|
GRegex *conf_regex;
|
|
GRegex *conn_regex;
|
|
GRegex *cend_regex;
|
|
GRegex *ddtmf_regex;
|
|
|
|
/* Regex to ignore */
|
|
GRegex *boot_regex;
|
|
GRegex *connect_regex;
|
|
GRegex *csnr_regex;
|
|
GRegex *cusatp_regex;
|
|
GRegex *cusatend_regex;
|
|
GRegex *dsdormant_regex;
|
|
GRegex *simst_regex;
|
|
GRegex *srvst_regex;
|
|
GRegex *stin_regex;
|
|
GRegex *hcsq_regex;
|
|
GRegex *pdpdeact_regex;
|
|
GRegex *ndisend_regex;
|
|
GRegex *rfswitch_regex;
|
|
GRegex *position_regex;
|
|
GRegex *posend_regex;
|
|
GRegex *ecclist_regex;
|
|
GRegex *ltersrp_regex;
|
|
GRegex *cschannelinfo_regex;
|
|
GRegex *ccallstate_regex;
|
|
GRegex *eons_regex;
|
|
GRegex *lwurc_regex;
|
|
|
|
FeatureSupport ndisdup_support;
|
|
FeatureSupport rfswitch_support;
|
|
FeatureSupport sysinfoex_support;
|
|
FeatureSupport syscfg_support;
|
|
FeatureSupport syscfgex_support;
|
|
FeatureSupport prefmode_support;
|
|
FeatureSupport time_support;
|
|
FeatureSupport nwtime_support;
|
|
FeatureSupport cvoice_support;
|
|
|
|
MMModemLocationSource enabled_sources;
|
|
|
|
GArray *syscfg_supported_modes;
|
|
GArray *syscfgex_supported_modes;
|
|
GArray *prefmode_supported_modes;
|
|
|
|
DetailedSignal detailed_signal;
|
|
|
|
/* Voice call audio related properties */
|
|
guint audio_hz;
|
|
guint audio_bits;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
GList *
|
|
mm_broadband_modem_huawei_get_at_port_list (MMBroadbandModemHuawei *self)
|
|
{
|
|
GList *out = NULL;
|
|
MMPortSerialAt *port;
|
|
GList *cdc_wdm_at_ports;
|
|
|
|
/* Primary */
|
|
port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
|
|
if (port)
|
|
out = g_list_append (out, port);
|
|
|
|
/* Secondary */
|
|
port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
|
|
if (port)
|
|
out = g_list_append (out, port);
|
|
|
|
/* Additional cdc-wdm ports used for dialing */
|
|
cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),
|
|
MM_PORT_SUBSYS_USBMISC,
|
|
MM_PORT_TYPE_AT);
|
|
|
|
return g_list_concat (out, cdc_wdm_at_ports);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct {
|
|
gboolean extended;
|
|
guint srv_status;
|
|
guint srv_domain;
|
|
guint roam_status;
|
|
guint sim_state;
|
|
guint sys_mode;
|
|
gboolean sys_submode_valid;
|
|
guint sys_submode;
|
|
} SysinfoResult;
|
|
|
|
static gboolean
|
|
sysinfo_finish (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
gboolean *extended,
|
|
guint *srv_status,
|
|
guint *srv_domain,
|
|
guint *roam_status,
|
|
guint *sim_state,
|
|
guint *sys_mode,
|
|
gboolean *sys_submode_valid,
|
|
guint *sys_submode,
|
|
GError **error)
|
|
{
|
|
SysinfoResult *result;
|
|
|
|
result = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
if (extended)
|
|
*extended = result->extended;
|
|
if (srv_status)
|
|
*srv_status = result->srv_status;
|
|
if (srv_domain)
|
|
*srv_domain = result->srv_domain;
|
|
if (roam_status)
|
|
*roam_status = result->roam_status;
|
|
if (sim_state)
|
|
*sim_state = result->sim_state;
|
|
if (sys_mode)
|
|
*sys_mode = result->sys_mode;
|
|
if (sys_submode_valid)
|
|
*sys_submode_valid = result->sys_submode_valid;
|
|
if (sys_submode)
|
|
*sys_submode = result->sys_submode;
|
|
|
|
g_free (result);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
run_sysinfo_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
SysinfoResult *result;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response) {
|
|
mm_obj_dbg (self, "^SYSINFO failed: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
result = g_new0 (SysinfoResult, 1);
|
|
result->extended = FALSE;
|
|
if (!mm_huawei_parse_sysinfo_response (response,
|
|
&result->srv_status,
|
|
&result->srv_domain,
|
|
&result->roam_status,
|
|
&result->sys_mode,
|
|
&result->sim_state,
|
|
&result->sys_submode_valid,
|
|
&result->sys_submode,
|
|
&error)) {
|
|
mm_obj_dbg (self, "^SYSINFO parsing failed: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
g_free (result);
|
|
return;
|
|
}
|
|
|
|
g_task_return_pointer (task, result, g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
run_sysinfo (MMBroadbandModemHuawei *self,
|
|
GTask *task)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SYSINFO",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)run_sysinfo_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
run_sysinfoex_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
SysinfoResult *result;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response) {
|
|
/* First time we try, we fallback to ^SYSINFO */
|
|
if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN) {
|
|
self->priv->sysinfoex_support = FEATURE_NOT_SUPPORTED;
|
|
mm_obj_dbg (self, "^SYSINFOEX failed: %s, assuming unsupported", error->message);
|
|
g_error_free (error);
|
|
run_sysinfo (self, task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, propagate error */
|
|
mm_obj_dbg (self, "^SYSINFOEX failed: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN)
|
|
self->priv->sysinfoex_support = FEATURE_SUPPORTED;
|
|
|
|
result = g_new0 (SysinfoResult, 1);
|
|
result->extended = TRUE;
|
|
if (!mm_huawei_parse_sysinfoex_response (response,
|
|
&result->srv_status,
|
|
&result->srv_domain,
|
|
&result->roam_status,
|
|
&result->sim_state,
|
|
&result->sys_mode,
|
|
&result->sys_submode,
|
|
&error)) {
|
|
mm_obj_dbg (self, "^SYSINFOEX parsing failed: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
g_free (result);
|
|
return;
|
|
}
|
|
|
|
/* Submode from SYSINFOEX always valid */
|
|
result->sys_submode_valid = TRUE;
|
|
g_task_return_pointer (task, result, g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
run_sysinfoex (MMBroadbandModemHuawei *self,
|
|
GTask *task)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SYSINFOEX",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)run_sysinfoex_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
sysinfo (MMBroadbandModemHuawei *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (self->priv->sysinfoex_support == FEATURE_SUPPORT_UNKNOWN ||
|
|
self->priv->sysinfoex_support == FEATURE_SUPPORTED)
|
|
run_sysinfoex (self, task);
|
|
else
|
|
run_sysinfo (self, task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Reset (Modem interface) */
|
|
|
|
static gboolean
|
|
reset_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
reset (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *command;
|
|
|
|
/* Unlike other Huawei modems that support AT^RESET for resetting the modem,
|
|
* Huawei MU736 supports AT^RESET but does not reset the modem upon receiving
|
|
* AT^RESET. It does, however, support resetting itself via AT+CFUN=16.
|
|
*/
|
|
if (g_strcmp0 (mm_iface_modem_get_model (self), "MU736") == 0)
|
|
command = "+CFUN=16";
|
|
else
|
|
command = "^RESET";
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load access technologies (Modem interface) */
|
|
|
|
static MMModemAccessTechnology
|
|
huawei_sysinfo_submode_to_act (guint submode)
|
|
{
|
|
/* new more detailed system mode/access technology */
|
|
switch (submode) {
|
|
case 1:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
|
|
case 2:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
|
|
case 3:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
|
|
case 4:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
|
|
case 5:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
|
|
case 6:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
|
|
case 7:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
|
|
case 8: /* TD-SCDMA */
|
|
break;
|
|
case 9: /* HSPA+ */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
|
|
case 10:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
|
|
case 11:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
|
|
case 12:
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB;
|
|
case 13: /* 1xRTT */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 16: /* 3xRTT */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 17: /* HSPA+ (64QAM) */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
|
|
case 18: /* HSPA+ (MIMO) */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
|
|
static MMModemAccessTechnology
|
|
huawei_sysinfo_mode_to_act (guint mode)
|
|
{
|
|
/* Older, less detailed system mode/access technology */
|
|
switch (mode) {
|
|
case 1: /* AMPS */
|
|
break;
|
|
case 2: /* CDMA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 3: /* GSM/GPRS */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
|
|
case 4: /* HDR */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
|
|
case 5: /* WCDMA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
|
|
case 6: /* GPS */
|
|
break;
|
|
case 7: /* GSM/WCDMA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
|
|
case 8: /* CDMA/HDR hybrid */
|
|
return (MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 | MM_MODEM_ACCESS_TECHNOLOGY_1XRTT);
|
|
case 15: /* TD-SCDMA */
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
|
|
static MMModemAccessTechnology
|
|
huawei_sysinfoex_submode_to_act (guint submode)
|
|
{
|
|
switch (submode) {
|
|
case 1: /* GSM */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
|
|
case 2: /* GPRS */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
|
|
case 3: /* EDGE */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
|
|
|
|
case 21: /* IS95A */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 22: /* IS95B */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 23: /* CDMA2000 1x */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 24: /* EVDO rel0 */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
|
|
case 25: /* EVDO relA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
|
|
case 26: /* EVDO relB */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB;
|
|
case 27: /* Hybrid CDMA2000 1x */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 28: /* Hybrid EVDO rel0 */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
|
|
case 29: /* Hybrid EVDO relA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
|
|
case 30: /* Hybrid EVDO relB */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_EVDOB;
|
|
|
|
case 41: /* WCDMA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
|
|
case 42: /* HSDPA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
|
|
case 43: /* HSUPA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
|
|
case 44: /* HSPA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
|
|
case 45: /* HSPA+ */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
|
|
case 46: /* DC-HSPA+ */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
|
|
|
|
case 61: /* TD-SCDMA */
|
|
break;
|
|
|
|
case 81: /* 802.16e (WiMAX) */
|
|
break;
|
|
|
|
case 101: /* LTE */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_LTE;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
|
|
static MMModemAccessTechnology
|
|
huawei_sysinfoex_mode_to_act (guint mode)
|
|
{
|
|
/* Older, less detailed system mode/access technology */
|
|
switch (mode) {
|
|
case 1: /* GSM */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
|
|
case 2: /* CDMA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
case 3: /* WCDMA */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
|
|
case 4: /* TD-SCDMA */
|
|
break;
|
|
case 5: /* WIMAX */
|
|
break;
|
|
case 6: /* LTE */
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_LTE;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
|
|
static gboolean
|
|
load_access_technologies_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
MMModemAccessTechnology *access_technologies,
|
|
guint *mask,
|
|
GError **error)
|
|
{
|
|
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
gboolean extended = FALSE;
|
|
guint srv_status = 0;
|
|
gboolean sys_submode_valid = FALSE;
|
|
guint sys_submode = 0;
|
|
guint sys_mode = 0;
|
|
|
|
if (!sysinfo_finish (MM_BROADBAND_MODEM_HUAWEI (self),
|
|
res,
|
|
&extended,
|
|
&srv_status,
|
|
NULL, /* srv_domain */
|
|
NULL, /* roam_status */
|
|
NULL, /* sim_state */
|
|
&sys_mode,
|
|
&sys_submode_valid,
|
|
&sys_submode,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (srv_status != 0) {
|
|
/* Valid service */
|
|
if (sys_submode_valid)
|
|
act = (extended ?
|
|
huawei_sysinfoex_submode_to_act (sys_submode) :
|
|
huawei_sysinfo_submode_to_act (sys_submode));
|
|
|
|
if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
|
|
act = (extended ?
|
|
huawei_sysinfoex_mode_to_act (sys_mode) :
|
|
huawei_sysinfo_mode_to_act (sys_mode));
|
|
}
|
|
|
|
*access_technologies = act;
|
|
*mask = MM_MODEM_ACCESS_TECHNOLOGY_ANY;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
load_access_technologies (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
sysinfo (MM_BROADBAND_MODEM_HUAWEI (self), callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load unlock retries (Modem interface) */
|
|
|
|
static MMUnlockRetries *
|
|
load_unlock_retries_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GRegex) r = NULL;
|
|
g_autoptr(GMatchInfo) match_info = NULL;
|
|
MMUnlockRetries *unlock_retries;
|
|
const gchar *result;
|
|
GError *match_error = NULL;
|
|
guint i;
|
|
|
|
MMModemLock locks[4] = {
|
|
MM_MODEM_LOCK_SIM_PUK,
|
|
MM_MODEM_LOCK_SIM_PIN,
|
|
MM_MODEM_LOCK_SIM_PUK2,
|
|
MM_MODEM_LOCK_SIM_PIN2
|
|
};
|
|
|
|
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
if (!result)
|
|
return NULL;
|
|
|
|
r = g_regex_new ("\\^CPIN:\\s*([^,]+),[^,]*,(\\d+),(\\d+),(\\d+),(\\d+)",
|
|
G_REGEX_UNGREEDY, 0, NULL);
|
|
g_assert (r != NULL);
|
|
|
|
if (!g_regex_match_full (r, result, strlen (result), 0, 0, &match_info, &match_error)) {
|
|
if (match_error)
|
|
g_propagate_error (error, match_error);
|
|
else
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse ^CPIN results: Response didn't match (%s)",
|
|
result);
|
|
return NULL;
|
|
}
|
|
|
|
unlock_retries = mm_unlock_retries_new ();
|
|
for (i = 0; i <= 3; i++) {
|
|
guint num;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, i + 2, &num) ||
|
|
num > 10) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse ^CPIN results: "
|
|
"Missing or invalid match info for lock '%s'",
|
|
mm_modem_lock_get_string (locks[i]));
|
|
g_object_unref (unlock_retries);
|
|
unlock_retries = NULL;
|
|
break;
|
|
}
|
|
|
|
mm_unlock_retries_set (unlock_retries, locks[i], num);
|
|
}
|
|
|
|
return unlock_retries;
|
|
}
|
|
|
|
static void
|
|
load_unlock_retries (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^CPIN?",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* After SIM unlock (Modem interface) */
|
|
|
|
static gboolean
|
|
modem_after_sim_unlock_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static gboolean
|
|
after_sim_unlock_wait_cb (GTask *task)
|
|
{
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
modem_after_sim_unlock (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* A 3-second wait is necessary for SIM to become ready, or the firmware may
|
|
* fail miserably and reboot itself */
|
|
g_timeout_add_seconds (3, (GSourceFunc)after_sim_unlock_wait_cb, task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Common band/mode handling code */
|
|
|
|
typedef struct {
|
|
MMModemBand mm;
|
|
guint32 huawei;
|
|
} BandTable;
|
|
|
|
static BandTable bands[] = {
|
|
/* Sort 3G first since it's preferred */
|
|
{ MM_MODEM_BAND_UTRAN_1, 0x00400000 },
|
|
{ MM_MODEM_BAND_UTRAN_2, 0x00800000 },
|
|
{ MM_MODEM_BAND_UTRAN_5, 0x04000000 },
|
|
{ MM_MODEM_BAND_UTRAN_8, 0x00020000 },
|
|
/* 2G second */
|
|
{ MM_MODEM_BAND_G850, 0x00080000 },
|
|
{ MM_MODEM_BAND_DCS, 0x00000080 },
|
|
{ MM_MODEM_BAND_EGSM, 0x00000100 },
|
|
{ MM_MODEM_BAND_PCS, 0x00200000 }
|
|
};
|
|
|
|
static gboolean
|
|
bands_array_to_huawei (GArray *bands_array,
|
|
guint32 *out_huawei)
|
|
{
|
|
guint i;
|
|
|
|
/* Treat ANY as a special case: All huawei flags enabled */
|
|
if (bands_array->len == 1 &&
|
|
g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
|
|
*out_huawei = 0x3FFFFFFF;
|
|
return TRUE;
|
|
}
|
|
|
|
*out_huawei = 0;
|
|
for (i = 0; i < bands_array->len; i++) {
|
|
guint j;
|
|
|
|
for (j = 0; j < G_N_ELEMENTS (bands); j++) {
|
|
if (g_array_index (bands_array, MMModemBand, i) == bands[j].mm)
|
|
*out_huawei |= bands[j].huawei;
|
|
}
|
|
}
|
|
|
|
return (*out_huawei > 0 ? TRUE : FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
huawei_to_bands_array (guint32 huawei,
|
|
GArray **bands_array,
|
|
GError **error)
|
|
{
|
|
guint i;
|
|
|
|
*bands_array = NULL;
|
|
for (i = 0; i < G_N_ELEMENTS (bands); i++) {
|
|
if (huawei & bands[i].huawei) {
|
|
if (G_UNLIKELY (!*bands_array))
|
|
*bands_array = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
|
|
g_array_append_val (*bands_array, bands[i].mm);
|
|
}
|
|
}
|
|
|
|
if (!*bands_array) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't build bands array from '%u'",
|
|
huawei);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_syscfg (const gchar *response,
|
|
GArray **bands_array,
|
|
GError **error)
|
|
{
|
|
gint mode;
|
|
gint acquisition_order;
|
|
guint32 band;
|
|
gint roaming;
|
|
gint srv_domain;
|
|
|
|
if (!response ||
|
|
strncmp (response, "^SYSCFG:", 8) != 0 ||
|
|
!sscanf (response + 8, "%d,%d,%x,%d,%d", &mode, &acquisition_order, &band, &roaming, &srv_domain)) {
|
|
/* Dump error to upper layer */
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Unexpected SYSCFG response: '%s'",
|
|
response);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Band */
|
|
if (bands_array &&
|
|
!huawei_to_bands_array (band, bands_array, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load current bands (Modem interface) */
|
|
|
|
static GArray *
|
|
load_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
const gchar *response;
|
|
GArray *bands_array = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
if (!response)
|
|
return NULL;
|
|
|
|
if (!parse_syscfg (response, &bands_array, error))
|
|
return NULL;
|
|
|
|
return bands_array;
|
|
}
|
|
|
|
static void
|
|
load_current_bands (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SYSCFG?",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set current bands (Modem interface) */
|
|
|
|
static gboolean
|
|
set_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
syscfg_set_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
|
|
/* Let the error be critical */
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
set_current_bands (MMIfaceModem *self,
|
|
GArray *bands_array,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
gchar *cmd;
|
|
guint32 huawei_band = 0x3FFFFFFF;
|
|
gchar *bands_string;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands_array->data,
|
|
bands_array->len);
|
|
|
|
if (!bands_array_to_huawei (bands_array, &huawei_band)) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Invalid bands requested: '%s'",
|
|
bands_string);
|
|
g_object_unref (task);
|
|
g_free (bands_string);
|
|
return;
|
|
}
|
|
|
|
cmd = g_strdup_printf ("AT^SYSCFG=16,3,%X,2,4", huawei_band);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)syscfg_set_ready,
|
|
task);
|
|
g_free (cmd);
|
|
g_free (bands_string);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* 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
|
|
syscfg_test_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (response) {
|
|
/* There are 2G+3G Huawei modems out there which support mode switching with
|
|
* AT^SYSCFG, but fail to provide a valid response for AT^SYSCFG=? (they just
|
|
* return an empty string). So handle that case by providing a default response
|
|
* string to get parsed. Ugly, ugly, blame Huawei.
|
|
*/
|
|
if (response[0])
|
|
self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (response, self, &error);
|
|
else {
|
|
self->priv->syscfg_supported_modes = mm_huawei_parse_syscfg_test (MM_HUAWEI_DEFAULT_SYSCFG_FMT, self, NULL);
|
|
g_assert (self->priv->syscfg_supported_modes != NULL);
|
|
}
|
|
}
|
|
|
|
if (self->priv->syscfg_supported_modes) {
|
|
MMModemModeCombination mode;
|
|
guint i;
|
|
GArray *combinations;
|
|
|
|
/* Build list of combinations */
|
|
combinations = g_array_sized_new (FALSE,
|
|
FALSE,
|
|
sizeof (MMModemModeCombination),
|
|
self->priv->syscfg_supported_modes->len);
|
|
for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) {
|
|
MMHuaweiSyscfgCombination *huawei_mode;
|
|
|
|
huawei_mode = &g_array_index (self->priv->syscfg_supported_modes,
|
|
MMHuaweiSyscfgCombination,
|
|
i);
|
|
mode.allowed = huawei_mode->allowed;
|
|
mode.preferred = huawei_mode->preferred;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
|
|
self->priv->syscfg_support = FEATURE_SUPPORTED;
|
|
g_task_return_pointer (task,
|
|
combinations,
|
|
(GDestroyNotify)g_array_unref);
|
|
} else {
|
|
mm_obj_dbg (self, "error while checking ^SYSCFG format: %s", error->message);
|
|
/* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */
|
|
if (!g_error_matches (error,
|
|
MM_MOBILE_EQUIPMENT_ERROR,
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN))
|
|
self->priv->syscfg_support = FEATURE_NOT_SUPPORTED;
|
|
g_task_return_error (task, error);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
syscfgex_test_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (response)
|
|
self->priv->syscfgex_supported_modes = mm_huawei_parse_syscfgex_test (response, &error);
|
|
|
|
if (self->priv->syscfgex_supported_modes) {
|
|
MMModemModeCombination mode;
|
|
guint i;
|
|
GArray *combinations;
|
|
|
|
/* Build list of combinations */
|
|
combinations = g_array_sized_new (FALSE,
|
|
FALSE,
|
|
sizeof (MMModemModeCombination),
|
|
self->priv->syscfgex_supported_modes->len);
|
|
for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) {
|
|
MMHuaweiSyscfgexCombination *huawei_mode;
|
|
|
|
huawei_mode = &g_array_index (self->priv->syscfgex_supported_modes,
|
|
MMHuaweiSyscfgexCombination,
|
|
i);
|
|
mode.allowed = huawei_mode->allowed;
|
|
mode.preferred = huawei_mode->preferred;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
|
|
self->priv->syscfgex_support = FEATURE_SUPPORTED;
|
|
|
|
g_task_return_pointer (task,
|
|
combinations,
|
|
(GDestroyNotify)g_array_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */
|
|
if (error) {
|
|
mm_obj_dbg (self, "error while checking ^SYSCFGEX format: %s", error->message);
|
|
if (g_error_matches (error,
|
|
MM_MOBILE_EQUIPMENT_ERROR,
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
g_error_free (error);
|
|
}
|
|
|
|
self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED;
|
|
|
|
/* Try with SYSCFG */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SYSCFG=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)syscfg_test_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
prefmode_test_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (response)
|
|
self->priv->prefmode_supported_modes = mm_huawei_parse_prefmode_test (response, self, &error);
|
|
|
|
if (self->priv->prefmode_supported_modes) {
|
|
MMModemModeCombination mode;
|
|
guint i;
|
|
GArray *combinations;
|
|
|
|
/* Build list of combinations */
|
|
combinations = g_array_sized_new (FALSE,
|
|
FALSE,
|
|
sizeof (MMModemModeCombination),
|
|
self->priv->prefmode_supported_modes->len);
|
|
for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) {
|
|
MMHuaweiPrefmodeCombination *huawei_mode;
|
|
|
|
huawei_mode = &g_array_index (self->priv->prefmode_supported_modes,
|
|
MMHuaweiPrefmodeCombination,
|
|
i);
|
|
mode.allowed = huawei_mode->allowed;
|
|
mode.preferred = huawei_mode->preferred;
|
|
g_array_append_val (combinations, mode);
|
|
}
|
|
|
|
self->priv->prefmode_support = FEATURE_SUPPORTED;
|
|
g_task_return_pointer (task,
|
|
combinations,
|
|
(GDestroyNotify)g_array_unref);
|
|
} else {
|
|
mm_obj_dbg (self, "error while checking ^PREFMODE format: %s", error->message);
|
|
/* If SIM-PIN error, don't mark as feature unsupported; we'll retry later */
|
|
if (!g_error_matches (error,
|
|
MM_MOBILE_EQUIPMENT_ERROR,
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN))
|
|
self->priv->prefmode_support = FEATURE_NOT_SUPPORTED;
|
|
g_task_return_error (task, error);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_supported_modes (MMIfaceModem *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (mm_iface_modem_is_cdma_only (_self)) {
|
|
/* ^PREFMODE only in CDMA-only modems */
|
|
self->priv->syscfg_support = FEATURE_NOT_SUPPORTED;
|
|
self->priv->syscfgex_support = FEATURE_NOT_SUPPORTED;
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^PREFMODE=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)prefmode_test_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Check SYSCFGEX */
|
|
self->priv->prefmode_support = FEATURE_NOT_SUPPORTED;
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^SYSCFGEX=?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)syscfgex_test_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load initial allowed/preferred modes (Modem interface) */
|
|
|
|
static gboolean
|
|
load_current_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
MMModemMode *allowed,
|
|
MMModemMode *preferred,
|
|
GError **error)
|
|
{
|
|
MMModemModeCombination *out;
|
|
|
|
out = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!out)
|
|
return FALSE;
|
|
|
|
*allowed = out->allowed;
|
|
*preferred = out->preferred;
|
|
|
|
g_free (out);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
prefmode_load_current_modes_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
const MMHuaweiPrefmodeCombination *current = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (response)
|
|
current = mm_huawei_parse_prefmode_response (response,
|
|
self->priv->prefmode_supported_modes,
|
|
&error);
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else {
|
|
MMModemModeCombination *out;
|
|
|
|
out = g_new (MMModemModeCombination, 1);
|
|
out->allowed = current->allowed;
|
|
out->preferred = current->preferred;
|
|
g_task_return_pointer (task, out, g_free);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
syscfg_load_current_modes_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
const MMHuaweiSyscfgCombination *current = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (response)
|
|
current = mm_huawei_parse_syscfg_response (response,
|
|
self->priv->syscfg_supported_modes,
|
|
&error);
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else {
|
|
MMModemModeCombination *out;
|
|
|
|
out = g_new (MMModemModeCombination, 1);
|
|
out->allowed = current->allowed;
|
|
out->preferred = current->preferred;
|
|
g_task_return_pointer (task, out, g_free);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
syscfgex_load_current_modes_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
const MMHuaweiSyscfgexCombination *current = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (response)
|
|
current = mm_huawei_parse_syscfgex_response (response,
|
|
self->priv->syscfgex_supported_modes,
|
|
&error);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else {
|
|
MMModemModeCombination *out;
|
|
|
|
out = g_new (MMModemModeCombination, 1);
|
|
out->allowed = current->allowed;
|
|
out->preferred = current->preferred;
|
|
g_task_return_pointer (task, out, g_free);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_current_modes (MMIfaceModem *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (self->priv->syscfgex_support == FEATURE_SUPPORTED) {
|
|
g_assert (self->priv->syscfgex_supported_modes != NULL);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"^SYSCFGEX?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)syscfgex_load_current_modes_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->syscfg_support == FEATURE_SUPPORTED) {
|
|
g_assert (self->priv->syscfg_supported_modes != NULL);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"^SYSCFG?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)syscfg_load_current_modes_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->prefmode_support == FEATURE_SUPPORTED) {
|
|
g_assert (self->priv->prefmode_supported_modes != NULL);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"^PREFMODE?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)prefmode_load_current_modes_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Unable to load current modes");
|
|
g_object_unref (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_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
|
|
if (error)
|
|
/* Let the error be critical. */
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
prefmode_set_current_modes (MMBroadbandModemHuawei *self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GTask *task,
|
|
GError **error)
|
|
{
|
|
guint i;
|
|
MMHuaweiPrefmodeCombination *found = NULL;
|
|
gchar *command;
|
|
|
|
for (i = 0; i < self->priv->prefmode_supported_modes->len; i++) {
|
|
MMHuaweiPrefmodeCombination *single;
|
|
|
|
single = &g_array_index (self->priv->prefmode_supported_modes,
|
|
MMHuaweiPrefmodeCombination,
|
|
i);
|
|
if (single->allowed == allowed && single->preferred == preferred) {
|
|
found = single;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_NOT_FOUND,
|
|
"Requested mode ^PREFMODE combination not found");
|
|
return FALSE;
|
|
}
|
|
|
|
command = g_strdup_printf ("^PREFMODE=%u", found->prefmode);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_current_modes_ready,
|
|
task);
|
|
g_free (command);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
syscfg_set_current_modes (MMBroadbandModemHuawei *self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GTask *task,
|
|
GError **error)
|
|
{
|
|
guint i;
|
|
MMHuaweiSyscfgCombination *found = NULL;
|
|
gchar *command;
|
|
|
|
for (i = 0; i < self->priv->syscfg_supported_modes->len; i++) {
|
|
MMHuaweiSyscfgCombination *single;
|
|
|
|
single = &g_array_index (self->priv->syscfg_supported_modes,
|
|
MMHuaweiSyscfgCombination,
|
|
i);
|
|
if (single->allowed == allowed && single->preferred == preferred) {
|
|
found = single;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_NOT_FOUND,
|
|
"Requested mode ^SYSCFG combination not found");
|
|
return FALSE;
|
|
}
|
|
|
|
command = g_strdup_printf ("^SYSCFG=%u,%u,40000000,2,4",
|
|
found->mode,
|
|
found->acqorder);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_current_modes_ready,
|
|
task);
|
|
g_free (command);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
syscfgex_set_current_modes (MMBroadbandModemHuawei *self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GTask *task,
|
|
GError **error)
|
|
{
|
|
guint i;
|
|
MMHuaweiSyscfgexCombination *found = NULL;
|
|
gchar *command;
|
|
|
|
for (i = 0; i < self->priv->syscfgex_supported_modes->len; i++) {
|
|
MMHuaweiSyscfgexCombination *single;
|
|
|
|
single = &g_array_index (self->priv->syscfgex_supported_modes,
|
|
MMHuaweiSyscfgexCombination,
|
|
i);
|
|
if (single->allowed == allowed && single->preferred == preferred) {
|
|
found = single;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_NOT_FOUND,
|
|
"Requested mode ^SYSCFGEX combination not found");
|
|
return FALSE;
|
|
}
|
|
|
|
command = g_strdup_printf ("^SYSCFGEX=\"%s\",3fffffff,2,4,7fffffffffffffff,,",
|
|
found->mode_str);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)set_current_modes_ready,
|
|
task);
|
|
g_free (command);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
set_current_modes (MMIfaceModem *_self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (self->priv->syscfgex_support == FEATURE_SUPPORTED)
|
|
syscfgex_set_current_modes (self, allowed, preferred, task, &error);
|
|
else if (self->priv->syscfg_support == FEATURE_SUPPORTED)
|
|
syscfg_set_current_modes (self, allowed, preferred, task, &error);
|
|
else if (self->priv->prefmode_support == FEATURE_SUPPORTED)
|
|
prefmode_set_current_modes (self, allowed, preferred, task, &error);
|
|
else
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Setting current modes is not supported");
|
|
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup/Cleanup unsolicited events (3GPP interface) */
|
|
|
|
static void
|
|
huawei_signal_changed (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
guint quality = 0;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &quality))
|
|
return;
|
|
|
|
if (quality == 99) {
|
|
/* 99 means unknown */
|
|
quality = 0;
|
|
} else {
|
|
/* Normalize the quality */
|
|
quality = MM_CLAMP_HIGH (quality, 31) * 100 / 31;
|
|
}
|
|
|
|
mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
|
|
}
|
|
|
|
static void
|
|
huawei_mode_changed (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
gchar *str;
|
|
gint a;
|
|
guint32 mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
|
|
str = g_match_info_fetch (match_info, 1);
|
|
a = atoi (str);
|
|
g_free (str);
|
|
|
|
/* CDMA/EVDO devices may not send this */
|
|
str = g_match_info_fetch (match_info, 2);
|
|
if (str[0])
|
|
act = huawei_sysinfo_submode_to_act (atoi (str));
|
|
g_free (str);
|
|
|
|
switch (a) {
|
|
case 3:
|
|
/* GSM/GPRS mode */
|
|
if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
|
|
(act < MM_MODEM_ACCESS_TECHNOLOGY_GSM ||
|
|
act > MM_MODEM_ACCESS_TECHNOLOGY_EDGE)) {
|
|
str = mm_modem_access_technology_build_string_from_mask (act);
|
|
mm_obj_warn (self, "unexpected access technology (%s) in GSM/GPRS mode", str);
|
|
g_free (str);
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
|
|
break;
|
|
|
|
case 5:
|
|
/* WCDMA mode */
|
|
if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
|
|
(act < MM_MODEM_ACCESS_TECHNOLOGY_UMTS ||
|
|
act > MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS)) {
|
|
str = mm_modem_access_technology_build_string_from_mask (act);
|
|
mm_obj_warn (self, "unexpected access technology (%s) in WCDMA mode", str);
|
|
g_free (str);
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
|
|
break;
|
|
|
|
case 2:
|
|
/* CDMA mode */
|
|
if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
|
|
act != MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) {
|
|
str = mm_modem_access_technology_build_string_from_mask (act);
|
|
mm_obj_warn (self, "unexpected access technology (%s) in CDMA mode", str);
|
|
g_free (str);
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
|
|
mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
|
|
break;
|
|
|
|
case 4: /* HDR mode */
|
|
case 8: /* CDMA/HDR hybrid mode */
|
|
if (act != MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN &&
|
|
(act < MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 ||
|
|
act > MM_MODEM_ACCESS_TECHNOLOGY_EVDOB)) {
|
|
str = mm_modem_access_technology_build_string_from_mask (act);
|
|
mm_obj_warn (self, "unexpected access technology (%s) in EVDO mode", str);
|
|
g_free (str);
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
if (act == MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN)
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
|
|
mask = MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK;
|
|
break;
|
|
|
|
case 0:
|
|
act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
break;
|
|
|
|
default:
|
|
mm_obj_warn (self, "unexpected mode change value reported: '%d'", a);
|
|
return;
|
|
}
|
|
|
|
mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self), act, mask);
|
|
}
|
|
|
|
static void
|
|
huawei_status_changed (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
gchar *str;
|
|
gint n1, n2, n3, n4, n5, n6, n7;
|
|
|
|
str = g_match_info_fetch (match_info, 1);
|
|
if (sscanf (str, "%x,%x,%x,%x,%x,%x,%x", &n1, &n2, &n3, &n4, &n5, &n6, &n7))
|
|
mm_obj_dbg (self, "duration: %d up: %d Kbps down: %d Kbps total: %d total: %d\n",
|
|
n1, n2 * 8 / 1000, n3 * 8 / 1000, n4 / 1024, n5 / 1024);
|
|
g_free (str);
|
|
}
|
|
|
|
typedef struct {
|
|
gboolean ipv4_available;
|
|
gboolean ipv4_connected;
|
|
gboolean ipv6_available;
|
|
gboolean ipv6_connected;
|
|
} NdisstatResult;
|
|
|
|
static void
|
|
bearer_report_connection_status (MMBaseBearer *bearer,
|
|
NdisstatResult *ndisstat_result)
|
|
{
|
|
if (ndisstat_result->ipv4_available) {
|
|
/* TODO: MMBroadbandBearerHuawei does not currently support IPv6.
|
|
* When it does, we should check the IP family associated with each bearer. */
|
|
mm_base_bearer_report_connection_status (bearer,
|
|
ndisstat_result->ipv4_connected ?
|
|
MM_BEARER_CONNECTION_STATUS_CONNECTED :
|
|
MM_BEARER_CONNECTION_STATUS_DISCONNECTED);
|
|
}
|
|
}
|
|
|
|
static void
|
|
huawei_ndisstat_changed (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
gchar *str;
|
|
NdisstatResult ndisstat_result;
|
|
GError *error = NULL;
|
|
MMBearerList *list = NULL;
|
|
|
|
str = g_match_info_fetch (match_info, 1);
|
|
if (!mm_huawei_parse_ndisstatqry_response (str,
|
|
&ndisstat_result.ipv4_available,
|
|
&ndisstat_result.ipv4_connected,
|
|
&ndisstat_result.ipv6_available,
|
|
&ndisstat_result.ipv6_connected,
|
|
&error)) {
|
|
mm_obj_dbg (self, "ignored invalid ^NDISSTAT unsolicited message '%s': %s",
|
|
str, error->message);
|
|
g_error_free (error);
|
|
g_free (str);
|
|
return;
|
|
}
|
|
g_free (str);
|
|
|
|
mm_obj_dbg (self, "NDIS status: IPv4 %s, IPv6 %s",
|
|
ndisstat_result.ipv4_available ?
|
|
(ndisstat_result.ipv4_connected ? "connected" : "disconnected") : "not available",
|
|
ndisstat_result.ipv6_available ?
|
|
(ndisstat_result.ipv6_connected ? "connected" : "disconnected") : "not available");
|
|
|
|
/* If empty bearer list, nothing else to do */
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_BEARER_LIST, &list,
|
|
NULL);
|
|
if (!list)
|
|
return;
|
|
|
|
mm_bearer_list_foreach (list,
|
|
(MMBearerListForeachFunc)bearer_report_connection_status,
|
|
&ndisstat_result);
|
|
|
|
g_object_unref (list);
|
|
}
|
|
|
|
static void
|
|
detailed_signal_clear (DetailedSignal *signal)
|
|
{
|
|
g_clear_object (&signal->cdma);
|
|
g_clear_object (&signal->evdo);
|
|
g_clear_object (&signal->gsm);
|
|
g_clear_object (&signal->umts);
|
|
g_clear_object (&signal->lte);
|
|
}
|
|
|
|
static gboolean
|
|
get_rssi_dbm (guint rssi, gdouble *out_val)
|
|
{
|
|
if (rssi <= 96) {
|
|
*out_val = (double) (-121.0 + rssi);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
get_ecio_db (guint ecio, gdouble *out_val)
|
|
{
|
|
if (ecio <= 65) {
|
|
*out_val = -32.5 + ((double) ecio / 2.0);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
get_rsrp_dbm (guint rsrp, gdouble *out_val)
|
|
{
|
|
if (rsrp <= 97) {
|
|
*out_val = (double) (-141.0 + rsrp);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
get_sinr_db (guint sinr, gdouble *out_val)
|
|
{
|
|
if (sinr <= 251) {
|
|
*out_val = -20.2 + (double) (sinr / 5.0);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
get_rsrq_db (guint rsrq, gdouble *out_val)
|
|
{
|
|
if (rsrq <= 34) {
|
|
*out_val = -20 + (double) (rsrq / 2.0);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
huawei_hcsq_changed (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
gchar *str;
|
|
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
|
guint value1 = 0;
|
|
guint value2 = 0;
|
|
guint value3 = 0;
|
|
guint value4 = 0;
|
|
guint value5 = 0;
|
|
gdouble v;
|
|
GError *error = NULL;
|
|
|
|
str = g_match_info_fetch (match_info, 1);
|
|
if (!mm_huawei_parse_hcsq_response (str,
|
|
&act,
|
|
&value1,
|
|
&value2,
|
|
&value3,
|
|
&value4,
|
|
&value5,
|
|
&error)) {
|
|
mm_obj_dbg (self, "ignored invalid ^HCSQ message '%s': %s", str, error->message);
|
|
g_error_free (error);
|
|
g_free (str);
|
|
return;
|
|
}
|
|
g_free (str);
|
|
|
|
detailed_signal_clear (&self->priv->detailed_signal);
|
|
|
|
/* 2G */
|
|
if (act == MM_MODEM_ACCESS_TECHNOLOGY_GSM) {
|
|
self->priv->detailed_signal.gsm = mm_signal_new ();
|
|
/* value1: gsm_rssi */
|
|
if (get_rssi_dbm (value1, &v))
|
|
mm_signal_set_rssi (self->priv->detailed_signal.gsm, v);
|
|
return;
|
|
}
|
|
|
|
/* 3G */
|
|
if (act == MM_MODEM_ACCESS_TECHNOLOGY_UMTS) {
|
|
self->priv->detailed_signal.umts = mm_signal_new ();
|
|
/* value1: wcdma_rssi */
|
|
if (get_rssi_dbm (value1, &v))
|
|
mm_signal_set_rssi (self->priv->detailed_signal.umts, v);
|
|
/* value2: wcdma_rscp; unused */
|
|
/* value3: wcdma_ecio */
|
|
if (get_ecio_db (value3, &v))
|
|
mm_signal_set_ecio (self->priv->detailed_signal.umts, v);
|
|
return;
|
|
}
|
|
|
|
/* 4G */
|
|
if (act == MM_MODEM_ACCESS_TECHNOLOGY_LTE) {
|
|
self->priv->detailed_signal.lte = mm_signal_new ();
|
|
/* value1: lte_rssi */
|
|
if (get_rssi_dbm (value1, &v))
|
|
mm_signal_set_rssi (self->priv->detailed_signal.lte, v);
|
|
/* value2: lte_rsrp */
|
|
if (get_rsrp_dbm (value2, &v))
|
|
mm_signal_set_rsrp (self->priv->detailed_signal.lte, v);
|
|
/* value3: lte_sinr -> SNR? */
|
|
if (get_sinr_db (value3, &v))
|
|
mm_signal_set_snr (self->priv->detailed_signal.lte, v);
|
|
/* value4: lte_rsrq */
|
|
if (get_rsrq_db (value4, &v))
|
|
mm_signal_set_rsrq (self->priv->detailed_signal.lte, v);
|
|
return;
|
|
}
|
|
|
|
/* CDMA and EVDO not yet supported */
|
|
}
|
|
|
|
static void
|
|
set_3gpp_unsolicited_events_handlers (MMBroadbandModemHuawei *self,
|
|
gboolean enable)
|
|
{
|
|
GList *ports, *l;
|
|
|
|
ports = mm_broadband_modem_huawei_get_at_port_list (self);
|
|
|
|
/* Enable/disable unsolicited events in given port */
|
|
for (l = ports; l; l = g_list_next (l)) {
|
|
MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
|
|
|
|
/* Signal quality related */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->rssi_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_signal_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
|
|
/* Access technology related */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->mode_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
|
|
/* Connection status related */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->dsflowrpt_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_status_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->ndisstat_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_ndisstat_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->hcsq_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_hcsq_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
}
|
|
|
|
g_list_free_full (ports, g_object_unref);
|
|
}
|
|
|
|
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_3gpp_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_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (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_3gpp_setup_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
parent_3gpp_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_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
|
|
|
|
/* And now chain up parent's cleanup */
|
|
iface_modem_3gpp_parent->cleanup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_3gpp_cleanup_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enabling 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
|
|
own_enable_unsolicited_events_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_sequence_full_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 const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
|
|
/* With ^PORTSEL we specify whether we want the PCUI port (0) or the
|
|
* modem port (1) to receive the unsolicited messages */
|
|
{ "^PORTSEL=0", 5, FALSE, NULL },
|
|
{ "^CURC=1", 3, FALSE, NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
parent_enable_unsolicited_events_ready (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_3gpp_parent->enable_unsolicited_events_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/* Our own enable now */
|
|
mm_base_modem_at_sequence_full (
|
|
MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
unsolicited_enable_sequence,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)own_enable_unsolicited_events_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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disabling 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)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_3gpp_parent->disable_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
|
|
own_disable_unsolicited_events_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_full_finish (self, res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Next, chain up parent's disable */
|
|
iface_modem_3gpp_parent->disable_unsolicited_events (
|
|
MM_IFACE_MODEM_3GPP (self),
|
|
(GAsyncReadyCallback)parent_disable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Our own disable first */
|
|
mm_base_modem_at_command_full (
|
|
MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
"^CURC=0",
|
|
5,
|
|
FALSE, /* allow_cached */
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)own_disable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Create Bearer (Modem interface) */
|
|
|
|
static MMBaseBearer *
|
|
huawei_modem_create_bearer_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
broadband_bearer_huawei_new_ready (GObject *source,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBaseBearer *bearer;
|
|
GError *error = NULL;
|
|
|
|
bearer = mm_broadband_bearer_huawei_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 *source,
|
|
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
|
|
create_bearer_for_net_port (GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self;
|
|
MMBearerProperties *properties;
|
|
|
|
self = g_task_get_source_object (task);
|
|
properties = g_task_get_task_data (task);
|
|
|
|
switch (self->priv->ndisdup_support) {
|
|
case FEATURE_NOT_SUPPORTED:
|
|
mm_obj_dbg (self, "^NDISDUP not supported, creating default bearer...");
|
|
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
|
|
properties,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_new_ready,
|
|
task);
|
|
return;
|
|
case FEATURE_SUPPORTED:
|
|
mm_obj_dbg (self, "^NDISDUP supported, creating huawei bearer...");
|
|
mm_broadband_bearer_huawei_new (MM_BROADBAND_MODEM_HUAWEI (self),
|
|
properties,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_huawei_new_ready,
|
|
task);
|
|
return;
|
|
case FEATURE_SUPPORT_UNKNOWN:
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static MMPortSerialAt *
|
|
peek_port_at_for_data (MMBroadbandModemHuawei *self,
|
|
MMPort *port)
|
|
{
|
|
GList *cdc_wdm_at_ports, *l;
|
|
const gchar *net_port_parent_path;
|
|
MMPortSerialAt *found = NULL;
|
|
|
|
g_warn_if_fail (mm_port_get_subsys (port) == MM_PORT_SUBSYS_NET);
|
|
net_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (port));
|
|
if (!net_port_parent_path) {
|
|
mm_obj_warn (self, "no parent path for net port %s", mm_port_get_device (port));
|
|
return NULL;
|
|
}
|
|
|
|
/* Find the CDC-WDM port on the same USB interface as the given net port */
|
|
cdc_wdm_at_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),
|
|
MM_PORT_SUBSYS_USBMISC,
|
|
MM_PORT_TYPE_AT);
|
|
for (l = cdc_wdm_at_ports; l && !found; l = g_list_next (l)) {
|
|
const gchar *wdm_port_parent_path;
|
|
|
|
g_assert (MM_IS_PORT_SERIAL_AT (l->data));
|
|
wdm_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (MM_PORT (l->data)));
|
|
if (wdm_port_parent_path && g_str_equal (wdm_port_parent_path, net_port_parent_path))
|
|
found = MM_PORT_SERIAL_AT (l->data);
|
|
}
|
|
|
|
g_list_free_full (cdc_wdm_at_ports, g_object_unref);
|
|
return found;
|
|
}
|
|
|
|
|
|
MMPortSerialAt *
|
|
mm_broadband_modem_huawei_peek_port_at_for_data (MMBroadbandModemHuawei *self,
|
|
MMPort *port)
|
|
{
|
|
MMPortSerialAt *found;
|
|
|
|
g_assert (self->priv->ndisdup_support == FEATURE_SUPPORTED);
|
|
|
|
found = peek_port_at_for_data (self, port);
|
|
if (!found)
|
|
mm_obj_dbg (self, "couldn't find associated cdc-wdm port for %s", mm_port_get_device (port));
|
|
return found;
|
|
}
|
|
|
|
static void
|
|
ensure_ndisdup_support_checked (MMBroadbandModemHuawei *self,
|
|
MMPort *port)
|
|
{
|
|
/* Check NDISDUP support the first time we need it */
|
|
if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN)
|
|
return;
|
|
|
|
/* First, check for devices which support NDISDUP on any AT port. These
|
|
* devices are tagged by udev */
|
|
if (mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_HUAWEI_NDISDUP_SUPPORTED")) {
|
|
mm_obj_dbg (self, "^NDISDUP is supported");
|
|
self->priv->ndisdup_support = FEATURE_SUPPORTED;
|
|
}
|
|
/* Then, look for devices which have both a net port and a cdc-wdm
|
|
* AT-capable port. We assume that these devices allow NDISDUP only
|
|
* when issued in the cdc-wdm port. */
|
|
else if (peek_port_at_for_data (self, port)) {
|
|
mm_obj_dbg (self, "^NDISDUP is supported on non-serial AT port");
|
|
self->priv->ndisdup_support = FEATURE_SUPPORTED;
|
|
}
|
|
|
|
if (self->priv->ndisdup_support != FEATURE_SUPPORT_UNKNOWN)
|
|
return;
|
|
|
|
mm_obj_dbg (self, "^NDISDUP is not supported");
|
|
self->priv->ndisdup_support = FEATURE_NOT_SUPPORTED;
|
|
}
|
|
|
|
static void
|
|
huawei_modem_create_bearer (MMIfaceModem *self,
|
|
MMBearerProperties *properties,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
MMPort *port;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, g_object_ref (properties), g_object_unref);
|
|
|
|
port = mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET);
|
|
if (port) {
|
|
ensure_ndisdup_support_checked (MM_BROADBAND_MODEM_HUAWEI (self), port);
|
|
create_bearer_for_net_port (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "creating default bearer...");
|
|
mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
|
|
properties,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)broadband_bearer_new_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* USSD encode/decode (3GPP-USSD interface)
|
|
*
|
|
* Huawei devices don't use the current charset (as per AT+CSCS) in the CUSD
|
|
* command, they instead expect data encoded in GSM-7 already, given as a
|
|
* hex string.
|
|
*/
|
|
|
|
static gchar *
|
|
encode (MMIfaceModem3gppUssd *self,
|
|
const gchar *command,
|
|
guint *scheme,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) gsm = NULL;
|
|
g_autofree guint8 *packed = NULL;
|
|
guint32 packed_len = 0;
|
|
|
|
gsm = mm_modem_charset_bytearray_from_utf8 (command, MM_MODEM_CHARSET_GSM, FALSE, error);
|
|
if (!gsm)
|
|
return NULL;
|
|
|
|
*scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
|
|
|
|
/* If command is a multiple of 7 characters long, Huawei firmwares
|
|
* apparently want that padded. Maybe all modems?
|
|
*/
|
|
if (gsm->len % 7 == 0) {
|
|
static const guint8 padding = 0x0d;
|
|
|
|
g_byte_array_append (gsm, &padding, 1);
|
|
}
|
|
|
|
packed = mm_charset_gsm_pack (gsm->data, gsm->len, 0, &packed_len);
|
|
return mm_utils_bin2hexstr (packed, packed_len);
|
|
}
|
|
|
|
static gchar *
|
|
decode (MMIfaceModem3gppUssd *self,
|
|
const gchar *reply,
|
|
GError **error)
|
|
{
|
|
g_autofree guint8 *bin = NULL;
|
|
gsize bin_len = 0;
|
|
g_autofree guint8 *unpacked = NULL;
|
|
guint32 unpacked_len;
|
|
g_autoptr(GByteArray) unpacked_array = NULL;
|
|
|
|
bin = mm_utils_hexstr2bin (reply, -1, &bin_len, error);
|
|
if (!bin)
|
|
return NULL;
|
|
|
|
unpacked = mm_charset_gsm_unpack (bin, (bin_len * 8) / 7, 0, &unpacked_len);
|
|
/* if the last character in a 7-byte block is padding, then drop it */
|
|
if ((bin_len % 7 == 0) && (unpacked[unpacked_len - 1] == 0x0d))
|
|
unpacked_len--;
|
|
|
|
unpacked_array = g_byte_array_sized_new (unpacked_len);
|
|
g_byte_array_append (unpacked_array, unpacked, unpacked_len);
|
|
|
|
return mm_modem_charset_bytearray_to_utf8 (unpacked_array, MM_MODEM_CHARSET_GSM, FALSE, error);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
huawei_1x_signal_changed (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
guint quality = 0;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &quality))
|
|
return;
|
|
|
|
quality = MM_CLAMP_HIGH (quality, 100);
|
|
mm_obj_dbg (self, "1X signal quality: %u", quality);
|
|
mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
|
|
}
|
|
|
|
static void
|
|
huawei_evdo_signal_changed (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
guint quality = 0;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &quality))
|
|
return;
|
|
|
|
quality = MM_CLAMP_HIGH (quality, 100);
|
|
mm_obj_dbg (self, "EVDO signal quality: %u", quality);
|
|
mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);
|
|
}
|
|
|
|
/* Signal quality loading (Modem interface) */
|
|
|
|
static guint
|
|
modem_load_signal_quality_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 0;
|
|
}
|
|
return (guint)value;
|
|
}
|
|
|
|
static void
|
|
parent_load_signal_quality_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
guint signal_quality;
|
|
|
|
signal_quality = iface_modem_parent->load_signal_quality_finish (self, res, &error);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_int (task, signal_quality);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
signal_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response, *command;
|
|
gchar buf[5];
|
|
guint quality = 0, i = 0;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, NULL);
|
|
if (!response) {
|
|
/* Fallback to parent's method */
|
|
iface_modem_parent->load_signal_quality (
|
|
MM_IFACE_MODEM (self),
|
|
(GAsyncReadyCallback)parent_load_signal_quality_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
command = g_task_get_task_data (task);
|
|
g_assert (command);
|
|
response = mm_strip_tag (response, command);
|
|
/* 'command' won't include the trailing ':' in the response, so strip that */
|
|
while ((*response == ':') || isspace (*response))
|
|
response++;
|
|
|
|
/* Sanitize response for mm_get_uint_from_str() which wants only digits */
|
|
memset (buf, 0, sizeof (buf));
|
|
while (i < (sizeof (buf) - 1) && isdigit (*response))
|
|
buf[i++] = *response++;
|
|
|
|
if (mm_get_uint_from_str (buf, &quality)) {
|
|
quality = MM_CLAMP_HIGH (quality, 100);
|
|
g_task_return_int (task, quality);
|
|
} else {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't parse %s response: '%s'",
|
|
command, response);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_load_signal_quality (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
|
|
const char *command = "^CSQLVL";
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* 3GPP modems can just run parent's signal quality loading */
|
|
if (mm_iface_modem_is_3gpp (self)) {
|
|
iface_modem_parent->load_signal_quality (
|
|
self,
|
|
(GAsyncReadyCallback)parent_load_signal_quality_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* CDMA modems need custom signal quality loading */
|
|
|
|
g_object_get (G_OBJECT (self),
|
|
MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, &evdo_state,
|
|
NULL);
|
|
if (evdo_state > MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN)
|
|
command = "^HDRCSQLVL";
|
|
|
|
g_task_set_task_data (task, g_strdup (command), g_free);
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)signal_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup/Cleanup unsolicited events (CDMA interface) */
|
|
|
|
static void
|
|
set_cdma_unsolicited_events_handlers (MMBroadbandModemHuawei *self,
|
|
gboolean enable)
|
|
{
|
|
GList *ports, *l;
|
|
|
|
ports = mm_broadband_modem_huawei_get_at_port_list (self);
|
|
|
|
/* Enable/disable unsolicited events in given port */
|
|
for (l = ports; l; l = g_list_next (l)) {
|
|
MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
|
|
|
|
/* Signal quality related */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->rssilvl_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_1x_signal_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->hrssilvl_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_evdo_signal_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
/* Access technology related */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->mode_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)huawei_mode_changed : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
}
|
|
|
|
g_list_free_full (ports, g_object_unref);
|
|
}
|
|
|
|
static gboolean
|
|
modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_cdma_setup_unsolicited_events_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_cdma_parent->setup_unsolicited_events_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else {
|
|
/* Our own setup now */
|
|
set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Chain up parent's setup if needed */
|
|
if (iface_modem_cdma_parent->setup_unsolicited_events &&
|
|
iface_modem_cdma_parent->setup_unsolicited_events_finish) {
|
|
iface_modem_cdma_parent->setup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_cdma_setup_unsolicited_events_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise just run our setup and complete */
|
|
set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
parent_cdma_cleanup_unsolicited_events_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_cdma_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_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Our own cleanup first */
|
|
set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
|
|
|
|
/* Chain up parent's setup if needed */
|
|
if (iface_modem_cdma_parent->cleanup_unsolicited_events &&
|
|
iface_modem_cdma_parent->cleanup_unsolicited_events_finish) {
|
|
iface_modem_cdma_parent->cleanup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_cdma_cleanup_unsolicited_events_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise we're done */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup registration checks (CDMA interface) */
|
|
|
|
typedef struct {
|
|
gboolean skip_qcdm_call_manager_step;
|
|
gboolean skip_qcdm_hdr_step;
|
|
gboolean skip_at_cdma_service_status_step;
|
|
gboolean skip_at_cdma1x_serving_system_step;
|
|
gboolean skip_detailed_registration_state;
|
|
} SetupRegistrationChecksResults;
|
|
|
|
static gboolean
|
|
setup_registration_checks_finish (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
gboolean *skip_qcdm_call_manager_step,
|
|
gboolean *skip_qcdm_hdr_step,
|
|
gboolean *skip_at_cdma_service_status_step,
|
|
gboolean *skip_at_cdma1x_serving_system_step,
|
|
gboolean *skip_detailed_registration_state,
|
|
GError **error)
|
|
{
|
|
SetupRegistrationChecksResults *results;
|
|
|
|
results = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!results)
|
|
return FALSE;
|
|
|
|
*skip_qcdm_call_manager_step = results->skip_qcdm_call_manager_step;
|
|
*skip_qcdm_hdr_step = results->skip_qcdm_hdr_step;
|
|
*skip_at_cdma_service_status_step = results->skip_at_cdma_service_status_step;
|
|
*skip_at_cdma1x_serving_system_step = results->skip_at_cdma1x_serving_system_step;
|
|
*skip_detailed_registration_state = results->skip_detailed_registration_state;
|
|
g_free (results);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
parent_setup_registration_checks_ready (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetupRegistrationChecksResults *results;
|
|
GError *error = NULL;
|
|
|
|
results = g_new0 (SetupRegistrationChecksResults, 1);
|
|
|
|
if (!iface_modem_cdma_parent->setup_registration_checks_finish (self,
|
|
res,
|
|
&results->skip_qcdm_call_manager_step,
|
|
&results->skip_qcdm_hdr_step,
|
|
&results->skip_at_cdma_service_status_step,
|
|
&results->skip_at_cdma1x_serving_system_step,
|
|
&results->skip_detailed_registration_state,
|
|
&error)) {
|
|
g_free (results);
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
gboolean evdo_supported = FALSE;
|
|
|
|
g_object_get (self,
|
|
MM_IFACE_MODEM_CDMA_EVDO_NETWORK_SUPPORTED, &evdo_supported,
|
|
NULL);
|
|
|
|
/* Don't use AT+CSS on EVDO-capable hardware for determining registration
|
|
* status, because often the device will have only an EVDO connection and
|
|
* AT+CSS won't necessarily report EVDO registration status, only 1X.
|
|
*/
|
|
if (evdo_supported)
|
|
results->skip_at_cdma1x_serving_system_step = TRUE;
|
|
|
|
/* Force to always use the detailed registration checks, as we have
|
|
* ^SYSINFO for that */
|
|
results->skip_detailed_registration_state = FALSE;
|
|
|
|
g_task_return_pointer (task, results, g_free);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
setup_registration_checks (MMIfaceModemCdma *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Run parent's checks first */
|
|
iface_modem_cdma_parent->setup_registration_checks (self,
|
|
(GAsyncReadyCallback)parent_setup_registration_checks_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Detailed registration state (CDMA interface) */
|
|
|
|
typedef struct {
|
|
MMModemCdmaRegistrationState detailed_cdma1x_state;
|
|
MMModemCdmaRegistrationState detailed_evdo_state;
|
|
} DetailedRegistrationStateResults;
|
|
|
|
typedef struct {
|
|
DetailedRegistrationStateResults state;
|
|
} DetailedRegistrationStateContext;
|
|
|
|
static gboolean
|
|
get_detailed_registration_state_finish (MMIfaceModemCdma *self,
|
|
GAsyncResult *res,
|
|
MMModemCdmaRegistrationState *detailed_cdma1x_state,
|
|
MMModemCdmaRegistrationState *detailed_evdo_state,
|
|
GError **error)
|
|
{
|
|
DetailedRegistrationStateResults *results;
|
|
|
|
results = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!results)
|
|
return FALSE;
|
|
|
|
*detailed_cdma1x_state = results->detailed_cdma1x_state;
|
|
*detailed_evdo_state = results->detailed_evdo_state;
|
|
g_free (results);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
registration_state_sysinfo_ready (MMBroadbandModemHuawei *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
DetailedRegistrationStateContext *ctx;
|
|
gboolean extended = FALSE;
|
|
guint srv_status = 0;
|
|
guint sys_mode = 0;
|
|
guint roam_status = 0;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!sysinfo_finish (self,
|
|
res,
|
|
&extended,
|
|
&srv_status,
|
|
NULL, /* srv_domain */
|
|
&roam_status,
|
|
NULL, /* sim_state */
|
|
&sys_mode,
|
|
NULL, /* sys_submode_valid */
|
|
NULL, /* sys_submode */
|
|
NULL)) {
|
|
/* If error, leave superclass' reg state alone if ^SYSINFO isn't supported. */
|
|
g_task_return_pointer (task,
|
|
g_memdup (&ctx->state, sizeof (ctx->state)),
|
|
g_free);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (srv_status == 2) {
|
|
MMModemCdmaRegistrationState reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
|
|
MMModemAccessTechnology act;
|
|
gboolean cdma1x = FALSE;
|
|
gboolean evdo = FALSE;
|
|
|
|
/* Service available, check roaming state */
|
|
if (roam_status == 0)
|
|
reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME;
|
|
else if (roam_status == 1)
|
|
reg_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING;
|
|
|
|
/* Check service type */
|
|
act = (extended ?
|
|
huawei_sysinfoex_mode_to_act (sys_mode):
|
|
huawei_sysinfo_mode_to_act (sys_mode));
|
|
|
|
if (act & MM_MODEM_ACCESS_TECHNOLOGY_1XRTT) {
|
|
cdma1x = TRUE;
|
|
ctx->state.detailed_cdma1x_state = reg_state;
|
|
}
|
|
|
|
if (act & MM_MODEM_ACCESS_TECHNOLOGY_EVDO0 ||
|
|
act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOA ||
|
|
act & MM_MODEM_ACCESS_TECHNOLOGY_EVDOB) {
|
|
evdo = TRUE;
|
|
ctx->state.detailed_evdo_state = reg_state;
|
|
}
|
|
|
|
if (!cdma1x && !evdo) {
|
|
/* Say we're registered to something even though sysmode parsing failed */
|
|
mm_obj_dbg (self, "assuming registered at least in CDMA1x");
|
|
ctx->state.detailed_cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
|
|
}
|
|
}
|
|
|
|
g_task_return_pointer (task,
|
|
g_memdup (&ctx->state, sizeof (ctx->state)),
|
|
g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
get_detailed_registration_state (MMIfaceModemCdma *self,
|
|
MMModemCdmaRegistrationState cdma1x_state,
|
|
MMModemCdmaRegistrationState evdo_state,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
DetailedRegistrationStateContext *ctx;
|
|
GTask *task;
|
|
|
|
/* Setup context */
|
|
ctx = g_new (DetailedRegistrationStateContext, 1);
|
|
ctx->state.detailed_cdma1x_state = cdma1x_state;
|
|
ctx->state.detailed_evdo_state = evdo_state;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, g_free);
|
|
|
|
sysinfo (MM_BROADBAND_MODEM_HUAWEI (self),
|
|
(GAsyncReadyCallback)registration_state_sysinfo_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check if Voice supported (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_check_support_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
voice_parent_check_support_ready (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
gboolean parent_support;
|
|
|
|
parent_support = iface_modem_voice_parent->check_support_finish (self, res, NULL);
|
|
g_task_return_boolean (task, parent_support);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
cvoice_check_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (!response ||
|
|
!mm_huawei_parse_cvoice_response (response,
|
|
&self->priv->audio_hz,
|
|
&self->priv->audio_bits,
|
|
&error)) {
|
|
self->priv->cvoice_support = FEATURE_NOT_SUPPORTED;
|
|
mm_obj_dbg (self, "CVOICE is unsupported: %s", error->message);
|
|
g_clear_error (&error);
|
|
|
|
/* Now check generic support */
|
|
iface_modem_voice_parent->check_support (MM_IFACE_MODEM_VOICE (self),
|
|
(GAsyncReadyCallback)voice_parent_check_support_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "CVOICE is supported");
|
|
self->priv->cvoice_support = FEATURE_SUPPORTED;
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_check_support (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
/* Check for Huawei-specific ^CVOICE support */
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^CVOICE?",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)cvoice_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* In-call audio channel setup/cleanup */
|
|
|
|
static gboolean
|
|
modem_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
modem_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* If there is no CVOICE support, no custom audio setup required
|
|
* (i.e. audio path is externally managed) */
|
|
if (self->priv->cvoice_support == FEATURE_SUPPORTED) {
|
|
MMPort *port;
|
|
|
|
/* The QCDM port, if present, switches back from voice to QCDM after
|
|
* the voice call is dropped. */
|
|
port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self)));
|
|
if (port) {
|
|
/* During a voice call, we'll set the QCDM port as connected, and that
|
|
* will make us ignore all incoming data and avoid sending any outgoing
|
|
* data. */
|
|
mm_port_set_connected (port, FALSE);
|
|
}
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
modem_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *_self,
|
|
GAsyncResult *res,
|
|
MMPort **audio_port,
|
|
MMCallAudioFormat **audio_format,
|
|
GError **error)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (res), error))
|
|
return FALSE;
|
|
|
|
if (self->priv->cvoice_support == FEATURE_SUPPORTED) {
|
|
MMPort *port;
|
|
|
|
/* Setup audio format */
|
|
if (audio_format) {
|
|
gchar *resolution_str;
|
|
|
|
resolution_str = g_strdup_printf ("s%ule", self->priv->audio_bits);
|
|
*audio_format = mm_call_audio_format_new ();
|
|
mm_call_audio_format_set_encoding (*audio_format, "pcm");
|
|
mm_call_audio_format_set_resolution (*audio_format, resolution_str);
|
|
mm_call_audio_format_set_rate (*audio_format, self->priv->audio_hz);
|
|
g_free (resolution_str);
|
|
}
|
|
|
|
/* The QCDM port, if present, switches from QCDM to voice while
|
|
* a voice call is active. */
|
|
port = MM_PORT (mm_base_modem_peek_port_qcdm (MM_BASE_MODEM (self)));
|
|
if (port) {
|
|
/* During a voice call, we'll set the QCDM port as connected, and that
|
|
* will make us ignore all incoming data and avoid sending any outgoing
|
|
* data. */
|
|
mm_port_set_connected (port, TRUE);
|
|
}
|
|
|
|
if (audio_port)
|
|
*audio_port = (port ? g_object_ref (port) : NULL);;
|
|
} else {
|
|
if (audio_format)
|
|
*audio_format = NULL;
|
|
if (audio_port)
|
|
*audio_port = NULL;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
ddsetex_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_setup_in_call_audio_channel (MMIfaceModemVoice *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* If there is no CVOICE support, no custom audio setup required
|
|
* (i.e. audio path is externally managed) */
|
|
if (self->priv->cvoice_support != FEATURE_SUPPORTED) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Enable audio streaming on the audio port */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^DDSETEX=2",
|
|
5,
|
|
FALSE,
|
|
(GAsyncReadyCallback)ddsetex_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Common setup/cleanup voice unsolicited events */
|
|
|
|
typedef enum {
|
|
HUAWEI_CALL_TYPE_VOICE = 0,
|
|
HUAWEI_CALL_TYPE_CS_DATA = 1,
|
|
HUAWEI_CALL_TYPE_PS_DATA = 2,
|
|
HUAWEI_CALL_TYPE_CDMA_SMS = 3,
|
|
HUAWEI_CALL_TYPE_OTA_STANDARD_OTASP = 7,
|
|
HUAWEI_CALL_TYPE_OTA_NON_STANDARD_OTASP = 8,
|
|
HUAWEI_CALL_TYPE_EMERGENCY = 9,
|
|
} HuaweiCallType;
|
|
|
|
static void
|
|
orig_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
MMCallInfo call_info = { 0 };
|
|
guint aux = 0;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 2, &aux)) {
|
|
mm_obj_warn (self, "couldn't parse call type from ^ORIG");
|
|
return;
|
|
}
|
|
if (aux != HUAWEI_CALL_TYPE_VOICE && aux != HUAWEI_CALL_TYPE_EMERGENCY) {
|
|
mm_obj_dbg (self, "ignored ^ORIG for non-voice call");
|
|
return;
|
|
}
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
|
|
mm_obj_warn (self, "couldn't parse call index from ^ORIG");
|
|
return;
|
|
}
|
|
call_info.index = aux;
|
|
call_info.state = MM_CALL_STATE_DIALING;
|
|
call_info.direction = MM_CALL_DIRECTION_OUTGOING;
|
|
|
|
mm_obj_dbg (self, "call %u state updated: dialing", call_info.index);
|
|
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
}
|
|
|
|
static void
|
|
conf_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
MMCallInfo call_info = { 0 };
|
|
guint aux = 0;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
|
|
mm_obj_warn (self, "couldn't parse call index from ^CONF");
|
|
return;
|
|
}
|
|
call_info.index = aux;
|
|
call_info.state = MM_CALL_STATE_RINGING_OUT;
|
|
call_info.direction = MM_CALL_DIRECTION_OUTGOING;
|
|
|
|
mm_obj_dbg (self, "call %u state updated: ringing-out", call_info.index);
|
|
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
}
|
|
|
|
static void
|
|
conn_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
MMCallInfo call_info = { 0 };
|
|
guint aux = 0;
|
|
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
|
|
mm_obj_warn (self, "couldn't parse call index from ^CONN");
|
|
return;
|
|
}
|
|
call_info.index = aux;
|
|
call_info.state = MM_CALL_STATE_ACTIVE;
|
|
call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
|
|
|
|
mm_obj_dbg (self, "call %u state updated: active", aux);
|
|
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
}
|
|
|
|
static void
|
|
cend_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
MMCallInfo call_info = { 0 };
|
|
guint aux = 0;
|
|
|
|
/* only index is mandatory */
|
|
if (!mm_get_uint_from_match_info (match_info, 1, &aux)) {
|
|
mm_obj_warn (self, "couldn't parse call index from ^CEND");
|
|
return;
|
|
}
|
|
call_info.index = aux;
|
|
call_info.state = MM_CALL_STATE_TERMINATED;
|
|
call_info.direction = MM_CALL_DIRECTION_UNKNOWN;
|
|
|
|
mm_obj_dbg (self, "call %u state updated: terminated", call_info.index);
|
|
if (mm_get_uint_from_match_info (match_info, 2, &aux))
|
|
mm_obj_dbg (self, " call duration: %u seconds", aux);
|
|
if (mm_get_uint_from_match_info (match_info, 3, &aux))
|
|
mm_obj_dbg (self, " end status code: %u", aux);
|
|
if (mm_get_uint_from_match_info (match_info, 4, &aux))
|
|
mm_obj_dbg (self, " call control cause: %u", aux);
|
|
|
|
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info);
|
|
}
|
|
|
|
static void
|
|
ddtmf_received (MMPortSerialAt *port,
|
|
GMatchInfo *match_info,
|
|
MMBroadbandModemHuawei *self)
|
|
{
|
|
gchar *dtmf;
|
|
|
|
dtmf = g_match_info_fetch (match_info, 1);
|
|
mm_obj_dbg (self, "received DTMF: %s", dtmf);
|
|
/* call index unknown */
|
|
mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf);
|
|
g_free (dtmf);
|
|
}
|
|
|
|
static void
|
|
common_voice_setup_cleanup_unsolicited_events (MMBroadbandModemHuawei *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));
|
|
|
|
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->orig_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)orig_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
|
|
self->priv->conf_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)conf_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
|
|
self->priv->conn_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)conn_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
|
|
self->priv->cend_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)cend_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (ports[i],
|
|
self->priv->ddtmf_regex,
|
|
enable ? (MMPortSerialAtUnsolicitedMsgFn)ddtmf_received : NULL,
|
|
enable ? self : NULL,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup unsolicited events (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Our own setup now */
|
|
common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), TRUE);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_setup_unsolicited_events (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Chain up parent's setup */
|
|
iface_modem_voice_parent->setup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Cleanup unsolicited events (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_voice_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_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* cleanup our own */
|
|
common_voice_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
|
|
|
|
/* Chain up parent's cleanup */
|
|
iface_modem_voice_parent->cleanup_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enabling unsolicited events (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
own_voice_enable_unsolicited_events_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_sequence_full_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 const MMBaseModemAtCommand unsolicited_voice_enable_sequence[] = {
|
|
/* With ^DDTMFCFG we active the DTMF Decoder */
|
|
{ "^DDTMFCFG=0,1", 3, FALSE, NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Our own enable now */
|
|
mm_base_modem_at_sequence_full (
|
|
MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
unsolicited_voice_enable_sequence,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)own_voice_enable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_enable_unsolicited_events (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Chain up parent's enable */
|
|
iface_modem_voice_parent->enable_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disabling unsolicited events (Voice interface) */
|
|
|
|
static gboolean
|
|
modem_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
own_voice_disable_unsolicited_events_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_sequence_full_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 const MMBaseModemAtCommand unsolicited_voice_disable_sequence[] = {
|
|
/* With ^DDTMFCFG we deactivate the DTMF Decoder */
|
|
{ "^DDTMFCFG=1,0", 3, FALSE, NULL },
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* our own disable now */
|
|
|
|
mm_base_modem_at_sequence_full (
|
|
MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
unsolicited_voice_disable_sequence,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)own_voice_disable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
modem_voice_disable_unsolicited_events (MMIfaceModemVoice *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Chain up parent's disable */
|
|
iface_modem_voice_parent->disable_unsolicited_events (
|
|
self,
|
|
(GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Create call (Voice interface) */
|
|
|
|
static MMBaseCall *
|
|
create_call (MMIfaceModemVoice *self,
|
|
MMCallDirection direction,
|
|
const gchar *number)
|
|
{
|
|
return mm_base_call_new (MM_BASE_MODEM (self),
|
|
direction,
|
|
number,
|
|
TRUE, /* skip_incoming_timeout */
|
|
TRUE, /* supports_dialing_to_ringing */
|
|
TRUE); /* supports_ringing_to_active) */
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load network time (Time interface) */
|
|
|
|
static MMNetworkTimezone *
|
|
modem_time_load_network_timezone_finish (MMIfaceModemTime *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
MMNetworkTimezone *tz = NULL;
|
|
const gchar *response;
|
|
|
|
g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED ||
|
|
self->priv->time_support == FEATURE_SUPPORTED);
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
|
|
if (!response)
|
|
return NULL;
|
|
|
|
if (self->priv->nwtime_support == FEATURE_SUPPORTED)
|
|
mm_huawei_parse_nwtime_response (response, NULL, &tz, error);
|
|
else if (self->priv->time_support == FEATURE_SUPPORTED)
|
|
mm_huawei_parse_time_response (response, NULL, &tz, error);
|
|
return tz;
|
|
}
|
|
|
|
|
|
static gchar *
|
|
modem_time_load_network_time_finish (MMIfaceModemTime *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
const gchar *response;
|
|
gchar *iso8601 = NULL;
|
|
|
|
g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED ||
|
|
self->priv->time_support == FEATURE_SUPPORTED);
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
|
|
if (!response)
|
|
return NULL;
|
|
|
|
if (self->priv->nwtime_support == FEATURE_SUPPORTED)
|
|
mm_huawei_parse_nwtime_response (response, &iso8601, NULL, error);
|
|
else if (self->priv->time_support == FEATURE_SUPPORTED)
|
|
mm_huawei_parse_time_response (response, &iso8601, NULL, error);
|
|
return iso8601;
|
|
}
|
|
|
|
static void
|
|
modem_time_load_network_time_or_zone (MMIfaceModemTime *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
const char *command = NULL;
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
|
|
if (self->priv->nwtime_support == FEATURE_SUPPORTED)
|
|
command = "^NWTIME?";
|
|
else if (self->priv->time_support == FEATURE_SUPPORTED)
|
|
command = "^TIME";
|
|
|
|
g_assert (command != NULL);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Power state loading (Modem interface) */
|
|
|
|
static void
|
|
enable_disable_unsolicited_rfswitch_event_handler (MMBroadbandModemHuawei *self,
|
|
gboolean enable)
|
|
{
|
|
GList *ports, *l;
|
|
|
|
ports = mm_broadband_modem_huawei_get_at_port_list (self);
|
|
|
|
mm_obj_dbg (self, "%s ^RFSWITCH unsolicited event handler",
|
|
enable ? "enable" : "disable");
|
|
|
|
for (l = ports; l; l = g_list_next (l)) {
|
|
MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
|
|
|
|
mm_port_serial_at_enable_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->rfswitch_regex,
|
|
enable);
|
|
}
|
|
|
|
g_list_free_full (ports, g_object_unref);
|
|
}
|
|
|
|
static void
|
|
parent_load_power_state_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
MMModemPowerState power_state;
|
|
|
|
power_state = iface_modem_parent->load_power_state_finish (self, res, &error);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else {
|
|
/* As modem_power_down uses +CFUN=0 to put the modem in low state, we treat
|
|
* CFUN 0 as 'LOW' power state instead of 'OFF'. Otherwise, MMIfaceModem
|
|
* would prevent the modem from transitioning back to the 'ON' power state. */
|
|
if (power_state == MM_MODEM_POWER_STATE_OFF)
|
|
power_state = MM_MODEM_POWER_STATE_LOW;
|
|
|
|
g_task_return_int (task, power_state);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
huawei_rfswitch_check_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
gint sw_state;
|
|
|
|
enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self),
|
|
TRUE /* enable */);
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (response) {
|
|
response = mm_strip_tag (response, "^RFSWITCH:");
|
|
if (sscanf (response, "%d", &sw_state) != 1 ||
|
|
(sw_state != 0 && sw_state != 1)) {
|
|
mm_obj_warn (self, "couldn't parse ^RFSWITCH response '%s'", response);
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't parse ^RFSWITCH response '%s'",
|
|
response);
|
|
}
|
|
}
|
|
|
|
if (self->priv->rfswitch_support == FEATURE_SUPPORT_UNKNOWN) {
|
|
if (error) {
|
|
mm_obj_dbg (self, "^RFSWITCH is not supported");
|
|
self->priv->rfswitch_support = FEATURE_NOT_SUPPORTED;
|
|
g_error_free (error);
|
|
/* Fall back to parent's load_power_state */
|
|
iface_modem_parent->load_power_state (MM_IFACE_MODEM (self),
|
|
(GAsyncReadyCallback)parent_load_power_state_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "^RFSWITCH is supported");
|
|
self->priv->rfswitch_support = FEATURE_SUPPORTED;
|
|
}
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_int (task,
|
|
sw_state ? MM_MODEM_POWER_STATE_ON : MM_MODEM_POWER_STATE_LOW);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static MMModemPowerState
|
|
load_power_state_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_POWER_STATE_UNKNOWN;
|
|
}
|
|
return (MMModemPowerState)value;
|
|
}
|
|
|
|
static void
|
|
load_power_state (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) {
|
|
case FEATURE_SUPPORT_UNKNOWN:
|
|
case FEATURE_SUPPORTED: {
|
|
/* Temporarily disable the unsolicited ^RFSWITCH event handler in order to
|
|
* prevent it from discarding the response to the ^RFSWITCH? command.
|
|
* It will be re-enabled in huawei_rfswitch_check_ready.
|
|
*/
|
|
enable_disable_unsolicited_rfswitch_event_handler (MM_BROADBAND_MODEM_HUAWEI (self),
|
|
FALSE /* enable */);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^RFSWITCH?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)huawei_rfswitch_check_ready,
|
|
task);
|
|
break;
|
|
}
|
|
case FEATURE_NOT_SUPPORTED:
|
|
/* Run parent's load_power_state */
|
|
iface_modem_parent->load_power_state (self,
|
|
(GAsyncReadyCallback)parent_load_power_state_ready,
|
|
task);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Modem power up (Modem interface) */
|
|
|
|
static gboolean
|
|
huawei_modem_power_up_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
huawei_modem_power_up (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) {
|
|
case FEATURE_NOT_SUPPORTED:
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CFUN=1",
|
|
30,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
break;
|
|
case FEATURE_SUPPORTED:
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^RFSWITCH=1",
|
|
30,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
break;
|
|
case FEATURE_SUPPORT_UNKNOWN:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Modem power down (Modem interface) */
|
|
|
|
static gboolean
|
|
huawei_modem_power_down_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
static void
|
|
huawei_modem_power_down (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
switch (MM_BROADBAND_MODEM_HUAWEI (self)->priv->rfswitch_support) {
|
|
case FEATURE_NOT_SUPPORTED:
|
|
/* +CFUN=0 is supported on all Huawei modems but +CFUN=4 isn't,
|
|
* thus we use +CFUN=0 to put the modem in low power state. */
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CFUN=0",
|
|
30,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
break;
|
|
case FEATURE_SUPPORTED:
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^RFSWITCH=0",
|
|
30,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
break;
|
|
case FEATURE_SUPPORT_UNKNOWN:
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Create SIM (Modem interface) */
|
|
|
|
static MMBaseSim *
|
|
huawei_modem_create_sim_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return mm_sim_huawei_new_finish (res, error);
|
|
}
|
|
|
|
static void
|
|
huawei_modem_create_sim (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
/* New Sierra SIM */
|
|
mm_sim_huawei_new (MM_BASE_MODEM (self),
|
|
NULL, /* cancellable */
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Location capabilities loading (Location interface) */
|
|
|
|
static MMModemLocationSource
|
|
location_load_capabilities_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_LOCATION_SOURCE_NONE;
|
|
}
|
|
return (MMModemLocationSource)value;
|
|
}
|
|
|
|
static void
|
|
parent_load_capabilities_ready (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource sources;
|
|
GError *error = NULL;
|
|
|
|
sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* not sure how to check if GPS is supported, just allow it */
|
|
if (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)))
|
|
sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED);
|
|
|
|
/* So we're done, complete */
|
|
g_task_return_int (task, sources);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
location_load_capabilities (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Chain up parent's setup */
|
|
iface_modem_location_parent->load_capabilities (self,
|
|
(GAsyncReadyCallback)parent_load_capabilities_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disable location gathering (Location interface) */
|
|
|
|
static gboolean
|
|
disable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
gps_disabled_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_at_command_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
disable_location_gathering (MMIfaceModemLocation *_self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
|
|
/* NOTE: no parent disable_location_gathering() implementation */
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
self->priv->enabled_sources &= ~source;
|
|
|
|
/* Only stop GPS engine if no GPS-related sources enabled */
|
|
if ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) &&
|
|
!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED))) {
|
|
MMPortSerialGps *gps_port;
|
|
|
|
/* Close the data port if we don't need it anymore */
|
|
if (source & (MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) {
|
|
gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
|
|
if (gps_port)
|
|
mm_port_serial_close (MM_PORT_SERIAL (gps_port));
|
|
}
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (_self),
|
|
"^WPEND",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)gps_disabled_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* For any other location (e.g. 3GPP), or if still some GPS needed, just return */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable location gathering (Location interface) */
|
|
|
|
static const MMBaseModemAtCommand gps_startup[] = {
|
|
{ "^WPDOM=0", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
|
|
{ "^WPDST=1", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
|
|
{ "^WPDFR=65535,30", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
|
|
{ "^WPDGP", 3, FALSE, mm_base_modem_response_processor_no_result_continue },
|
|
{ NULL }
|
|
};
|
|
|
|
static gboolean
|
|
enable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
gps_startup_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
MMModemLocationSource source;
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_sequence_finish (_self, res, NULL, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
/* Only open the GPS port in NMEA/RAW setups */
|
|
if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
|
|
MMPortSerialGps *gps_port;
|
|
|
|
gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
|
|
if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) {
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't open raw GPS serial port");
|
|
} else {
|
|
/* GPS port was successfully opened */
|
|
self->priv->enabled_sources |= source;
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
} else {
|
|
/* No need to open GPS port */
|
|
self->priv->enabled_sources |= source;
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
parent_enable_location_gathering_ready (MMIfaceModemLocation *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GError *error = NULL;
|
|
MMModemLocationSource source;
|
|
gboolean start_gps = FALSE;
|
|
|
|
if (!iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Now our own enabling */
|
|
|
|
source = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
/* Only start GPS engine if not done already */
|
|
start_gps = ((source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) &&
|
|
!(self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)));
|
|
|
|
if (start_gps) {
|
|
mm_base_modem_at_sequence (
|
|
MM_BASE_MODEM (self),
|
|
gps_startup,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
(GAsyncReadyCallback)gps_startup_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* For any other location (e.g. 3GPP), or if GPS already running just return */
|
|
self->priv->enabled_sources |= source;
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
enable_location_gathering (MMIfaceModemLocation *self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
|
|
|
|
/* Chain up parent's gathering enable */
|
|
iface_modem_location_parent->enable_location_gathering (self,
|
|
source,
|
|
(GAsyncReadyCallback)parent_enable_location_gathering_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check support (Time interface) */
|
|
|
|
static gboolean
|
|
modem_time_check_support_finish (MMIfaceModemTime *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
modem_time_check_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
|
|
/* Responses are checked in the sequence parser, ignore overall result */
|
|
mm_base_modem_at_sequence_finish (_self, res, NULL, NULL);
|
|
|
|
g_task_return_boolean (task,
|
|
(self->priv->nwtime_support == FEATURE_SUPPORTED ||
|
|
self->priv->time_support == FEATURE_SUPPORTED));
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static MMBaseModemAtResponseProcessorResult
|
|
modem_check_time_reply (MMBaseModem *_self,
|
|
gpointer none,
|
|
const gchar *command,
|
|
const gchar *response,
|
|
gboolean last_command,
|
|
const GError *error,
|
|
GVariant **result,
|
|
GError **result_error)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
|
|
if (!error) {
|
|
if (strstr (response, "^NTCT"))
|
|
self->priv->nwtime_support = FEATURE_SUPPORTED;
|
|
else if (strstr (response, "^TIME"))
|
|
self->priv->time_support = FEATURE_SUPPORTED;
|
|
} else {
|
|
if (strstr (command, "^NTCT"))
|
|
self->priv->nwtime_support = FEATURE_NOT_SUPPORTED;
|
|
else if (strstr (command, "^TIME"))
|
|
self->priv->time_support = FEATURE_NOT_SUPPORTED;
|
|
}
|
|
|
|
*result = NULL;
|
|
*result_error = NULL;
|
|
return MM_BASE_MODEM_AT_RESPONSE_PROCESSOR_RESULT_CONTINUE;
|
|
}
|
|
|
|
static const MMBaseModemAtCommand time_cmd_sequence[] = {
|
|
{ "^NTCT?", 3, FALSE, modem_check_time_reply }, /* 3GPP/LTE */
|
|
{ "^TIME", 3, FALSE, modem_check_time_reply }, /* CDMA */
|
|
{ NULL }
|
|
};
|
|
|
|
static void
|
|
modem_time_check_support (MMIfaceModemTime *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_sequence (MM_BASE_MODEM (self),
|
|
time_cmd_sequence,
|
|
NULL, /* response_processor_context */
|
|
NULL, /* response_processor_context_free */
|
|
(GAsyncReadyCallback)modem_time_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check support (Signal interface) */
|
|
|
|
static void
|
|
hcsq_check_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (_self, res, &error);
|
|
if (response)
|
|
g_task_return_boolean (task, TRUE);
|
|
else
|
|
g_task_return_error (task, error);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
signal_check_support_finish (MMIfaceModemSignal *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
signal_check_support (MMIfaceModemSignal *_self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^HCSQ?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)hcsq_check_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load extended signal information */
|
|
|
|
static void
|
|
detailed_signal_free (DetailedSignal *signal)
|
|
{
|
|
detailed_signal_clear (signal);
|
|
g_slice_free (DetailedSignal, signal);
|
|
}
|
|
|
|
static gboolean
|
|
signal_load_values_finish (MMIfaceModemSignal *self,
|
|
GAsyncResult *res,
|
|
MMSignal **cdma,
|
|
MMSignal **evdo,
|
|
MMSignal **gsm,
|
|
MMSignal **umts,
|
|
MMSignal **lte,
|
|
MMSignal **nr5g,
|
|
GError **error)
|
|
{
|
|
DetailedSignal *signals;
|
|
|
|
signals = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!signals)
|
|
return FALSE;
|
|
|
|
*cdma = signals->cdma ? g_object_ref (signals->cdma) : NULL;
|
|
*evdo = signals->evdo ? g_object_ref (signals->evdo) : NULL;
|
|
*gsm = signals->gsm ? g_object_ref (signals->gsm) : NULL;
|
|
*umts = signals->umts ? g_object_ref (signals->umts) : NULL;
|
|
*lte = signals->lte ? g_object_ref (signals->lte) : NULL;
|
|
*nr5g = signals->nr5g ? g_object_ref (signals->nr5g) : NULL;
|
|
|
|
detailed_signal_free (signals);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
hcsq_get_ready (MMBaseModem *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
DetailedSignal *signals;
|
|
GError *error = NULL;
|
|
|
|
/* Don't care about the response; it will have been parsed by the HCSQ
|
|
* unsolicited event handler and self->priv->detailed_signal will already
|
|
* be updated.
|
|
*/
|
|
if (!mm_base_modem_at_command_finish (_self, res, &error)) {
|
|
mm_obj_dbg (self, "^HCSQ failed: %s", error->message);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
signals = g_slice_new0 (DetailedSignal);
|
|
signals->cdma = self->priv->detailed_signal.cdma ? g_object_ref (self->priv->detailed_signal.cdma) : NULL;
|
|
signals->evdo = self->priv->detailed_signal.evdo ? g_object_ref (self->priv->detailed_signal.evdo) : NULL;
|
|
signals->gsm = self->priv->detailed_signal.gsm ? g_object_ref (self->priv->detailed_signal.gsm) : NULL;
|
|
signals->umts = self->priv->detailed_signal.umts ? g_object_ref (self->priv->detailed_signal.umts) : NULL;
|
|
signals->lte = self->priv->detailed_signal.lte ? g_object_ref (self->priv->detailed_signal.lte) : NULL;
|
|
|
|
g_task_return_pointer (task, signals, (GDestroyNotify)detailed_signal_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
signal_load_values (MMIfaceModemSignal *_self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
/* Clear any previous detailed signal values to get new ones */
|
|
detailed_signal_clear (&self->priv->detailed_signal);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"^HCSQ?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)hcsq_get_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup ports (Broadband modem class) */
|
|
|
|
static void
|
|
set_ignored_unsolicited_events_handlers (MMBroadbandModemHuawei *self)
|
|
{
|
|
GList *ports, *l;
|
|
|
|
ports = mm_broadband_modem_huawei_get_at_port_list (self);
|
|
|
|
/* Enable/disable unsolicited events in given port */
|
|
for (l = ports; l; l = g_list_next (l)) {
|
|
MMPortSerialAt *port = MM_PORT_SERIAL_AT (l->data);
|
|
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->boot_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->connect_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->csnr_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->cusatp_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->cusatend_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->dsdormant_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->simst_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->srvst_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->stin_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->pdpdeact_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->ndisend_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->rfswitch_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->position_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->posend_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->ecclist_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->ltersrp_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->cschannelinfo_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->ccallstate_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->eons_regex,
|
|
NULL, NULL, NULL);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
port,
|
|
self->priv->lwurc_regex,
|
|
NULL, NULL, NULL);
|
|
}
|
|
|
|
g_list_free_full (ports, g_object_unref);
|
|
}
|
|
|
|
static void
|
|
gps_trace_received (MMPortSerialGps *port,
|
|
const gchar *trace,
|
|
MMIfaceModemLocation *self)
|
|
{
|
|
mm_iface_modem_location_gps_update (self, trace);
|
|
}
|
|
|
|
static void
|
|
setup_ports (MMBroadbandModem *self)
|
|
{
|
|
MMPortSerialGps *gps_data_port;
|
|
|
|
/* Call parent's setup ports first always */
|
|
MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_huawei_parent_class)->setup_ports (self);
|
|
|
|
/* Unsolicited messages to always ignore */
|
|
set_ignored_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self));
|
|
|
|
/* Now reset the unsolicited messages we'll handle when enabled */
|
|
set_3gpp_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
|
|
set_cdma_unsolicited_events_handlers (MM_BROADBAND_MODEM_HUAWEI (self), FALSE);
|
|
|
|
/* NMEA GPS monitoring */
|
|
gps_data_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self));
|
|
if (gps_data_port) {
|
|
/* make sure GPS is stopped incase it was left enabled */
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)),
|
|
"^WPEND",
|
|
3, FALSE, FALSE, NULL, NULL, NULL);
|
|
/* Add handler for the NMEA traces */
|
|
mm_port_serial_gps_add_trace_handler (gps_data_port,
|
|
(MMPortSerialGpsTraceFn)gps_trace_received,
|
|
self, NULL);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMBroadbandModemHuawei *
|
|
mm_broadband_modem_huawei_new (const gchar *device,
|
|
const gchar **drivers,
|
|
const gchar *plugin,
|
|
guint16 vendor_id,
|
|
guint16 product_id)
|
|
{
|
|
return g_object_new (MM_TYPE_BROADBAND_MODEM_HUAWEI,
|
|
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 Huawei bearer (NET) supported */
|
|
MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
|
|
MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self)
|
|
{
|
|
/* Initialize private data */
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
|
|
MM_TYPE_BROADBAND_MODEM_HUAWEI,
|
|
MMBroadbandModemHuaweiPrivate);
|
|
/* Prepare regular expressions to setup */
|
|
self->priv->rssi_regex = g_regex_new ("\\r\\n\\^RSSI:\\s*(\\d+)\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->rssilvl_regex = g_regex_new ("\\r\\n\\^RSSILVL:\\s*(\\d+)\\r+\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->hrssilvl_regex = g_regex_new ("\\r\\n\\^HRSSILVL:\\s*(\\d+)\\r+\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
|
|
/* 3GPP: <cr><lf>^MODE:5<cr><lf>
|
|
* CDMA: <cr><lf>^MODE: 2<cr><cr><lf>
|
|
*/
|
|
self->priv->mode_regex = g_regex_new ("\\r\\n\\^MODE:\\s*(\\d*),?(\\d*)\\r+\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->dsflowrpt_regex = g_regex_new ("\\r\\n\\^DSFLOWRPT:(.+)\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->ndisstat_regex = g_regex_new ("\\r\\n(\\^NDISSTAT:.+)\\r+\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
|
|
self->priv->orig_regex = g_regex_new ("\\r\\n\\^ORIG:\\s*(\\d+),\\s*(\\d+)\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->conf_regex = g_regex_new ("\\r\\n\\^CONF:\\s*(\\d+)\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->conn_regex = g_regex_new ("\\r\\n\\^CONN:\\s*(\\d+),\\s*(\\d+)\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->cend_regex = g_regex_new ("\\r\\n\\^CEND:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d*))?\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->ddtmf_regex = g_regex_new ("\\r\\n\\^DDTMF:\\s*([0-9A-D\\*\\#])\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
|
|
self->priv->boot_regex = g_regex_new ("\\r\\n\\^BOOT:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->connect_regex = g_regex_new ("\\r\\n\\^CONNECT .+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->csnr_regex = g_regex_new ("\\r\\n\\^CSNR:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->cusatp_regex = g_regex_new ("\\r\\n\\+CUSATP:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->cusatend_regex = g_regex_new ("\\r\\n\\+CUSATEND\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->dsdormant_regex = g_regex_new ("\\r\\n\\^DSDORMANT:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->simst_regex = g_regex_new ("\\r\\n\\^SIMST:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->srvst_regex = g_regex_new ("\\r\\n\\^SRVST:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->stin_regex = g_regex_new ("\\r\\n\\^STIN:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->hcsq_regex = g_regex_new ("\\r\\n(\\^HCSQ:.+)\\r+\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->pdpdeact_regex = g_regex_new ("\\r\\n\\^PDPDEACT:.+\\r+\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->ndisend_regex = g_regex_new ("\\r\\n\\^NDISEND:.+\\r+\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->rfswitch_regex = g_regex_new ("\\r\\n\\^RFSWITCH:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->position_regex = g_regex_new ("\\r\\n\\^POSITION:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->posend_regex = g_regex_new ("\\r\\n\\^POSEND:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->ecclist_regex = g_regex_new ("\\r\\n\\^ECCLIST:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->ltersrp_regex = g_regex_new ("\\r\\n\\^LTERSRP:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->cschannelinfo_regex = g_regex_new ("\\r\\n\\^CSCHANNELINFO:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->ccallstate_regex = g_regex_new ("\\r\\n\\^CCALLSTATE:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->eons_regex = g_regex_new ("\\r\\n\\^EONS:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
self->priv->lwurc_regex = g_regex_new ("\\r\\n\\^LWURC:.+\\r\\n",
|
|
G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
|
|
self->priv->ndisdup_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->rfswitch_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->sysinfoex_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->syscfg_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->syscfgex_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->nwtime_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->time_support = FEATURE_SUPPORT_UNKNOWN;
|
|
self->priv->cvoice_support = FEATURE_SUPPORT_UNKNOWN;
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object);
|
|
|
|
detailed_signal_clear (&self->priv->detailed_signal);
|
|
|
|
G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (object);
|
|
|
|
g_regex_unref (self->priv->rssi_regex);
|
|
g_regex_unref (self->priv->rssilvl_regex);
|
|
g_regex_unref (self->priv->hrssilvl_regex);
|
|
g_regex_unref (self->priv->mode_regex);
|
|
g_regex_unref (self->priv->dsflowrpt_regex);
|
|
g_regex_unref (self->priv->ndisstat_regex);
|
|
g_regex_unref (self->priv->orig_regex);
|
|
g_regex_unref (self->priv->conf_regex);
|
|
g_regex_unref (self->priv->conn_regex);
|
|
g_regex_unref (self->priv->cend_regex);
|
|
g_regex_unref (self->priv->ddtmf_regex);
|
|
|
|
g_regex_unref (self->priv->boot_regex);
|
|
g_regex_unref (self->priv->connect_regex);
|
|
g_regex_unref (self->priv->csnr_regex);
|
|
g_regex_unref (self->priv->cusatp_regex);
|
|
g_regex_unref (self->priv->cusatend_regex);
|
|
g_regex_unref (self->priv->dsdormant_regex);
|
|
g_regex_unref (self->priv->simst_regex);
|
|
g_regex_unref (self->priv->srvst_regex);
|
|
g_regex_unref (self->priv->stin_regex);
|
|
g_regex_unref (self->priv->hcsq_regex);
|
|
g_regex_unref (self->priv->pdpdeact_regex);
|
|
g_regex_unref (self->priv->ndisend_regex);
|
|
g_regex_unref (self->priv->rfswitch_regex);
|
|
g_regex_unref (self->priv->position_regex);
|
|
g_regex_unref (self->priv->posend_regex);
|
|
g_regex_unref (self->priv->ecclist_regex);
|
|
g_regex_unref (self->priv->ltersrp_regex);
|
|
g_regex_unref (self->priv->cschannelinfo_regex);
|
|
g_regex_unref (self->priv->ccallstate_regex);
|
|
g_regex_unref (self->priv->eons_regex);
|
|
g_regex_unref (self->priv->lwurc_regex);
|
|
|
|
if (self->priv->syscfg_supported_modes)
|
|
g_array_unref (self->priv->syscfg_supported_modes);
|
|
if (self->priv->syscfgex_supported_modes)
|
|
g_array_unref (self->priv->syscfgex_supported_modes);
|
|
if (self->priv->prefmode_supported_modes)
|
|
g_array_unref (self->priv->prefmode_supported_modes);
|
|
|
|
G_OBJECT_CLASS (mm_broadband_modem_huawei_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
iface_modem_init (MMIfaceModem *iface)
|
|
{
|
|
iface_modem_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->reset = reset;
|
|
iface->reset_finish = reset_finish;
|
|
iface->load_access_technologies = load_access_technologies;
|
|
iface->load_access_technologies_finish = load_access_technologies_finish;
|
|
iface->load_unlock_retries = load_unlock_retries;
|
|
iface->load_unlock_retries_finish = load_unlock_retries_finish;
|
|
iface->modem_after_sim_unlock = modem_after_sim_unlock;
|
|
iface->modem_after_sim_unlock_finish = modem_after_sim_unlock_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_supported_modes = load_supported_modes;
|
|
iface->load_supported_modes_finish = load_supported_modes_finish;
|
|
iface->load_current_modes = load_current_modes;
|
|
iface->load_current_modes_finish = load_current_modes_finish;
|
|
iface->set_current_modes = set_current_modes;
|
|
iface->set_current_modes_finish = set_current_modes_finish;
|
|
iface->load_signal_quality = modem_load_signal_quality;
|
|
iface->load_signal_quality_finish = modem_load_signal_quality_finish;
|
|
iface->create_bearer = huawei_modem_create_bearer;
|
|
iface->create_bearer_finish = huawei_modem_create_bearer_finish;
|
|
iface->load_power_state = load_power_state;
|
|
iface->load_power_state_finish = load_power_state_finish;
|
|
iface->modem_power_up = huawei_modem_power_up;
|
|
iface->modem_power_up_finish = huawei_modem_power_up_finish;
|
|
iface->modem_power_down = huawei_modem_power_down;
|
|
iface->modem_power_down_finish = huawei_modem_power_down_finish;
|
|
iface->create_sim = huawei_modem_create_sim;
|
|
iface->create_sim_finish = huawei_modem_create_sim_finish;
|
|
}
|
|
|
|
static void
|
|
iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
|
|
{
|
|
iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
|
|
|
|
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->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;
|
|
}
|
|
|
|
static void
|
|
iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface)
|
|
{
|
|
iface->encode = encode;
|
|
iface->decode = decode;
|
|
}
|
|
|
|
static void
|
|
iface_modem_cdma_init (MMIfaceModemCdma *iface)
|
|
{
|
|
iface_modem_cdma_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events;
|
|
iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
|
|
iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events;
|
|
iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish;
|
|
iface->setup_registration_checks = setup_registration_checks;
|
|
iface->setup_registration_checks_finish = setup_registration_checks_finish;
|
|
iface->get_detailed_registration_state = get_detailed_registration_state;
|
|
iface->get_detailed_registration_state_finish = get_detailed_registration_state_finish;
|
|
}
|
|
|
|
static void
|
|
iface_modem_location_init (MMIfaceModemLocation *iface)
|
|
{
|
|
iface_modem_location_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->load_capabilities = location_load_capabilities;
|
|
iface->load_capabilities_finish = location_load_capabilities_finish;
|
|
iface->enable_location_gathering = enable_location_gathering;
|
|
iface->enable_location_gathering_finish = enable_location_gathering_finish;
|
|
iface->disable_location_gathering = disable_location_gathering;
|
|
iface->disable_location_gathering_finish = disable_location_gathering_finish;
|
|
}
|
|
|
|
static void
|
|
iface_modem_time_init (MMIfaceModemTime *iface)
|
|
{
|
|
iface->check_support = modem_time_check_support;
|
|
iface->check_support_finish = modem_time_check_support_finish;
|
|
iface->load_network_time = modem_time_load_network_time_or_zone;
|
|
iface->load_network_time_finish = modem_time_load_network_time_finish;
|
|
iface->load_network_timezone = modem_time_load_network_time_or_zone;
|
|
iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
|
|
}
|
|
|
|
static void
|
|
iface_modem_voice_init (MMIfaceModemVoice *iface)
|
|
{
|
|
iface_modem_voice_parent = g_type_interface_peek_parent (iface);
|
|
|
|
iface->check_support = modem_voice_check_support;
|
|
iface->check_support_finish = modem_voice_check_support_finish;
|
|
iface->setup_unsolicited_events = modem_voice_setup_unsolicited_events;
|
|
iface->setup_unsolicited_events_finish = modem_voice_setup_unsolicited_events_finish;
|
|
iface->cleanup_unsolicited_events = modem_voice_cleanup_unsolicited_events;
|
|
iface->cleanup_unsolicited_events_finish = modem_voice_cleanup_unsolicited_events_finish;
|
|
iface->enable_unsolicited_events = modem_voice_enable_unsolicited_events;
|
|
iface->enable_unsolicited_events_finish = modem_voice_enable_unsolicited_events_finish;
|
|
iface->disable_unsolicited_events = modem_voice_disable_unsolicited_events;
|
|
iface->disable_unsolicited_events_finish = modem_voice_disable_unsolicited_events_finish;
|
|
iface->setup_in_call_audio_channel = modem_voice_setup_in_call_audio_channel;
|
|
iface->setup_in_call_audio_channel_finish = modem_voice_setup_in_call_audio_channel_finish;
|
|
iface->cleanup_in_call_audio_channel = modem_voice_cleanup_in_call_audio_channel;
|
|
iface->cleanup_in_call_audio_channel_finish = modem_voice_cleanup_in_call_audio_channel_finish;
|
|
|
|
iface->create_call = create_call;
|
|
}
|
|
|
|
static void
|
|
iface_modem_signal_init (MMIfaceModemSignal *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_huawei_class_init (MMBroadbandModemHuaweiClass *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 (MMBroadbandModemHuaweiPrivate));
|
|
|
|
object_class->dispose = dispose;
|
|
object_class->finalize = finalize;
|
|
|
|
broadband_modem_class->setup_ports = setup_ports;
|
|
}
|