1568 lines
52 KiB
C
1568 lines
52 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <glib-object.h>
|
|
#include <gio/gio.h>
|
|
|
|
#define _LIBMM_INSIDE_MM
|
|
#include <libmm-glib.h>
|
|
|
|
#include "mm-log.h"
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-iface-modem-signal.h"
|
|
#include "mm-iface-modem-location.h"
|
|
#include "mm-base-modem.h"
|
|
#include "mm-base-modem-at.h"
|
|
#include "mm-shared-xmm.h"
|
|
#include "mm-modem-helpers-xmm.h"
|
|
|
|
/*****************************************************************************/
|
|
/* Private data context */
|
|
|
|
#define PRIVATE_TAG "shared-xmm-private-tag"
|
|
static GQuark private_quark;
|
|
|
|
typedef enum {
|
|
GPS_ENGINE_STATE_OFF,
|
|
GPS_ENGINE_STATE_STANDALONE,
|
|
GPS_ENGINE_STATE_ASSISTED,
|
|
} GpsEngineState;
|
|
|
|
typedef struct {
|
|
/* Broadband modem class support */
|
|
MMBroadbandModemClass *broadband_modem_class_parent;
|
|
|
|
/* Modem interface support */
|
|
GArray *supported_modes;
|
|
GArray *supported_bands;
|
|
MMModemMode allowed_modes;
|
|
|
|
/* Location interface support */
|
|
MMIfaceModemLocation *iface_modem_location_parent;
|
|
MMModemLocationSource supported_sources;
|
|
MMModemLocationSource enabled_sources;
|
|
GpsEngineState gps_engine_state;
|
|
MMPortSerialAt *gps_port;
|
|
GRegex *xlsrstop_regex;
|
|
GRegex *nmea_regex;
|
|
} Private;
|
|
|
|
static void
|
|
private_free (Private *priv)
|
|
{
|
|
g_clear_object (&priv->gps_port);
|
|
if (priv->supported_modes)
|
|
g_array_unref (priv->supported_modes);
|
|
if (priv->supported_bands)
|
|
g_array_unref (priv->supported_bands);
|
|
g_regex_unref (priv->xlsrstop_regex);
|
|
g_regex_unref (priv->nmea_regex);
|
|
g_slice_free (Private, priv);
|
|
}
|
|
|
|
static Private *
|
|
get_private (MMSharedXmm *self)
|
|
{
|
|
Private *priv;
|
|
|
|
if (G_UNLIKELY (!private_quark))
|
|
private_quark = g_quark_from_static_string (PRIVATE_TAG);
|
|
|
|
priv = g_object_get_qdata (G_OBJECT (self), private_quark);
|
|
if (!priv) {
|
|
priv = g_slice_new0 (Private);
|
|
priv->gps_engine_state = GPS_ENGINE_STATE_OFF;
|
|
|
|
/* Setup regex for URCs */
|
|
priv->xlsrstop_regex = g_regex_new ("\\r\\n\\+XLSRSTOP:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
priv->nmea_regex = g_regex_new ("(?:\\r\\n)?(?:\\r\\n)?(\\$G.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
|
|
|
|
/* Setup parent class' MMBroadbandModemClass */
|
|
g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class);
|
|
priv->broadband_modem_class_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self);
|
|
|
|
/* Setup parent class' MMIfaceModemLocation */
|
|
g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface);
|
|
priv->iface_modem_location_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface (self);
|
|
|
|
g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
|
|
}
|
|
|
|
return priv;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Supported modes/bands (Modem interface) */
|
|
|
|
GArray *
|
|
mm_shared_xmm_load_supported_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
Private *priv;
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (res), error))
|
|
return NULL;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
g_assert (priv->supported_modes);
|
|
return g_array_ref (priv->supported_modes);
|
|
}
|
|
|
|
GArray *
|
|
mm_shared_xmm_load_supported_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
Private *priv;
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (res), error))
|
|
return NULL;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
g_assert (priv->supported_bands);
|
|
return g_array_ref (priv->supported_bands);
|
|
}
|
|
|
|
static void
|
|
xact_test_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response ||
|
|
!mm_xmm_parse_xact_test_response (response,
|
|
&priv->supported_modes,
|
|
&priv->supported_bands,
|
|
&error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
common_load_supported_modes_bands (GTask *task)
|
|
{
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (g_task_get_source_object (task)),
|
|
"+XACT=?",
|
|
3,
|
|
TRUE, /* allow caching */
|
|
(GAsyncReadyCallback)xact_test_ready,
|
|
task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_load_supported_modes (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
if (!priv->supported_modes) {
|
|
common_load_supported_modes_bands (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_load_supported_bands (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
if (!priv->supported_bands) {
|
|
common_load_supported_modes_bands (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Current modes (Modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_xmm_load_current_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
MMModemMode *allowed,
|
|
MMModemMode *preferred,
|
|
GError **error)
|
|
{
|
|
MMModemModeCombination *result;
|
|
|
|
result = g_task_propagate_pointer (G_TASK (res), error);
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
*allowed = result->allowed;
|
|
*preferred = result->preferred;
|
|
g_free (result);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
xact_query_modes_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
MMModemModeCombination *result;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
result = g_new0 (MMModemModeCombination, 1);
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response || !mm_xmm_parse_xact_query_response (response, result, NULL, &error)) {
|
|
priv->allowed_modes = MM_MODEM_MODE_NONE;
|
|
g_free (result);
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
priv->allowed_modes = result->allowed;
|
|
g_task_return_pointer (task, result, g_free);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_load_current_modes (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+XACT?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)xact_query_modes_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Current bands (Modem interface) */
|
|
|
|
GArray *
|
|
mm_shared_xmm_load_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
|
|
static void
|
|
xact_query_bands_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
GArray *result = NULL;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response ||
|
|
!mm_xmm_parse_xact_query_response (response, NULL, &result, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_pointer (task, result, (GDestroyNotify)g_array_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_load_current_bands (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
"+XACT?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)xact_query_bands_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set current modes (Modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_xmm_set_current_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
xact_set_modes_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);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_set_current_modes (MMIfaceModem *self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
MMModemModeCombination mode;
|
|
gchar *command;
|
|
GError *error = NULL;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (allowed != MM_MODEM_MODE_ANY) {
|
|
mode.allowed = allowed;
|
|
mode.preferred = preferred;
|
|
} else {
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
mode.allowed = mm_xmm_get_modem_mode_any (priv->supported_modes);
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
}
|
|
|
|
command = mm_xmm_build_xact_set_command (&mode, NULL, &error);
|
|
if (!command) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)xact_set_modes_ready,
|
|
task);
|
|
g_free (command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set current bands (Modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_xmm_set_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
xact_set_bands_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 gchar *
|
|
validate_and_build_command_set_current_bands (const GArray *bands_array,
|
|
const GArray *supported_modes,
|
|
MMModemMode allowed_modes,
|
|
GError **error)
|
|
{
|
|
gboolean band_2g_found = FALSE;
|
|
gboolean band_3g_found = FALSE;
|
|
gboolean band_4g_found = FALSE;
|
|
GArray *unapplied_bands;
|
|
GError *inner_error = NULL;
|
|
guint i;
|
|
|
|
/* ANY applies only to the currently selected modes */
|
|
if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
|
|
MMModemModeCombination mode;
|
|
MMModemMode unapplied;
|
|
|
|
/* If we are enabling automatic band selection to a mode combination that does not include
|
|
* all supported modes, warn about it because automatic band selection wouldn't be executed
|
|
* for the non-selected modes.
|
|
*
|
|
* This is a known limitation of the modem firmware.
|
|
*/
|
|
unapplied = mm_xmm_get_modem_mode_any (supported_modes) & ~(allowed_modes);
|
|
if (unapplied != MM_MODEM_MODE_NONE) {
|
|
gchar *str;
|
|
|
|
str = mm_modem_mode_build_string_from_mask (unapplied);
|
|
mm_warn ("Automatic band selection not applied to non-current modes %s", str);
|
|
g_free (str);
|
|
}
|
|
|
|
/* Nothing else to validate, go build the command right away */
|
|
|
|
/* We must create the set command with an explicit set of allowed modes.
|
|
* We pass NONE as preferred, but that WON'T change the currently selected preferred mode,
|
|
* it will be ignored when the command is processed as an empty field will be given */
|
|
mode.allowed = allowed_modes;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
return mm_xmm_build_xact_set_command (&mode, bands_array, error);
|
|
}
|
|
|
|
unapplied_bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
|
|
for (i = 0; i < bands_array->len; i++) {
|
|
MMModemBand band;
|
|
|
|
band = g_array_index (bands_array, MMModemBand, i);
|
|
if (mm_common_band_is_eutran (band)) {
|
|
band_4g_found = TRUE;
|
|
if (!(allowed_modes & MM_MODEM_MODE_4G))
|
|
g_array_append_val (unapplied_bands, band);
|
|
}
|
|
if (mm_common_band_is_utran (band)) {
|
|
band_3g_found = TRUE;
|
|
if (!(allowed_modes & MM_MODEM_MODE_3G))
|
|
g_array_append_val (unapplied_bands, band);
|
|
}
|
|
if (mm_common_band_is_gsm (band)) {
|
|
band_2g_found = TRUE;
|
|
if (!(allowed_modes & MM_MODEM_MODE_2G))
|
|
g_array_append_val (unapplied_bands, band);
|
|
}
|
|
}
|
|
|
|
/* If 2G selected, there must be at least one 2G band */
|
|
if ((allowed_modes & MM_MODEM_MODE_2G) && !band_2g_found) {
|
|
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
|
"At least one GSM band is required when 2G mode is allowed");
|
|
goto out;
|
|
}
|
|
|
|
/* If 3G selected, there must be at least one 3G band */
|
|
if ((allowed_modes & MM_MODEM_MODE_3G) && !band_3g_found) {
|
|
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
|
"At least one UTRAN band is required when 3G mode is allowed");
|
|
goto out;
|
|
}
|
|
|
|
/* If 4G selected, there must be at least one 4G band */
|
|
if ((allowed_modes & MM_MODEM_MODE_4G) && !band_4g_found) {
|
|
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
|
"At least one E-UTRAN band is required when 4G mode is allowed");
|
|
goto out;
|
|
}
|
|
|
|
/* Don't try to modify bands for modes that are not enabled */
|
|
if (unapplied_bands->len > 0) {
|
|
gchar *str;
|
|
|
|
str = mm_common_build_bands_string ((const MMModemBand *)unapplied_bands->data, unapplied_bands->len);
|
|
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
|
"Cannot update bands for modes not currently allowed: %s", str);
|
|
g_free (str);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (unapplied_bands)
|
|
g_array_unref (unapplied_bands);
|
|
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return NULL;
|
|
}
|
|
|
|
return mm_xmm_build_xact_set_command (NULL, bands_array, error);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_set_current_bands (MMIfaceModem *self,
|
|
GArray *bands_array,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
gchar *command = NULL;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Setting bands requires additional validation rules based on the
|
|
* currently selected list of allowed modes */
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
if (priv->allowed_modes == MM_MODEM_MODE_NONE) {
|
|
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Cannot set bands if allowed modes are unknown");
|
|
goto out;
|
|
}
|
|
|
|
command = validate_and_build_command_set_current_bands (bands_array,
|
|
priv->supported_modes,
|
|
priv->allowed_modes,
|
|
&error);
|
|
|
|
out:
|
|
if (!command) {
|
|
g_assert (error);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self),
|
|
command,
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)xact_set_bands_ready,
|
|
task);
|
|
g_free (command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Power state loading (Modem interface) */
|
|
|
|
MMModemPowerState
|
|
mm_shared_xmm_load_power_state_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
guint state;
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
if (!response)
|
|
return MM_MODEM_POWER_STATE_UNKNOWN;
|
|
|
|
if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
|
|
return MM_MODEM_POWER_STATE_UNKNOWN;
|
|
|
|
switch (state) {
|
|
case 1:
|
|
return MM_MODEM_POWER_STATE_ON;
|
|
case 4:
|
|
return MM_MODEM_POWER_STATE_LOW;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Unknown +CFUN state: %u", state);
|
|
return MM_MODEM_POWER_STATE_UNKNOWN;
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_load_power_state (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+CFUN?",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Modem power up/down/off (Modem interface) */
|
|
|
|
static gboolean
|
|
common_modem_power_operation_finish (MMSharedXmm *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
power_operation_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
|
|
common_modem_power_operation (MMSharedXmm *self,
|
|
const gchar *command,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
command,
|
|
30,
|
|
FALSE,
|
|
(GAsyncReadyCallback) power_operation_ready,
|
|
task);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_xmm_reset_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_reset (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=16", callback, user_data);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_xmm_power_off_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_power_off (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_modem_power_operation (MM_SHARED_XMM (self), "+CPWROFF", callback, user_data);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_xmm_power_down_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_power_down (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=4", callback, user_data);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_xmm_power_up_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return common_modem_power_operation_finish (MM_SHARED_XMM (self), res, error);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_power_up (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_modem_power_operation (MM_SHARED_XMM (self), "+CFUN=1", callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Check support (Signal interface) */
|
|
|
|
gboolean
|
|
mm_shared_xmm_signal_check_support_finish (MMIfaceModemSignal *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_signal_check_support (MMIfaceModemSignal *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+XCESQ=?",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load extended signal information (Signal interface) */
|
|
|
|
gboolean
|
|
mm_shared_xmm_signal_load_values_finish (MMIfaceModemSignal *self,
|
|
GAsyncResult *res,
|
|
MMSignal **cdma,
|
|
MMSignal **evdo,
|
|
MMSignal **gsm,
|
|
MMSignal **umts,
|
|
MMSignal **lte,
|
|
GError **error)
|
|
{
|
|
const gchar *response;
|
|
|
|
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
|
if (!response || !mm_xmm_xcesq_response_to_signal_info (response, gsm, umts, lte, error))
|
|
return FALSE;
|
|
|
|
if (cdma)
|
|
*cdma = NULL;
|
|
if (evdo)
|
|
*evdo = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+XCESQ?",
|
|
3,
|
|
FALSE,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load capabilities (Location interface) */
|
|
|
|
MMModemLocationSource
|
|
mm_shared_xmm_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
|
|
xlcslsr_test_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource sources;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
gboolean transport_protocol_invalid_supported;
|
|
gboolean transport_protocol_supl_supported;
|
|
gboolean standalone_position_mode_supported;
|
|
gboolean ms_assisted_based_position_mode_supported;
|
|
gboolean loc_response_type_nmea_supported;
|
|
gboolean gnss_type_gps_glonass_supported;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
/* Recover parent sources */
|
|
sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response ||
|
|
!mm_xmm_parse_xlcslsr_test_response (response,
|
|
&transport_protocol_invalid_supported,
|
|
&transport_protocol_supl_supported,
|
|
&standalone_position_mode_supported,
|
|
&ms_assisted_based_position_mode_supported,
|
|
&loc_response_type_nmea_supported,
|
|
&gnss_type_gps_glonass_supported,
|
|
&error)) {
|
|
mm_dbg ("XLCSLSR based GPS control unsupported: %s", error->message);
|
|
g_clear_error (&error);
|
|
} else if (!transport_protocol_invalid_supported ||
|
|
!standalone_position_mode_supported ||
|
|
!loc_response_type_nmea_supported ||
|
|
!gnss_type_gps_glonass_supported) {
|
|
mm_dbg ("XLCSLSR based GPS control unsupported: protocol invalid %s, standalone %s, nmea %s, gps/glonass %s",
|
|
transport_protocol_invalid_supported ? "supported" : "unsupported",
|
|
standalone_position_mode_supported ? "supported" : "unsupported",
|
|
loc_response_type_nmea_supported ? "supported" : "unsupported",
|
|
gnss_type_gps_glonass_supported ? "supported" : "unsupported");
|
|
} else {
|
|
mm_dbg ("XLCSLSR based GPS control supported");
|
|
priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW);
|
|
|
|
if (transport_protocol_supl_supported && ms_assisted_based_position_mode_supported) {
|
|
mm_dbg ("XLCSLSR based A-GPS control supported");
|
|
priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_AGPS;
|
|
} else {
|
|
mm_dbg ("XLCSLSR based A-GPS control unsupported: protocol supl %s, ms assisted/based %s",
|
|
transport_protocol_supl_supported ? "supported" : "unsupported",
|
|
ms_assisted_based_position_mode_supported ? "supported" : "unsupported");
|
|
}
|
|
|
|
sources |= priv->supported_sources;
|
|
}
|
|
|
|
g_task_return_int (task, sources);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
parent_load_capabilities_ready (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource sources;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error);
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* If parent already supports GPS sources, we won't do anything else */
|
|
if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
|
|
mm_dbg ("No need to run XLCSLSR based location gathering");
|
|
g_task_return_int (task, sources);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Cache sources supported by the parent */
|
|
g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (g_task_get_source_object (task)),
|
|
"+XLCSLSR=?",
|
|
3,
|
|
TRUE, /* allow caching */
|
|
(GAsyncReadyCallback)xlcslsr_test_ready,
|
|
task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
g_assert (priv->iface_modem_location_parent);
|
|
g_assert (priv->iface_modem_location_parent->load_capabilities);
|
|
g_assert (priv->iface_modem_location_parent->load_capabilities_finish);
|
|
|
|
priv->iface_modem_location_parent->load_capabilities (self,
|
|
(GAsyncReadyCallback)parent_load_capabilities_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* GPS engine state selection */
|
|
|
|
static void
|
|
nmea_received (MMPortSerialAt *port,
|
|
GMatchInfo *info,
|
|
MMSharedXmm *self)
|
|
{
|
|
gchar *trace;
|
|
|
|
trace = g_match_info_fetch (info, 1);
|
|
|
|
/* Helper to debug GPS location related issues. Don't depend on a real GPS
|
|
* fix for debugging, just use some random values to update */
|
|
#if 0
|
|
{
|
|
const gchar *prefix = NULL;
|
|
const gchar *lat = NULL;
|
|
|
|
/* lat N/S just to test which one is used */
|
|
if (g_str_has_prefix (trace, "$GPGGA")) {
|
|
prefix = "GPGGA";
|
|
lat = "S";
|
|
} else if (g_str_has_prefix (trace, "$GNGGA")) {
|
|
prefix = "GNGGA";
|
|
lat = "N";
|
|
}
|
|
|
|
if (prefix && lat) {
|
|
GString *str;
|
|
GDateTime *now;
|
|
|
|
mm_dbg ("GGA trace detected: '%s'", trace);
|
|
g_free (trace);
|
|
|
|
now = g_date_time_new_now_utc ();
|
|
str = g_string_new ("");
|
|
g_string_append_printf (str,
|
|
"$%s,%02u%02u%02u,4807.038,%s,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
|
|
prefix,
|
|
g_date_time_get_hour (now),
|
|
g_date_time_get_minute (now),
|
|
g_date_time_get_second (now),
|
|
lat);
|
|
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), str->str);
|
|
g_string_free (str, TRUE);
|
|
g_date_time_unref (now);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), trace);
|
|
g_free (trace);
|
|
}
|
|
|
|
static gboolean
|
|
gps_engine_state_select_finish (MMSharedXmm *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
xlcslsr_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GpsEngineState state;
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
response = mm_base_modem_at_command_full_finish (self, res, &error);
|
|
if (!response) {
|
|
g_clear_object (&priv->gps_port);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
state = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
g_assert (priv->gps_port);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (priv->gps_port,
|
|
priv->nmea_regex,
|
|
(MMPortSerialAtUnsolicitedMsgFn)nmea_received,
|
|
self,
|
|
NULL);
|
|
priv->gps_engine_state = state;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
gps_engine_start (GTask *task)
|
|
{
|
|
GpsEngineState state;
|
|
MMSharedXmm *self;
|
|
Private *priv;
|
|
guint transport_protocol = 0;
|
|
guint pos_mode = 0;
|
|
gchar *cmd;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
state = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
/* Look for an AT port to use for GPS. Prefer secondary port if there is one,
|
|
* otherwise use primary */
|
|
g_assert (!priv->gps_port);
|
|
priv->gps_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
|
|
if (!priv->gps_port) {
|
|
priv->gps_port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
|
|
if (!priv->gps_port) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"No valid port found to control GPS");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (state) {
|
|
case GPS_ENGINE_STATE_STANDALONE:
|
|
transport_protocol = 2;
|
|
pos_mode = 3;
|
|
break;
|
|
case GPS_ENGINE_STATE_ASSISTED:
|
|
transport_protocol = 1;
|
|
pos_mode = 2;
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* AT+XLCSLSR
|
|
* transport_protocol: 2 (invalid) or 1 (supl)
|
|
* pos_mode: 3 (standalone) or 2 (ms assisted+based)
|
|
* client_id: <empty>
|
|
* client_id_type: <empty>
|
|
* mlc_number: <empty>
|
|
* mlc_number_type: <empty>
|
|
* interval: 1 (seconds)
|
|
* service_type_id: <empty>
|
|
* pseudonym_indicator: <empty>
|
|
* loc_response_type: 1 (NMEA strings)
|
|
* nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
|
|
* gnss_type: 0 (GPS or GLONASS)
|
|
*/
|
|
g_assert (priv->gps_port);
|
|
cmd = g_strdup_printf ("AT+XLCSLSR=%u,%u,,,,,1,,,1,118,0", transport_protocol, pos_mode);
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
priv->gps_port,
|
|
cmd,
|
|
3,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)xlcslsr_ready,
|
|
task);
|
|
g_free (cmd);
|
|
}
|
|
|
|
static void
|
|
xlsrstop_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GpsEngineState state;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
mm_base_modem_at_command_full_finish (self, res, &error);
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
state = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
g_assert (priv->gps_port);
|
|
mm_port_serial_at_add_unsolicited_msg_handler (priv->gps_port, priv->nmea_regex, NULL, NULL, NULL);
|
|
g_clear_object (&priv->gps_port);
|
|
priv->gps_engine_state = GPS_ENGINE_STATE_OFF;
|
|
|
|
/* If already reached requested state, we're done */
|
|
if (state == priv->gps_engine_state) {
|
|
/* If we had an error when requesting this specific state, report it */
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Ignore errors if the stop operation was an intermediate one */
|
|
g_clear_error (&error);
|
|
|
|
/* Otherwise, start with new state */
|
|
gps_engine_start (task);
|
|
}
|
|
|
|
static void
|
|
gps_engine_stop (GTask *task)
|
|
{
|
|
MMSharedXmm *self;
|
|
Private *priv;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
g_assert (priv->gps_port);
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
priv->gps_port,
|
|
"+XLSRSTOP",
|
|
3,
|
|
FALSE,
|
|
FALSE, /* raw */
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)xlsrstop_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
gps_engine_state_select (MMSharedXmm *self,
|
|
GpsEngineState state,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, GUINT_TO_POINTER (state), NULL);
|
|
|
|
priv = get_private (self);
|
|
|
|
/* If already in the requested state, we're done */
|
|
if (state == priv->gps_engine_state) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* If states are different we always STOP first */
|
|
if (priv->gps_engine_state != GPS_ENGINE_STATE_OFF) {
|
|
gps_engine_stop (task);
|
|
return;
|
|
}
|
|
|
|
/* If GPS already stopped, go on to START right away */
|
|
g_assert (state != GPS_ENGINE_STATE_OFF);
|
|
gps_engine_start (task);
|
|
}
|
|
|
|
static GpsEngineState
|
|
gps_engine_state_get_expected (MMModemLocationSource sources)
|
|
{
|
|
/* If at lease one of GPS nmea/raw sources enabled, engine started */
|
|
if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
|
|
/* If A-GPS is enabled, ASSISTED mode */
|
|
if (sources & MM_MODEM_LOCATION_SOURCE_AGPS)
|
|
return GPS_ENGINE_STATE_ASSISTED;
|
|
/* Otherwise, STANDALONE */
|
|
return GPS_ENGINE_STATE_STANDALONE;
|
|
}
|
|
/* If no GPS nmea/raw sources enabled, engine stopped */
|
|
return GPS_ENGINE_STATE_OFF;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Disable location gathering (Location interface) */
|
|
|
|
gboolean
|
|
mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
disable_gps_engine_state_select_ready (MMSharedXmm *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource source;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
if (!gps_engine_state_select_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
priv->enabled_sources &= ~source;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
g_assert (priv->iface_modem_location_parent);
|
|
if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
Private *priv;
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
g_assert (priv->iface_modem_location_parent);
|
|
|
|
/* Only consider request if it applies to one of the sources we are
|
|
* supporting, otherwise run parent disable */
|
|
if (!(priv->supported_sources & source)) {
|
|
/* If disabling implemented by the parent, run it. */
|
|
if (priv->iface_modem_location_parent->disable_location_gathering &&
|
|
priv->iface_modem_location_parent->disable_location_gathering_finish) {
|
|
priv->iface_modem_location_parent->disable_location_gathering (self,
|
|
source,
|
|
(GAsyncReadyCallback)parent_disable_location_gathering_ready,
|
|
task);
|
|
return;
|
|
}
|
|
/* Otherwise, we're done */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* We only expect GPS sources here */
|
|
g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_AGPS));
|
|
|
|
/* Update engine based on the expected sources */
|
|
gps_engine_state_select (MM_SHARED_XMM (self),
|
|
gps_engine_state_get_expected (priv->enabled_sources & ~source),
|
|
(GAsyncReadyCallback) disable_gps_engine_state_select_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable location gathering (Location interface) */
|
|
|
|
gboolean
|
|
mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
enable_gps_engine_state_select_ready (MMSharedXmm *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource source;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
if (!gps_engine_state_select_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
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)
|
|
{
|
|
GError *error;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
|
|
g_assert (priv->iface_modem_location_parent);
|
|
if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
Private *priv;
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
g_assert (priv->iface_modem_location_parent);
|
|
g_assert (priv->iface_modem_location_parent->enable_location_gathering);
|
|
g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish);
|
|
|
|
/* Only consider request if it applies to one of the sources we are
|
|
* supporting, otherwise run parent enable */
|
|
if (!(priv->supported_sources & source)) {
|
|
priv->iface_modem_location_parent->enable_location_gathering (self,
|
|
source,
|
|
(GAsyncReadyCallback)parent_enable_location_gathering_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* We only expect GPS sources here */
|
|
g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_AGPS));
|
|
|
|
/* Update engine based on the expected sources */
|
|
gps_engine_state_select (MM_SHARED_XMM (self),
|
|
gps_engine_state_get_expected (priv->enabled_sources | source),
|
|
(GAsyncReadyCallback) enable_gps_engine_state_select_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: Load SUPL server */
|
|
|
|
gchar *
|
|
mm_shared_xmm_location_load_supl_server_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
xlcsslp_query_ready (MMBaseModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
const gchar *response;
|
|
GError *error = NULL;
|
|
gchar *supl_address;
|
|
|
|
response = mm_base_modem_at_command_finish (self, res, &error);
|
|
if (!response || !mm_xmm_parse_xlcsslp_query_response (response, &supl_address, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_pointer (task, supl_address, g_free);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_location_load_supl_server (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
"+XLCSSLP?",
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)xlcsslp_query_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
gboolean
|
|
mm_shared_xmm_location_set_supl_server_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
xlcsslp_set_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);
|
|
}
|
|
|
|
void
|
|
mm_shared_xmm_location_set_supl_server (MMIfaceModemLocation *self,
|
|
const gchar *supl,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
gchar *cmd = NULL;
|
|
gchar *fqdn = NULL;
|
|
guint32 ip;
|
|
guint16 port;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_parse_supl_address (supl, &fqdn, &ip, &port, NULL);
|
|
g_assert (port);
|
|
if (fqdn)
|
|
cmd = g_strdup_printf ("+XLCSSLP=1,%s,%u", fqdn, port);
|
|
else if (ip) {
|
|
struct in_addr a = { .s_addr = ip };
|
|
gchar buf[INET_ADDRSTRLEN + 1] = { 0 };
|
|
|
|
/* we got 'ip' from inet_pton(), so this next step should always succeed */
|
|
g_assert (inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1));
|
|
cmd = g_strdup_printf ("+XLCSSLP=0,%s,%u", buf, port);
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self),
|
|
cmd,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)xlcsslp_set_ready,
|
|
task);
|
|
g_free (cmd);
|
|
g_free (fqdn);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
mm_shared_xmm_setup_ports (MMBroadbandModem *self)
|
|
{
|
|
Private *priv;
|
|
MMPortSerialAt *ports[2];
|
|
guint i;
|
|
|
|
priv = get_private (MM_SHARED_XMM (self));
|
|
g_assert (priv->broadband_modem_class_parent);
|
|
g_assert (priv->broadband_modem_class_parent->setup_ports);
|
|
|
|
/* Parent setup first always */
|
|
priv->broadband_modem_class_parent->setup_ports (self);
|
|
|
|
ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
|
|
ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
|
|
|
|
/* Setup primary and secondary ports */
|
|
for (i = 0; i < G_N_ELEMENTS (ports); i++) {
|
|
if (!ports[i])
|
|
continue;
|
|
|
|
/* After running AT+XLSRSTOP we may get an unsolicited response
|
|
* reporting its status, we just ignore it. */
|
|
mm_port_serial_at_add_unsolicited_msg_handler (
|
|
ports[i],
|
|
priv->xlsrstop_regex,
|
|
NULL, NULL, NULL);
|
|
|
|
|
|
|
|
/* make sure GPS is stopped in case it was left enabled */
|
|
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
|
|
ports[i],
|
|
"+XLSRSTOP",
|
|
3, FALSE, FALSE, NULL, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
shared_xmm_init (gpointer g_iface)
|
|
{
|
|
}
|
|
|
|
GType
|
|
mm_shared_xmm_get_type (void)
|
|
{
|
|
static GType shared_xmm_type = 0;
|
|
|
|
if (!G_UNLIKELY (shared_xmm_type)) {
|
|
static const GTypeInfo info = {
|
|
sizeof (MMSharedXmm), /* class_size */
|
|
shared_xmm_init, /* base_init */
|
|
NULL, /* base_finalize */
|
|
};
|
|
|
|
shared_xmm_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedXmm", &info, 0);
|
|
g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM);
|
|
g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM_LOCATION);
|
|
}
|
|
|
|
return shared_xmm_type;
|
|
}
|