Files
ModemManager/plugins/xmm/mm-shared-xmm.c
Aleksander Morgado c659492022 xmm: wait for +XLSRSTOP URC before starting next session
It is important to gracefully stop the session before starting the
next session, making sure the engine is completely stopped. This can
be ensured by waiting for the +XLSRSTOP URC just after having received
the +XLSRSTOP command response.

We'll do an explicit wait for that URC with a 10s timeout, in order to
avoid waiting forever if the URC is never received. It will be assumed
that the engine is off if the 10s timeout happens, in the same way as
we were doing until now.

During the wait time for the URC, the operation task ownership is
shared among the response processor, the URC handler and the timeout
source. Once any of them decides to complete the task, the others will
automatically avoid attempting to complete the same task.

Based on a patch originally developed by:
Som_SP <somashekhar.puttagangaiah@intel.com>
2022-05-19 11:45:37 +00:00

1711 lines
57 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-object.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;
/* Asynchronous GPS engine stop task completion */
GTask *pending_gps_engine_stop_task;
} Private;
static void
private_free (Private *priv)
{
g_assert (!priv->pending_gps_engine_stop_task);
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,
self,
&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 (MMSharedXmm *self,
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) {
g_autofree gchar *str = NULL;
str = mm_modem_mode_build_string_from_mask (unapplied);
mm_obj_warn (self, "automatic band selection not applied to non-current modes %s", 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 *)(gconstpointer)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 (MM_SHARED_XMM (self),
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,
MMSignal **nr5g,
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, self, gsm, umts, lte, error))
return FALSE;
if (cdma)
*cdma = NULL;
if (evdo)
*evdo = NULL;
if (nr5g)
*nr5g = 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);
}
/*****************************************************************************/
/* Get GPS control port (Location interface)
*
* This port is an AT port that will also be used for NMEA data.
*/
static MMPortSerialAt *
shared_xmm_get_gps_control_port (MMSharedXmm *self,
GError **error)
{
MMPortSerialAt *gps_port = NULL;
gps_port = mm_base_modem_get_port_gps_control (MM_BASE_MODEM (self));
if (!gps_port) {
gps_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
if (!gps_port)
gps_port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
}
if (!gps_port)
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No valid port found to control GPS");
return gps_port;
}
/*****************************************************************************/
/* 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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "XLCSLSR based A-GPS control supported");
priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_AGPS_MSA | MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
} else {
mm_obj_dbg (self, "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_obj_dbg (self, "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);
}
/*****************************************************************************/
/* NMEA trace processing */
static void
nmea_received (MMPortSerialAt *port,
GMatchInfo *info,
MMSharedXmm *self)
{
gchar *trace;
trace = g_match_info_fetch (info, 1);
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), trace);
g_free (trace);
}
/*****************************************************************************/
/* GPS engine state selection */
#define GPS_ENGINE_STOP_TIMEOUT_SECS 10
typedef struct {
GpsEngineState state;
guint engine_stop_timeout_id;
} GpsEngineSelectContext;
static void
gps_engine_select_context_free (GpsEngineSelectContext *ctx)
{
g_assert (!ctx->engine_stop_timeout_id);
g_slice_free (GpsEngineSelectContext, ctx);
}
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)
{
GpsEngineSelectContext *ctx;
const gchar *response;
GError *error = NULL;
Private *priv;
priv = get_private (MM_SHARED_XMM (self));
ctx = g_task_get_task_data (task);
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;
}
mm_obj_dbg (self, "GPS engine started");
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 = ctx->state;
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
gps_engine_start (GTask *task)
{
GpsEngineSelectContext *ctx;
MMSharedXmm *self;
Private *priv;
GError *error = NULL;
guint transport_protocol = 0;
guint pos_mode = 0;
gchar *cmd;
self = g_task_get_source_object (task);
priv = get_private (self);
ctx = g_task_get_task_data (task);
g_assert (!priv->gps_port);
priv->gps_port = shared_xmm_get_gps_control_port (self, &error);
if (!priv->gps_port) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
switch (ctx->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;
case GPS_ENGINE_STATE_OFF:
default:
g_assert_not_reached ();
break;
}
mm_obj_dbg (self, "starting GPS engine...");
/*
* 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 GTask *
recover_pending_gps_engine_stop_task (Private *priv)
{
GTask *task;
GpsEngineSelectContext *ctx;
/* We're taking over full ownership of the GTask at this point. */
if (!priv->pending_gps_engine_stop_task)
return NULL;
task = g_steal_pointer (&priv->pending_gps_engine_stop_task);
ctx = g_task_get_task_data (task);
/* remove timeout */
if (ctx->engine_stop_timeout_id) {
g_source_remove (ctx->engine_stop_timeout_id);
ctx->engine_stop_timeout_id = 0;
}
/* disable urc handling */
mm_port_serial_at_add_unsolicited_msg_handler (
priv->gps_port,
priv->xlsrstop_regex,
NULL, NULL, NULL);
return task;
}
static void
gps_engine_stopped (GTask *task)
{
MMSharedXmm *self;
GpsEngineSelectContext *ctx;
Private *priv;
self = g_task_get_source_object (task);
priv = get_private (self);
ctx = 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 (ctx->state == priv->gps_engine_state) {
/* If we had an error when requesting this specific state, report it */
g_task_return_boolean (task, TRUE);
g_object_unref (task);
return;
}
/* Otherwise, start with new state */
gps_engine_start (task);
}
static gboolean
xlsrstop_urc_timeout (MMSharedXmm *self)
{
GTask *task;
Private *priv;
priv = get_private (self);
task = recover_pending_gps_engine_stop_task (priv);
g_assert (task);
mm_obj_dbg (self, "timed out waiting for full GPS engine stop report, assuming stopped...");
gps_engine_stopped (task);
return G_SOURCE_REMOVE;
}
static void
xlsrstop_urc_received (MMPortSerialAt *port,
GMatchInfo *info,
MMSharedXmm *self)
{
GTask *task;
Private *priv;
priv = get_private (self);
task = recover_pending_gps_engine_stop_task (priv);
g_assert (task);
mm_obj_dbg (self, "GPS engine fully stopped");
gps_engine_stopped (task);
}
static void
xlsrstop_ready (MMBaseModem *self,
GAsyncResult *res)
{
g_autoptr(GError) error = NULL;
if (!mm_base_modem_at_command_full_finish (self, res, &error)) {
Private *priv;
GTask *task;
mm_obj_dbg (self, "GPS engine stop request failed: %s", error->message);
priv = get_private (MM_SHARED_XMM (self));
task = recover_pending_gps_engine_stop_task (priv);
if (task) {
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
}
return;
}
mm_obj_dbg (self, "GPS engine stop request accepted");
}
static void
gps_engine_stop (GTask *task)
{
MMSharedXmm *self;
Private *priv;
GpsEngineSelectContext *ctx;
self = g_task_get_source_object (task);
priv = get_private (self);
ctx = g_task_get_task_data (task);
g_assert (priv->gps_port);
/* After a +XLSRSTOP command the modem will reply OK to report that the stop
* request has been received, but at this point the engine may still be on.
* We must wait for the additional +XLSRSTOP URC to tell us that the engine
* is really off before going on.
*
* We do this by setting up a temporary regex match for the URC during this
* operation, and also by configuring a timeout so that we don't wait forever
* for the URC.
*
* The initial +XLSRSTOP response will be ignored unless an error is being
* reported.
*
* The operation task is kept in private info because it will be shared by all
* the possible paths that may complete it (response, URC, timeout).
*/
if (priv->pending_gps_engine_stop_task) {
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE,
"An engine stop task is already ongoing");
g_object_unref (task);
return;
}
priv->pending_gps_engine_stop_task = task;
mm_obj_dbg (self, "launching GPS engine stop operation...");
ctx->engine_stop_timeout_id = g_timeout_add_seconds (GPS_ENGINE_STOP_TIMEOUT_SECS,
(GSourceFunc) xlsrstop_urc_timeout,
self);
mm_port_serial_at_add_unsolicited_msg_handler (
priv->gps_port,
priv->xlsrstop_regex,
(MMPortSerialAtUnsolicitedMsgFn)xlsrstop_urc_received,
self,
NULL);
mm_base_modem_at_command_full (MM_BASE_MODEM (self),
priv->gps_port,
"+XLSRSTOP",
3,
FALSE,
FALSE, /* raw */
NULL, /* cancellable */
(GAsyncReadyCallback)xlsrstop_ready,
NULL);
}
static void
gps_engine_state_select (MMSharedXmm *self,
GpsEngineState state,
GAsyncReadyCallback callback,
gpointer user_data)
{
GpsEngineSelectContext *ctx;
GTask *task;
Private *priv;
priv = get_private (self);
task = g_task_new (self, NULL, callback, user_data);
ctx = g_slice_new0 (GpsEngineSelectContext);
ctx->state = state;
g_task_set_task_data (task, ctx, (GDestroyNotify)gps_engine_select_context_free);
/* 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 = NULL;
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 = NULL;
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;
g_autoptr(MMPortSerialAt) gps_port = NULL;
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);
/* Then, setup the GPS port */
gps_port = shared_xmm_get_gps_control_port (MM_SHARED_XMM (self), NULL);
if (gps_port) {
/* 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 (
gps_port,
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),
gps_port,
"+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;
}