Files
ModemManager/plugins/xmm/mm-shared-xmm.c
Aleksander Morgado 7f822fbc50 xmm,shared: don't assume parent object implements location support
E.g. when MM is built without QMI support, the MBIM modem won't have
any location support, as the only location implementation is based on
QMI over MBIM.

https://gitlab.freedesktop.org/mobile-broadband/ModemManager/issues/125
2019-05-28 22:56:54 +02:00

1589 lines
53 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_AGPS_MSA,
GPS_ENGINE_STATE_AGPS_MSB,
} 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_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
} 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
run_xlcslsr_test (GTask *task)
{
mm_base_modem_at_command (
MM_BASE_MODEM (g_task_get_source_object (task)),
"+XLCSLSR=?",
3,
TRUE, /* allow caching */
(GAsyncReadyCallback)xlcslsr_test_ready,
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);
run_xlcslsr_test (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);
if (!priv->iface_modem_location_parent->load_capabilities ||
!priv->iface_modem_location_parent->load_capabilities_finish) {
/* no parent capabilities */
g_task_set_task_data (task, GUINT_TO_POINTER (MM_MODEM_LOCATION_SOURCE_NONE), NULL);
run_xlcslsr_test (task);
return;
}
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_AGPS_MSB:
transport_protocol = 1;
pos_mode = 1;
break;
case GPS_ENGINE_STATE_AGPS_MSA:
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), 1 (msb) or 2 (msa)
* 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 MSA A-GPS is enabled, MSA mode */
if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSA)
return GPS_ENGINE_STATE_AGPS_MSA;
/* If MSB A-GPS is enabled, MSB mode */
if (sources & MM_MODEM_LOCATION_SOURCE_AGPS_MSB)
return GPS_ENGINE_STATE_AGPS_MSB;
/* 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_MSA |
MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
/* 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);
/* Only consider request if it applies to one of the sources we are
* supporting, otherwise run parent enable */
if (priv->iface_modem_location_parent->enable_location_gathering &&
priv->iface_modem_location_parent->enable_location_gathering_finish &&
!(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_MSA |
MM_MODEM_LOCATION_SOURCE_AGPS_MSB));
/* 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;
}