To be able to have quick position fix GNSS engine needs to know - predicted orbits data, - current time, - approximate position. Modem sends indications with requests, when it needs corresponding information. Predicted orbits can be injected using 'mmcli -m any --location-inject-assistance-data=<file>'. But to accelerate time to first fix after cold start extra information is needed like time and coarse position. Inject position requests are received, when GPS session is initiated using QMI_LOC_START. In inject position request modem sends information about its last known position. If last known position is zero, when this is a cold start and inject position is needed to improve TTFF. Last position from the modem is cached in ModemManager and is injected next time, when modem requests it. For example, if modem is restarted. When timestamp on injected position is too old, than position is ignored and location engine does cold start. Therefore current time is used for injected position. When injected position is far away the actual one (for example, after flight), after some time location engine falls back to cold start. Tested on Quectel EG25-G. Signed-off-by: Andrey Skvortsov <andrej.skvortzov@gmail.com>
7666 lines
282 KiB
C
7666 lines
282 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>
|
|
* Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <glib-object.h>
|
|
#include <gio/gio.h>
|
|
|
|
#define _LIBMM_INSIDE_MM
|
|
#include <libmm-glib.h>
|
|
|
|
#include <libqmi-glib.h>
|
|
|
|
#include "mm-log-object.h"
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-iface-modem-3gpp.h"
|
|
#include "mm-iface-modem-location.h"
|
|
#include "mm-location-cache.h"
|
|
#include "mm-sim-qmi.h"
|
|
#include "mm-shared-qmi.h"
|
|
#include "mm-modem-helpers-qmi.h"
|
|
|
|
/* Default session id to use in LOC operations */
|
|
#define DEFAULT_LOC_SESSION_ID 0x10
|
|
|
|
/* Default description for the default configuration of the firmware */
|
|
#define DEFAULT_CONFIG_DESCRIPTION "default"
|
|
|
|
/*****************************************************************************/
|
|
/* Private data context */
|
|
|
|
#define PRIVATE_TAG "shared-qmi-private-tag"
|
|
static GQuark private_quark;
|
|
|
|
typedef enum {
|
|
FEATURE_UNKNOWN,
|
|
FEATURE_UNSUPPORTED,
|
|
FEATURE_SUPPORTED,
|
|
} Feature;
|
|
|
|
typedef struct {
|
|
GArray *id;
|
|
QmiPdcConfigurationType config_type;
|
|
guint32 token;
|
|
guint32 version;
|
|
gchar *description;
|
|
guint32 total_size;
|
|
} ConfigInfo;
|
|
|
|
static void
|
|
config_info_clear (ConfigInfo *config_info)
|
|
{
|
|
g_array_unref (config_info->id);
|
|
g_free (config_info->description);
|
|
}
|
|
|
|
typedef struct {
|
|
/* Capabilities & modes helpers */
|
|
gboolean multimode;
|
|
MMModemCapability current_capabilities;
|
|
GArray *supported_radio_interfaces;
|
|
Feature feature_nas_tp;
|
|
Feature feature_nas_ssp;
|
|
Feature feature_nas_ssp_extended_lte_band_preference;
|
|
Feature feature_nas_ssp_acquisition_order_preference;
|
|
GArray *feature_nas_ssp_acquisition_order_preference_array;
|
|
GArray *supported_bands;
|
|
|
|
/* Location helpers */
|
|
MMIfaceModemLocationInterface *iface_modem_location_parent;
|
|
MMModemLocationSource enabled_sources;
|
|
QmiClient *pds_client;
|
|
gulong pds_location_event_report_indication_id;
|
|
QmiClient *loc_client;
|
|
gulong loc_location_nmea_indication_id;
|
|
gchar **loc_assistance_data_servers;
|
|
guint32 loc_assistance_data_max_file_size;
|
|
guint32 loc_assistance_data_max_part_size;
|
|
|
|
gulong loc_assistance_inject_time_req_indication_id;
|
|
gulong loc_assistance_inject_position_req_indication_id;
|
|
|
|
/* Carrier config helpers */
|
|
gboolean config_active_default;
|
|
GArray *config_list;
|
|
gint config_active_i;
|
|
|
|
/* Slot status monitoring */
|
|
GArray *slots_status;
|
|
QmiClient *uim_client;
|
|
gulong uim_slot_status_indication_id;
|
|
gulong uim_refresh_indication_id;
|
|
guint uim_refresh_start_timeout_id;
|
|
|
|
/* Cached last location from nmea */
|
|
MMLocationCache *location_cache;
|
|
} Private;
|
|
|
|
static void
|
|
private_free (Private *priv)
|
|
{
|
|
if (priv->config_list)
|
|
g_array_unref (priv->config_list);
|
|
if (priv->supported_bands)
|
|
g_array_unref (priv->supported_bands);
|
|
if (priv->supported_radio_interfaces)
|
|
g_array_unref (priv->supported_radio_interfaces);
|
|
if (priv->pds_location_event_report_indication_id)
|
|
g_signal_handler_disconnect (priv->pds_client, priv->pds_location_event_report_indication_id);
|
|
if (priv->pds_client)
|
|
g_object_unref (priv->pds_client);
|
|
if (priv->loc_location_nmea_indication_id)
|
|
g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id);
|
|
if (priv->loc_assistance_inject_time_req_indication_id)
|
|
g_signal_handler_disconnect (priv->loc_client, priv->loc_assistance_inject_time_req_indication_id);
|
|
if (priv->loc_assistance_inject_position_req_indication_id)
|
|
g_signal_handler_disconnect (priv->loc_client, priv->loc_assistance_inject_position_req_indication_id);
|
|
if (priv->loc_client)
|
|
g_object_unref (priv->loc_client);
|
|
if (priv->uim_slot_status_indication_id)
|
|
g_signal_handler_disconnect (priv->uim_client, priv->uim_slot_status_indication_id);
|
|
if (priv->slots_status)
|
|
g_array_unref (priv->slots_status);
|
|
if (priv->uim_refresh_indication_id)
|
|
g_signal_handler_disconnect (priv->uim_client, priv->uim_refresh_indication_id);
|
|
if (priv->uim_client)
|
|
g_object_unref (priv->uim_client);
|
|
if (priv->uim_refresh_start_timeout_id)
|
|
g_source_remove (priv->uim_refresh_start_timeout_id);
|
|
g_clear_object (&priv->location_cache);
|
|
if (priv->feature_nas_ssp_acquisition_order_preference_array)
|
|
g_array_unref (priv->feature_nas_ssp_acquisition_order_preference_array);
|
|
g_strfreev (priv->loc_assistance_data_servers);
|
|
g_slice_free (Private, priv);
|
|
}
|
|
|
|
static Private *
|
|
get_private (MMSharedQmi *self)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
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->feature_nas_tp = FEATURE_UNKNOWN;
|
|
priv->feature_nas_ssp = FEATURE_UNKNOWN;
|
|
priv->feature_nas_ssp_extended_lte_band_preference = FEATURE_UNKNOWN;
|
|
priv->feature_nas_ssp_acquisition_order_preference = FEATURE_UNKNOWN;
|
|
priv->config_active_i = -1;
|
|
|
|
priv->location_cache = mm_location_cache_new ();
|
|
if (!mm_location_cache_load (priv->location_cache, &error))
|
|
mm_obj_warn (priv->location_cache, "%s", error->message);
|
|
|
|
/* Setup parent class' MMIfaceModemLocation */
|
|
g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface);
|
|
priv->iface_modem_location_parent = MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface (self);
|
|
|
|
g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
|
|
}
|
|
|
|
return priv;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Register in network (3GPP interface) */
|
|
|
|
/* wait this amount of time at most if we don't get the serving system
|
|
* indication earlier */
|
|
#define REGISTER_IN_NETWORK_TIMEOUT_SECS 25
|
|
|
|
typedef struct {
|
|
guint timeout_id;
|
|
gulong serving_system_indication_id;
|
|
GCancellable *cancellable;
|
|
gulong cancellable_id;
|
|
QmiClientNas *client;
|
|
} RegisterInNetworkContext;
|
|
|
|
static void
|
|
register_in_network_context_free (RegisterInNetworkContext *ctx)
|
|
{
|
|
g_assert (!ctx->cancellable_id);
|
|
g_assert (!ctx->timeout_id);
|
|
if (ctx->client) {
|
|
g_assert (!ctx->serving_system_indication_id);
|
|
g_object_unref (ctx->client);
|
|
}
|
|
g_clear_object (&ctx->cancellable);
|
|
g_slice_free (RegisterInNetworkContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_3gpp_register_in_network_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
register_in_network_cancelled (GCancellable *cancellable,
|
|
GTask *task)
|
|
{
|
|
RegisterInNetworkContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_assert (ctx->cancellable);
|
|
if (ctx->cancellable_id)
|
|
ctx->cancellable_id = 0;
|
|
|
|
g_assert (ctx->timeout_id);
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_assert (ctx->client);
|
|
g_assert (ctx->serving_system_indication_id);
|
|
g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id);
|
|
ctx->serving_system_indication_id = 0;
|
|
|
|
g_task_return_error_if_cancelled (task);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
register_in_network_timeout (GTask *task)
|
|
{
|
|
RegisterInNetworkContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_assert (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_assert (ctx->client);
|
|
g_assert (ctx->serving_system_indication_id);
|
|
g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id);
|
|
ctx->serving_system_indication_id = 0;
|
|
|
|
if (ctx->cancellable && ctx->cancellable_id) {
|
|
g_cancellable_disconnect (ctx->cancellable, ctx->cancellable_id);
|
|
ctx->cancellable_id = 0;
|
|
}
|
|
|
|
/* the 3GPP interface will take care of checking if the registration is
|
|
* the one we asked for */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
register_in_network_ready (GTask *task,
|
|
QmiIndicationNasServingSystemOutput *output)
|
|
{
|
|
RegisterInNetworkContext *ctx;
|
|
QmiNasRegistrationState registration_state;
|
|
|
|
/* ignore indication updates reporting "searching" */
|
|
qmi_indication_nas_serving_system_output_get_serving_system (
|
|
output,
|
|
®istration_state,
|
|
NULL, /* cs_attach_state */
|
|
NULL, /* ps_attach_state */
|
|
NULL, /* selected_network */
|
|
NULL, /* radio_interfaces */
|
|
NULL);
|
|
if (registration_state == QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
|
|
return;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_assert (ctx->client);
|
|
g_assert (ctx->serving_system_indication_id);
|
|
g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id);
|
|
ctx->serving_system_indication_id = 0;
|
|
|
|
g_assert (ctx->timeout_id);
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
if (ctx->cancellable && ctx->cancellable_id) {
|
|
g_cancellable_disconnect (ctx->cancellable, ctx->cancellable_id);
|
|
ctx->cancellable_id = 0;
|
|
}
|
|
|
|
/* the 3GPP interface will take care of checking if the registration is
|
|
* the one we asked for */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
initiate_network_register_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
QmiMessageNasInitiateNetworkRegisterOutput *output;
|
|
RegisterInNetworkContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_nas_initiate_network_register_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_initiate_network_register_output_get_result (output, &error)) {
|
|
/* No effect would mean we're already in the desired network */
|
|
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_error_free (error);
|
|
} else {
|
|
g_prefix_error (&error, "Couldn't initiate network register: ");
|
|
g_task_return_error (task, error);
|
|
}
|
|
g_object_unref (task);
|
|
goto out;
|
|
}
|
|
|
|
/* Registration attempt started, now we need to monitor "serving system" indications
|
|
* to get notified when the registration changed. Note that we won't need to process
|
|
* the indication, because we already have that logic setup (and it runs before this
|
|
* new signal handler), we just need to get notified of when it happens. We will also
|
|
* setup a maximum operation timeuot plus a cancellability point, as this operation
|
|
* may be explicitly cancelled by the 3GPP interface if a new registration request
|
|
* arrives while the current one is being processed.
|
|
*
|
|
* Task is shared among cancellable, indication and timeout. The first one triggered
|
|
* will cancel the others.
|
|
*/
|
|
|
|
ctx->serving_system_indication_id = g_signal_connect_swapped (client,
|
|
"serving-system",
|
|
G_CALLBACK (register_in_network_ready),
|
|
task);
|
|
|
|
ctx->timeout_id = g_timeout_add_seconds (REGISTER_IN_NETWORK_TIMEOUT_SECS,
|
|
(GSourceFunc) register_in_network_timeout,
|
|
task);
|
|
|
|
/* The cancellable may already be cancelled, and if so the given callback will be called
|
|
* right away. So make sure this cancellable is always configured last, so that it clears the
|
|
* timeout or signal handler upon early cancellation. */
|
|
if (ctx->cancellable)
|
|
ctx->cancellable_id = g_cancellable_connect (ctx->cancellable,
|
|
G_CALLBACK (register_in_network_cancelled),
|
|
task,
|
|
NULL);
|
|
|
|
out:
|
|
|
|
if (output)
|
|
qmi_message_nas_initiate_network_register_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
register_in_network_inr (GTask *task,
|
|
QmiClient *client,
|
|
GCancellable *cancellable,
|
|
guint16 mcc,
|
|
guint16 mnc,
|
|
gboolean mnc_pcs_digit)
|
|
{
|
|
QmiMessageNasInitiateNetworkRegisterInput *input;
|
|
|
|
input = qmi_message_nas_initiate_network_register_input_new ();
|
|
|
|
if (mcc) {
|
|
/* If the user sent a specific network to use, lock it in. */
|
|
qmi_message_nas_initiate_network_register_input_set_action (
|
|
input,
|
|
QMI_NAS_NETWORK_REGISTER_TYPE_MANUAL,
|
|
NULL);
|
|
qmi_message_nas_initiate_network_register_input_set_manual_registration_info_3gpp (
|
|
input,
|
|
mcc,
|
|
mnc,
|
|
QMI_NAS_RADIO_INTERFACE_UNKNOWN, /* don't change radio interface */
|
|
NULL);
|
|
if (mnc_pcs_digit && mnc < 100)
|
|
qmi_message_nas_initiate_network_register_input_set_mnc_pcs_digit_include_status (
|
|
input,
|
|
mnc_pcs_digit,
|
|
NULL
|
|
);
|
|
} else {
|
|
/* Otherwise, automatic registration */
|
|
qmi_message_nas_initiate_network_register_input_set_action (
|
|
input,
|
|
QMI_NAS_NETWORK_REGISTER_TYPE_AUTOMATIC,
|
|
NULL);
|
|
}
|
|
|
|
qmi_client_nas_initiate_network_register (
|
|
QMI_CLIENT_NAS (client),
|
|
input,
|
|
120,
|
|
cancellable,
|
|
(GAsyncReadyCallback)initiate_network_register_ready,
|
|
task);
|
|
|
|
qmi_message_nas_initiate_network_register_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
set_system_selection_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
QmiMessageNasSetSystemSelectionPreferenceOutput *output;
|
|
|
|
output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) {
|
|
if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_prefix_error (&error, "Couldn't set network selection preference: ");
|
|
g_task_return_error (task, error);
|
|
goto out;
|
|
}
|
|
g_error_free (error);
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
out:
|
|
g_object_unref (task);
|
|
|
|
if (output)
|
|
qmi_message_nas_set_system_selection_preference_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
register_in_network_sssp (GTask *task,
|
|
QmiClient *client,
|
|
GCancellable *cancellable,
|
|
guint16 mcc,
|
|
guint16 mnc,
|
|
gboolean mnc_pcs_digit)
|
|
{
|
|
QmiMessageNasSetSystemSelectionPreferenceInput *input;
|
|
|
|
input = qmi_message_nas_set_system_selection_preference_input_new ();
|
|
|
|
qmi_message_nas_set_system_selection_preference_input_set_network_selection_preference (
|
|
input,
|
|
mcc ? QMI_NAS_NETWORK_SELECTION_PREFERENCE_MANUAL : QMI_NAS_NETWORK_SELECTION_PREFERENCE_AUTOMATIC,
|
|
mcc,
|
|
mnc,
|
|
NULL);
|
|
|
|
if (mnc_pcs_digit && mnc < 100)
|
|
qmi_message_nas_set_system_selection_preference_input_set_mnc_pcs_digit_include_status (
|
|
input,
|
|
mnc_pcs_digit,
|
|
NULL
|
|
);
|
|
|
|
qmi_client_nas_set_system_selection_preference (
|
|
QMI_CLIENT_NAS (client),
|
|
input,
|
|
120,
|
|
cancellable,
|
|
(GAsyncReadyCallback)set_system_selection_preference_ready,
|
|
task);
|
|
|
|
qmi_message_nas_set_system_selection_preference_input_unref (input);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_3gpp_register_in_network (MMIfaceModem3gpp *self,
|
|
const gchar *operator_id,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
RegisterInNetworkContext *ctx;
|
|
guint16 mcc = 0;
|
|
guint16 mnc = 0;
|
|
gboolean mnc_pcs_digit = FALSE;
|
|
QmiClient *client = NULL;
|
|
GError *error = NULL;
|
|
Private *priv = NULL;
|
|
|
|
/* Get NAS client */
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (RegisterInNetworkContext);
|
|
ctx->client = QMI_CLIENT_NAS (g_object_ref (client));
|
|
ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)register_in_network_context_free);
|
|
|
|
/* Parse input MCC/MNC */
|
|
if (operator_id && !mm_3gpp_parse_operator_id (operator_id, &mcc, &mnc, &mnc_pcs_digit, &error)) {
|
|
g_assert (error != NULL);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
if (priv->feature_nas_ssp == FEATURE_SUPPORTED)
|
|
register_in_network_sssp (task, client, cancellable, mcc, mnc, mnc_pcs_digit);
|
|
else
|
|
register_in_network_inr (task, client, cancellable, mcc, mnc, mnc_pcs_digit);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Current capabilities setting (Modem interface) */
|
|
|
|
typedef enum {
|
|
SET_CURRENT_CAPABILITIES_STEP_FIRST,
|
|
SET_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE,
|
|
SET_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE,
|
|
SET_CURRENT_CAPABILITIES_STEP_RESET,
|
|
SET_CURRENT_CAPABILITIES_STEP_LAST,
|
|
} SetCurrentCapabilitiesStep;
|
|
|
|
typedef struct {
|
|
QmiClientNas *client;
|
|
MMModemCapability capabilities;
|
|
gboolean capabilities_updated;
|
|
SetCurrentCapabilitiesStep step;
|
|
} SetCurrentCapabilitiesContext;
|
|
|
|
static void
|
|
set_current_capabilities_context_free (SetCurrentCapabilitiesContext *ctx)
|
|
{
|
|
g_object_unref (ctx->client);
|
|
g_slice_free (SetCurrentCapabilitiesContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_set_current_capabilities_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void set_current_capabilities_step (GTask *task);
|
|
|
|
static void
|
|
set_current_capabilities_reset_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetCurrentCapabilitiesContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!mm_shared_qmi_reset_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->step++;
|
|
set_current_capabilities_step (task);
|
|
}
|
|
|
|
static void
|
|
set_current_capabilities_set_technology_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetCurrentCapabilitiesContext *ctx;
|
|
QmiMessageNasSetTechnologyPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_nas_set_technology_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_set_technology_preference_output_get_result (output, &error)) {
|
|
/* A no-effect error here is not a real error */
|
|
if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
goto out;
|
|
}
|
|
/* no effect, just end operation without reset */
|
|
g_clear_error (&error);
|
|
ctx->step = SET_CURRENT_CAPABILITIES_STEP_LAST;
|
|
set_current_capabilities_step (task);
|
|
goto out;
|
|
}
|
|
|
|
/* success! */
|
|
ctx->step = SET_CURRENT_CAPABILITIES_STEP_RESET;
|
|
set_current_capabilities_step (task);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_nas_set_technology_preference_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
set_current_capabilities_technology_preference (GTask *task)
|
|
{
|
|
SetCurrentCapabilitiesContext *ctx;
|
|
QmiMessageNasSetTechnologyPreferenceInput *input;
|
|
QmiNasRadioTechnologyPreference pref;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
pref = mm_modem_capability_to_qmi_radio_technology_preference (ctx->capabilities);
|
|
if (!pref) {
|
|
gchar *str;
|
|
|
|
str = mm_modem_capability_build_string_from_mask (ctx->capabilities);
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Unhandled capabilities setting: '%s'",
|
|
str);
|
|
g_object_unref (task);
|
|
g_free (str);
|
|
return;
|
|
}
|
|
|
|
input = qmi_message_nas_set_technology_preference_input_new ();
|
|
qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL);
|
|
|
|
qmi_client_nas_set_technology_preference (
|
|
ctx->client,
|
|
input,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)set_current_capabilities_set_technology_preference_ready,
|
|
task);
|
|
qmi_message_nas_set_technology_preference_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
set_current_capabilities_set_system_selection_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetCurrentCapabilitiesContext *ctx;
|
|
QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
goto out;
|
|
}
|
|
|
|
/* success! */
|
|
ctx->step = SET_CURRENT_CAPABILITIES_STEP_RESET;
|
|
set_current_capabilities_step (task);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_nas_set_system_selection_preference_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
set_current_capabilities_system_selection_preference (GTask *task)
|
|
{
|
|
SetCurrentCapabilitiesContext *ctx;
|
|
QmiMessageNasSetSystemSelectionPreferenceInput *input;
|
|
QmiNasRatModePreference pref;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
pref = mm_modem_capability_to_qmi_rat_mode_preference (ctx->capabilities);
|
|
if (!pref) {
|
|
gchar *str;
|
|
|
|
str = mm_modem_capability_build_string_from_mask (ctx->capabilities);
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Unhandled capabilities setting: '%s'",
|
|
str);
|
|
g_object_unref (task);
|
|
g_free (str);
|
|
return;
|
|
}
|
|
|
|
input = qmi_message_nas_set_system_selection_preference_input_new ();
|
|
qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL);
|
|
qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL);
|
|
|
|
qmi_client_nas_set_system_selection_preference (
|
|
ctx->client,
|
|
input,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)set_current_capabilities_set_system_selection_preference_ready,
|
|
task);
|
|
qmi_message_nas_set_system_selection_preference_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
set_current_capabilities_step (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
SetCurrentCapabilitiesContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case SET_CURRENT_CAPABILITIES_STEP_FIRST:
|
|
/* Error out early if both unsupported */
|
|
if ((priv->feature_nas_ssp != FEATURE_SUPPORTED) &&
|
|
(priv->feature_nas_tp != FEATURE_SUPPORTED)) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Setting capabilities is not supported by this device");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SET_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE:
|
|
if (priv->feature_nas_ssp == FEATURE_SUPPORTED) {
|
|
set_current_capabilities_system_selection_preference (task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SET_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE:
|
|
if (priv->feature_nas_tp == FEATURE_SUPPORTED) {
|
|
set_current_capabilities_technology_preference (task);
|
|
return;
|
|
}
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SET_CURRENT_CAPABILITIES_STEP_RESET:
|
|
mm_shared_qmi_reset (MM_IFACE_MODEM (self),
|
|
(GAsyncReadyCallback)set_current_capabilities_reset_ready,
|
|
task);
|
|
return;
|
|
|
|
case SET_CURRENT_CAPABILITIES_STEP_LAST:
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_set_current_capabilities (MMIfaceModem *self,
|
|
MMModemCapability capabilities,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
Private *priv;
|
|
SetCurrentCapabilitiesContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
g_assert (priv->feature_nas_tp != FEATURE_UNKNOWN);
|
|
g_assert (priv->feature_nas_ssp != FEATURE_UNKNOWN);
|
|
|
|
ctx = g_slice_new0 (SetCurrentCapabilitiesContext);
|
|
ctx->client = QMI_CLIENT_NAS (g_object_ref (client));
|
|
ctx->capabilities = capabilities;
|
|
ctx->step = SET_CURRENT_CAPABILITIES_STEP_FIRST;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_capabilities_context_free);
|
|
|
|
set_current_capabilities_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Current capabilities (Modem interface) */
|
|
|
|
typedef enum {
|
|
LOAD_CURRENT_CAPABILITIES_STEP_FIRST,
|
|
LOAD_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE,
|
|
LOAD_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE,
|
|
LOAD_CURRENT_CAPABILITIES_STEP_DMS_GET_CAPABILITIES,
|
|
LOAD_CURRENT_CAPABILITIES_STEP_LAST,
|
|
} LoadCurrentCapabilitiesStep;
|
|
|
|
typedef struct {
|
|
QmiClientNas *nas_client;
|
|
QmiClientDms *dms_client;
|
|
LoadCurrentCapabilitiesStep step;
|
|
MMQmiCurrentCapabilitiesContext capabilities_context;
|
|
} LoadCurrentCapabilitiesContext;
|
|
|
|
MMModemCapability
|
|
mm_shared_qmi_load_current_capabilities_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_CAPABILITY_NONE;
|
|
}
|
|
return (MMModemCapability)value;
|
|
}
|
|
|
|
static void
|
|
load_current_capabilities_context_free (LoadCurrentCapabilitiesContext *ctx)
|
|
{
|
|
g_object_unref (ctx->nas_client);
|
|
g_object_unref (ctx->dms_client);
|
|
g_slice_free (LoadCurrentCapabilitiesContext, ctx);
|
|
}
|
|
|
|
static void load_current_capabilities_step (GTask *task);
|
|
|
|
static void
|
|
load_current_capabilities_get_capabilities_ready (QmiClientDms *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
LoadCurrentCapabilitiesContext *ctx;
|
|
QmiMessageDmsGetCapabilitiesOutput *output = NULL;
|
|
GError *error = NULL;
|
|
guint i;
|
|
GArray *radio_interface_list;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_dms_get_capabilities_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't get Capabilities: ");
|
|
goto out;
|
|
}
|
|
|
|
qmi_message_dms_get_capabilities_output_get_info (
|
|
output,
|
|
NULL, /* info_max_tx_channel_rate */
|
|
NULL, /* info_max_rx_channel_rate */
|
|
NULL, /* info_data_service_capability */
|
|
NULL, /* info_sim_capability */
|
|
&radio_interface_list,
|
|
NULL);
|
|
|
|
/* Cache supported radio interfaces */
|
|
g_assert (!priv->supported_radio_interfaces);
|
|
priv->supported_radio_interfaces = g_array_ref (radio_interface_list);
|
|
|
|
for (i = 0; i < radio_interface_list->len; i++)
|
|
ctx->capabilities_context.dms_capabilities |=
|
|
mm_modem_capability_from_qmi_radio_interface (g_array_index (radio_interface_list, QmiDmsRadioInterface, i), self);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_dms_get_capabilities_output_unref (output);
|
|
|
|
/* Failure in DMS Get Capabilities is fatal */
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->step++;
|
|
load_current_capabilities_step (task);
|
|
}
|
|
|
|
static void
|
|
load_current_capabilities_get_technology_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
LoadCurrentCapabilitiesContext *ctx;
|
|
QmiMessageNasGetTechnologyPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_nas_get_technology_preference_finish (client, res, &error);
|
|
if (!output) {
|
|
mm_obj_dbg (self, "QMI operation failed: %s", error->message);
|
|
g_error_free (error);
|
|
priv->feature_nas_tp = FEATURE_UNSUPPORTED;
|
|
} else if (!qmi_message_nas_get_technology_preference_output_get_result (output, &error)) {
|
|
mm_obj_dbg (self, "couldn't get technology preference: %s", error->message);
|
|
g_error_free (error);
|
|
priv->feature_nas_tp = FEATURE_UNSUPPORTED;
|
|
} else {
|
|
qmi_message_nas_get_technology_preference_output_get_active (
|
|
output,
|
|
&ctx->capabilities_context.nas_tp_mask,
|
|
NULL, /* duration */
|
|
NULL);
|
|
priv->feature_nas_tp = FEATURE_SUPPORTED;
|
|
}
|
|
|
|
if (output)
|
|
qmi_message_nas_get_technology_preference_output_unref (output);
|
|
|
|
ctx->step++;
|
|
load_current_capabilities_step (task);
|
|
}
|
|
|
|
static void
|
|
load_current_capabilities_get_system_selection_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
LoadCurrentCapabilitiesContext *ctx;
|
|
QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
priv->feature_nas_ssp = FEATURE_UNSUPPORTED;
|
|
priv->feature_nas_ssp_extended_lte_band_preference = FEATURE_UNSUPPORTED;
|
|
priv->feature_nas_ssp_acquisition_order_preference = FEATURE_UNSUPPORTED;
|
|
|
|
output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error);
|
|
if (!output) {
|
|
mm_obj_dbg (self, "QMI operation failed: %s", error->message);
|
|
g_error_free (error);
|
|
} else if (!qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) {
|
|
mm_obj_dbg (self, "couldn't get system selection preference: %s", error->message);
|
|
g_error_free (error);
|
|
} else {
|
|
GArray *acquisition_order_preference_array = NULL;
|
|
|
|
/* SSP is supported, perform feature checks */
|
|
priv->feature_nas_ssp = FEATURE_SUPPORTED;
|
|
if (qmi_message_nas_get_system_selection_preference_output_get_extended_lte_band_preference (output, NULL, NULL, NULL, NULL, NULL))
|
|
priv->feature_nas_ssp_extended_lte_band_preference = FEATURE_SUPPORTED;
|
|
if (qmi_message_nas_get_system_selection_preference_output_get_acquisition_order_preference (output, &acquisition_order_preference_array, NULL) &&
|
|
acquisition_order_preference_array &&
|
|
acquisition_order_preference_array->len) {
|
|
priv->feature_nas_ssp_acquisition_order_preference = FEATURE_SUPPORTED;
|
|
priv->feature_nas_ssp_acquisition_order_preference_array = g_array_ref (acquisition_order_preference_array);
|
|
}
|
|
|
|
qmi_message_nas_get_system_selection_preference_output_get_mode_preference (
|
|
output,
|
|
&ctx->capabilities_context.nas_ssp_mode_preference_mask,
|
|
NULL);
|
|
}
|
|
|
|
if (output)
|
|
qmi_message_nas_get_system_selection_preference_output_unref (output);
|
|
|
|
ctx->step++;
|
|
load_current_capabilities_step (task);
|
|
}
|
|
|
|
static void
|
|
load_current_capabilities_step (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
LoadCurrentCapabilitiesContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case LOAD_CURRENT_CAPABILITIES_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case LOAD_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE:
|
|
qmi_client_nas_get_system_selection_preference (
|
|
ctx->nas_client, NULL, 5, NULL,
|
|
(GAsyncReadyCallback)load_current_capabilities_get_system_selection_preference_ready,
|
|
task);
|
|
return;
|
|
|
|
case LOAD_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE:
|
|
qmi_client_nas_get_technology_preference (
|
|
ctx->nas_client, NULL, 5, NULL,
|
|
(GAsyncReadyCallback)load_current_capabilities_get_technology_preference_ready,
|
|
task);
|
|
return;
|
|
|
|
case LOAD_CURRENT_CAPABILITIES_STEP_DMS_GET_CAPABILITIES:
|
|
qmi_client_dms_get_capabilities (
|
|
ctx->dms_client, NULL, 5, NULL,
|
|
(GAsyncReadyCallback)load_current_capabilities_get_capabilities_ready,
|
|
task);
|
|
return;
|
|
|
|
case LOAD_CURRENT_CAPABILITIES_STEP_LAST:
|
|
g_assert (priv->feature_nas_tp != FEATURE_UNKNOWN);
|
|
g_assert (priv->feature_nas_ssp != FEATURE_UNKNOWN);
|
|
|
|
/* At this point we can already know if this is a multimode device or not */
|
|
if ((ctx->capabilities_context.dms_capabilities & MM_MODEM_CAPABILITY_MULTIMODE) == MM_MODEM_CAPABILITY_MULTIMODE)
|
|
priv->multimode = ctx->capabilities_context.multimode = TRUE;
|
|
|
|
priv->current_capabilities = mm_current_capability_from_qmi_current_capabilities_context (&ctx->capabilities_context, self);
|
|
|
|
if (priv->current_capabilities == MM_MODEM_CAPABILITY_NONE)
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"modem has no current capabilities");
|
|
else
|
|
g_task_return_int (task, priv->current_capabilities);
|
|
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_current_capabilities (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
LoadCurrentCapabilitiesContext *ctx;
|
|
GTask *task;
|
|
QmiClient *nas_client = NULL;
|
|
QmiClient *dms_client = NULL;
|
|
Private *priv;
|
|
|
|
/*
|
|
* We assume that DMS Get Capabilities reports always the same result,
|
|
* that will include all capabilities supported by the device regardless
|
|
* of which ones are configured at the moment. E.g. for the Load Supported
|
|
* Capabilities we base the logic exclusively on this method's output.
|
|
*
|
|
* We then consider 3 different cases:
|
|
* a) If the device supports NAS System Selection Preference, we use the
|
|
* "mode preference" TLV to select currently enabled capabilities.
|
|
* b) If the device supports NAS Technology Preference (older devices),
|
|
* we use this method to select currently enabled capabilities.
|
|
* c) If none of those messages is supported we don't allow switching
|
|
* capabilities.
|
|
*/
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &nas_client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_DMS, &dms_client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
/* Current capabilities is the first thing run, and will only be run once per modem,
|
|
* so we should here check support for the optional features. */
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
g_assert (priv->feature_nas_tp == FEATURE_UNKNOWN);
|
|
g_assert (priv->feature_nas_ssp == FEATURE_UNKNOWN);
|
|
|
|
ctx = g_slice_new0 (LoadCurrentCapabilitiesContext);
|
|
ctx->nas_client = QMI_CLIENT_NAS (g_object_ref (nas_client));
|
|
ctx->dms_client = QMI_CLIENT_DMS (g_object_ref (dms_client));
|
|
ctx->step = LOAD_CURRENT_CAPABILITIES_STEP_FIRST;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_current_capabilities_context_free);
|
|
|
|
load_current_capabilities_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Supported capabilities (Modem interface) */
|
|
|
|
GArray *
|
|
mm_shared_qmi_load_supported_capabilities_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_supported_capabilities (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
GArray *supported_combinations;
|
|
guint i;
|
|
MMQmiSupportedCapabilitiesContext ctx = { 0 };
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* List of radio interfaces preloaded in current capabilities */
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
if (!priv->supported_radio_interfaces) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"cannot load current capabilities without radio interface information");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Build mask with all supported capabilities */
|
|
ctx.dms_capabilities = MM_MODEM_CAPABILITY_NONE;
|
|
for (i = 0; i < priv->supported_radio_interfaces->len; i++)
|
|
ctx.dms_capabilities |= mm_modem_capability_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
|
|
|
|
ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED);
|
|
ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED);
|
|
ctx.multimode = priv->multimode;
|
|
|
|
/* Build list of supported combinations */
|
|
supported_combinations = mm_supported_capabilities_from_qmi_supported_capabilities_context (&ctx, self);
|
|
g_task_return_pointer (task, supported_combinations, (GDestroyNotify) g_array_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load model (Modem interface) */
|
|
|
|
gchar *
|
|
mm_shared_qmi_load_model_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
dms_get_model_ready (QmiClientDms *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageDmsGetModelOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_dms_get_model_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
} else if (!qmi_message_dms_get_model_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't get Model: ");
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
const gchar *str;
|
|
|
|
qmi_message_dms_get_model_output_get_model (output, &str, NULL);
|
|
g_task_return_pointer (task, g_strdup (str), g_free);
|
|
}
|
|
|
|
if (output)
|
|
qmi_message_dms_get_model_output_unref (output);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_model (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
QmiClient *client = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_DMS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
mm_obj_dbg (self, "loading model...");
|
|
qmi_client_dms_get_model (QMI_CLIENT_DMS (client),
|
|
NULL,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)dms_get_model_ready,
|
|
g_task_new (self, NULL, callback, user_data));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Allowed modes setting (Modem interface) */
|
|
|
|
typedef struct {
|
|
QmiClientNas *client;
|
|
MMModemMode allowed;
|
|
MMModemMode preferred;
|
|
} SetCurrentModesContext;
|
|
|
|
static void
|
|
set_current_modes_context_free (SetCurrentModesContext *ctx)
|
|
{
|
|
g_object_unref (ctx->client);
|
|
g_slice_free (SetCurrentModesContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_set_current_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
set_current_modes_technology_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageNasSetTechnologyPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_nas_set_technology_preference_finish (client, res, &error);
|
|
if (!output ||
|
|
(!qmi_message_nas_set_technology_preference_output_get_result (output, &error) &&
|
|
!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT))) {
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
g_clear_error (&error);
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
g_object_unref (task);
|
|
|
|
if (output)
|
|
qmi_message_nas_set_technology_preference_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
set_current_modes_technology_preference (GTask *task)
|
|
{
|
|
MMIfaceModem *self;
|
|
SetCurrentModesContext *ctx;
|
|
QmiMessageNasSetTechnologyPreferenceInput *input;
|
|
QmiNasRadioTechnologyPreference pref;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->preferred != MM_MODEM_MODE_NONE) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Cannot set specific preferred mode");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
pref = mm_modem_mode_to_qmi_radio_technology_preference (ctx->allowed, mm_iface_modem_is_cdma (self));
|
|
if (!pref) {
|
|
gchar *str;
|
|
|
|
str = mm_modem_mode_build_string_from_mask (ctx->allowed);
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Unhandled allowed mode setting: '%s'",
|
|
str);
|
|
g_object_unref (task);
|
|
g_free (str);
|
|
return;
|
|
}
|
|
|
|
input = qmi_message_nas_set_technology_preference_input_new ();
|
|
qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL);
|
|
|
|
qmi_client_nas_set_technology_preference (
|
|
ctx->client,
|
|
input,
|
|
5,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)set_current_modes_technology_preference_ready,
|
|
task);
|
|
qmi_message_nas_set_technology_preference_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
set_current_modes_system_selection_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
if (output)
|
|
qmi_message_nas_set_system_selection_preference_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
set_current_modes_system_selection_preference (GTask *task)
|
|
{
|
|
MMIfaceModem *self;
|
|
Private *priv;
|
|
SetCurrentModesContext *ctx;
|
|
QmiMessageNasSetSystemSelectionPreferenceInput *input;
|
|
QmiNasRatModePreference pref;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
input = qmi_message_nas_set_system_selection_preference_input_new ();
|
|
qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL);
|
|
|
|
/* Preferred modes */
|
|
|
|
if (ctx->preferred != MM_MODEM_MODE_NONE) {
|
|
if (priv->feature_nas_ssp_acquisition_order_preference == FEATURE_SUPPORTED) {
|
|
GArray *array;
|
|
|
|
/* Acquisition order array */
|
|
array = mm_modem_mode_to_qmi_acquisition_order_preference (ctx->allowed,
|
|
ctx->preferred,
|
|
priv->feature_nas_ssp_acquisition_order_preference_array);
|
|
g_assert (array);
|
|
qmi_message_nas_set_system_selection_preference_input_set_acquisition_order_preference (input, array, NULL);
|
|
g_array_unref (array);
|
|
}
|
|
|
|
/* Only set GSM/WCDMA acquisition order preference if both 2G and 3G given as allowed */
|
|
if (mm_iface_modem_is_3gpp (self) && ((ctx->allowed & (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G))) {
|
|
QmiNasGsmWcdmaAcquisitionOrderPreference order;
|
|
|
|
order = mm_modem_mode_to_qmi_gsm_wcdma_acquisition_order_preference (ctx->preferred, self);
|
|
qmi_message_nas_set_system_selection_preference_input_set_gsm_wcdma_acquisition_order_preference (input, order, NULL);
|
|
}
|
|
}
|
|
|
|
/* Allowed modes */
|
|
pref = mm_modem_mode_to_qmi_rat_mode_preference (ctx->allowed,
|
|
mm_iface_modem_is_cdma (self),
|
|
mm_iface_modem_is_3gpp (self));
|
|
qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL);
|
|
|
|
qmi_client_nas_set_system_selection_preference (
|
|
ctx->client,
|
|
input,
|
|
5,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)set_current_modes_system_selection_preference_ready,
|
|
task);
|
|
qmi_message_nas_set_system_selection_preference_input_unref (input);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_set_current_modes (MMIfaceModem *self,
|
|
MMModemMode allowed,
|
|
MMModemMode preferred,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SetCurrentModesContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
Private *priv;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
ctx = g_slice_new0 (SetCurrentModesContext);
|
|
ctx->client = QMI_CLIENT_NAS (g_object_ref (client));
|
|
|
|
if (allowed == MM_MODEM_MODE_ANY && ctx->preferred == MM_MODEM_MODE_NONE) {
|
|
ctx->allowed = MM_MODEM_MODE_NONE;
|
|
if (mm_iface_modem_is_2g (self))
|
|
ctx->allowed |= MM_MODEM_MODE_2G;
|
|
if (mm_iface_modem_is_3g (self))
|
|
ctx->allowed |= MM_MODEM_MODE_3G;
|
|
if (mm_iface_modem_is_4g (self))
|
|
ctx->allowed |= MM_MODEM_MODE_4G;
|
|
if (mm_iface_modem_is_5g (self))
|
|
ctx->allowed |= MM_MODEM_MODE_5G;
|
|
ctx->preferred = MM_MODEM_MODE_NONE;
|
|
} else {
|
|
ctx->allowed = allowed;
|
|
ctx->preferred = preferred;
|
|
}
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_modes_context_free);
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
if (priv->feature_nas_ssp == FEATURE_SUPPORTED) {
|
|
set_current_modes_system_selection_preference (task);
|
|
return;
|
|
}
|
|
|
|
if (priv->feature_nas_tp == FEATURE_SUPPORTED) {
|
|
set_current_modes_technology_preference (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Setting allowed modes is not supported by this device");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load current modes (Modem interface) */
|
|
|
|
typedef struct {
|
|
QmiClientNas *client;
|
|
} LoadCurrentModesContext;
|
|
|
|
typedef struct {
|
|
MMModemMode allowed;
|
|
MMModemMode preferred;
|
|
} LoadCurrentModesResult;
|
|
|
|
static void
|
|
load_current_modes_context_free (LoadCurrentModesContext *ctx)
|
|
{
|
|
g_object_unref (ctx->client);
|
|
g_free (ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_load_current_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
MMModemMode *allowed,
|
|
MMModemMode *preferred,
|
|
GError **error)
|
|
{
|
|
LoadCurrentModesResult *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 MMModemMode
|
|
filter_modes_by_supported_radio_interfaces (MMSharedQmi *self,
|
|
GArray *supported_radio_interfaces,
|
|
MMModemMode allowed_modes)
|
|
{
|
|
MMModemMode modem_modes;
|
|
guint i;
|
|
|
|
modem_modes = MM_MODEM_MODE_NONE;
|
|
|
|
for (i = 0; i < supported_radio_interfaces->len; i++)
|
|
modem_modes |= mm_modem_mode_from_qmi_radio_interface (g_array_index (supported_radio_interfaces, QmiDmsRadioInterface, i), self);
|
|
|
|
return allowed_modes & modem_modes;
|
|
}
|
|
|
|
static void
|
|
get_technology_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
LoadCurrentModesResult *result = NULL;
|
|
QmiMessageNasGetTechnologyPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
MMModemMode allowed;
|
|
QmiNasRadioTechnologyPreference preference_mask;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
output = qmi_client_nas_get_technology_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_get_technology_preference_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
goto out;
|
|
}
|
|
|
|
qmi_message_nas_get_technology_preference_output_get_active (
|
|
output,
|
|
&preference_mask,
|
|
NULL, /* duration */
|
|
NULL);
|
|
allowed = mm_modem_mode_from_qmi_radio_technology_preference (preference_mask);
|
|
if (allowed == MM_MODEM_MODE_NONE) {
|
|
gchar *str;
|
|
|
|
str = qmi_nas_radio_technology_preference_build_string_from_mask (preference_mask);
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Unsupported modes reported: '%s'", str);
|
|
g_free (str);
|
|
goto out;
|
|
}
|
|
|
|
g_assert (priv->supported_radio_interfaces);
|
|
allowed = filter_modes_by_supported_radio_interfaces (self, priv->supported_radio_interfaces, allowed);
|
|
|
|
/* We got a valid value from here */
|
|
result = g_new (LoadCurrentModesResult, 1);
|
|
result->allowed = allowed;
|
|
result->preferred = MM_MODEM_MODE_NONE;
|
|
g_task_return_pointer (task, result, g_free);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_nas_get_technology_preference_output_unref (output);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_current_modes_technology_preference (GTask *task)
|
|
{
|
|
LoadCurrentModesContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
qmi_client_nas_get_technology_preference (
|
|
ctx->client,
|
|
NULL, /* no input */
|
|
5,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)get_technology_preference_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_current_modes_system_selection_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
LoadCurrentModesResult *result = NULL;
|
|
QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
QmiNasRatModePreference mode_preference_mask = 0;
|
|
MMModemMode allowed;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
goto out;
|
|
}
|
|
|
|
if (!qmi_message_nas_get_system_selection_preference_output_get_mode_preference (
|
|
output,
|
|
&mode_preference_mask,
|
|
NULL)) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Mode preference not reported in system selection preference");
|
|
goto out;
|
|
}
|
|
|
|
allowed = mm_modem_mode_from_qmi_rat_mode_preference (mode_preference_mask);
|
|
if (allowed == MM_MODEM_MODE_NONE) {
|
|
gchar *str;
|
|
|
|
str = qmi_nas_rat_mode_preference_build_string_from_mask (mode_preference_mask);
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Unsupported modes reported: '%s'", str);
|
|
g_free (str);
|
|
goto out;
|
|
}
|
|
|
|
g_assert (priv->supported_radio_interfaces);
|
|
allowed = filter_modes_by_supported_radio_interfaces (self, priv->supported_radio_interfaces, allowed);
|
|
|
|
/* We got a valid value from here */
|
|
result = g_new (LoadCurrentModesResult, 1);
|
|
result->allowed = allowed;
|
|
result->preferred = MM_MODEM_MODE_NONE;
|
|
|
|
/* If acquisition order preference is available, always use that first */
|
|
if (priv->feature_nas_ssp_acquisition_order_preference == FEATURE_SUPPORTED) {
|
|
GArray *array;
|
|
|
|
if (qmi_message_nas_get_system_selection_preference_output_get_acquisition_order_preference (output, &array, NULL) &&
|
|
array->len > 0) {
|
|
guint i;
|
|
|
|
/* The array of preference contains the preference of the full list of supported
|
|
* access technologies, regardless of whether they're enabled or not. So, look for
|
|
* the first one that is flagged as enabled, not just the first one in the array.
|
|
*/
|
|
for (i = 0; i < array->len; i++) {
|
|
MMModemMode mode;
|
|
|
|
mode = mm_modem_mode_from_qmi_nas_radio_interface (g_array_index (array, QmiNasRadioInterface, i));
|
|
if (allowed == mode)
|
|
break;
|
|
if (allowed & mode) {
|
|
result->preferred = mode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* For 2G+3G only rely on the GSM/WCDMA acquisition order preference TLV */
|
|
else if (mode_preference_mask == (QMI_NAS_RAT_MODE_PREFERENCE_GSM | QMI_NAS_RAT_MODE_PREFERENCE_UMTS)) {
|
|
QmiNasGsmWcdmaAcquisitionOrderPreference gsm_or_wcdma;
|
|
|
|
if (qmi_message_nas_get_system_selection_preference_output_get_gsm_wcdma_acquisition_order_preference (
|
|
output,
|
|
&gsm_or_wcdma,
|
|
NULL))
|
|
result->preferred = mm_modem_mode_from_qmi_gsm_wcdma_acquisition_order_preference (gsm_or_wcdma, self);
|
|
}
|
|
|
|
g_task_return_pointer (task, result, g_free);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_nas_get_system_selection_preference_output_unref (output);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
load_current_modes_system_selection_preference (GTask *task)
|
|
{
|
|
LoadCurrentModesContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
qmi_client_nas_get_system_selection_preference (
|
|
ctx->client,
|
|
NULL, /* no input */
|
|
5,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)load_current_modes_system_selection_preference_ready,
|
|
task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_current_modes (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
Private *priv;
|
|
LoadCurrentModesContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
ctx = g_new0 (LoadCurrentModesContext, 1);
|
|
ctx->client = QMI_CLIENT_NAS (g_object_ref (client));
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_current_modes_context_free);
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
if (priv->feature_nas_ssp != FEATURE_UNSUPPORTED) {
|
|
load_current_modes_system_selection_preference (task);
|
|
return;
|
|
}
|
|
|
|
if (priv->feature_nas_tp != FEATURE_UNSUPPORTED) {
|
|
load_current_modes_technology_preference (task);
|
|
return;
|
|
}
|
|
|
|
/* Default to supported */
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Loading current modes is not supported by this device");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Supported modes (Modem interface) */
|
|
|
|
GArray *
|
|
mm_shared_qmi_load_supported_modes_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_supported_modes (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
MMQmiSupportedModesContext ctx = { 0 };
|
|
guint i;
|
|
GArray *combinations;
|
|
GError *error = NULL;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
g_assert (priv->supported_radio_interfaces);
|
|
g_assert (priv->current_capabilities != MM_MODEM_CAPABILITY_NONE);
|
|
|
|
/* Build all, based on the supported radio interfaces */
|
|
ctx.all = MM_MODEM_MODE_NONE;
|
|
for (i = 0; i < priv->supported_radio_interfaces->len; i++)
|
|
ctx.all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
|
|
|
|
/* Filter out those unsupported by the current capabilities */
|
|
if (!(priv->current_capabilities & (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)))
|
|
ctx.all &= ~(MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
|
|
else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_LTE))
|
|
ctx.all &= ~MM_MODEM_MODE_4G;
|
|
else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_5GNR))
|
|
ctx.all &= ~MM_MODEM_MODE_5G;
|
|
|
|
ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED);
|
|
ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED);
|
|
ctx.current_capabilities = priv->current_capabilities;
|
|
ctx.multimode = priv->multimode;
|
|
|
|
combinations = mm_supported_modes_from_qmi_supported_modes_context (&ctx, self, &error);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load supported bands (Modem interface) */
|
|
|
|
GArray *
|
|
mm_shared_qmi_load_supported_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
dms_get_band_capabilities_ready (QmiClientDms *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiMessageDmsGetBandCapabilitiesOutput *output;
|
|
GError *error = NULL;
|
|
GArray *mm_bands = NULL;
|
|
QmiDmsBandCapability qmi_bands = 0;
|
|
QmiDmsLteBandCapability qmi_lte_bands = 0;
|
|
GArray *extended_qmi_lte_bands = NULL;
|
|
GArray *qmi_nr5g_bands = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
output = qmi_client_dms_get_band_capabilities_finish (client, res, &error);
|
|
if (!output || !qmi_message_dms_get_band_capabilities_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't get band capabilities: ");
|
|
goto out;
|
|
}
|
|
|
|
qmi_message_dms_get_band_capabilities_output_get_band_capability (
|
|
output,
|
|
&qmi_bands,
|
|
NULL);
|
|
qmi_message_dms_get_band_capabilities_output_get_lte_band_capability (
|
|
output,
|
|
&qmi_lte_bands,
|
|
NULL);
|
|
qmi_message_dms_get_band_capabilities_output_get_extended_lte_band_capability (
|
|
output,
|
|
&extended_qmi_lte_bands,
|
|
NULL);
|
|
qmi_message_dms_get_band_capabilities_output_get_nr5g_band_capability (
|
|
output,
|
|
&qmi_nr5g_bands,
|
|
NULL);
|
|
|
|
mm_bands = mm_modem_bands_from_qmi_band_capabilities (qmi_bands, qmi_lte_bands, extended_qmi_lte_bands, qmi_nr5g_bands, self);
|
|
if (mm_bands->len == 0) {
|
|
g_clear_pointer (&mm_bands, g_array_unref);
|
|
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't parse the list of supported bands");
|
|
goto out;
|
|
}
|
|
|
|
/* Cache the result */
|
|
g_clear_pointer (&priv->supported_bands, g_array_unref);
|
|
priv->supported_bands = g_array_ref (mm_bands);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_dms_get_band_capabilities_output_unref (output);
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else if (mm_bands)
|
|
g_task_return_pointer (task, mm_bands, (GDestroyNotify)g_array_unref);
|
|
else
|
|
g_assert_not_reached ();
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_supported_bands (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_DMS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
qmi_client_dms_get_band_capabilities (QMI_CLIENT_DMS (client),
|
|
NULL,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)dms_get_band_capabilities_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load current bands (Modem interface) */
|
|
|
|
GArray *
|
|
mm_shared_qmi_load_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return (GArray *) g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
load_bands_get_system_selection_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
GArray *mm_bands = NULL;
|
|
QmiNasBandPreference band_preference_mask = 0;
|
|
QmiNasLteBandPreference lte_band_preference_mask = 0;
|
|
guint64 extended_lte_band_preference[4] = { 0 };
|
|
guint extended_lte_band_preference_size = 0;
|
|
guint64 nr5g_sa_band_preference[8] = { 0 };
|
|
guint64 nr5g_nsa_band_preference[8] = { 0 };
|
|
guint64 nr5g_band_preference[8] = { 0 };
|
|
guint nr5g_band_preference_size = 0;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't get system selection preference: ");
|
|
goto out;
|
|
}
|
|
|
|
qmi_message_nas_get_system_selection_preference_output_get_band_preference (
|
|
output,
|
|
&band_preference_mask,
|
|
NULL);
|
|
|
|
qmi_message_nas_get_system_selection_preference_output_get_lte_band_preference (
|
|
output,
|
|
<e_band_preference_mask,
|
|
NULL);
|
|
|
|
if ((priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED) &&
|
|
qmi_message_nas_get_system_selection_preference_output_get_extended_lte_band_preference (
|
|
output,
|
|
&extended_lte_band_preference[0],
|
|
&extended_lte_band_preference[1],
|
|
&extended_lte_band_preference[2],
|
|
&extended_lte_band_preference[3],
|
|
NULL))
|
|
extended_lte_band_preference_size = G_N_ELEMENTS (extended_lte_band_preference);
|
|
|
|
if (qmi_message_nas_get_system_selection_preference_output_get_nr5g_sa_band_preference (
|
|
output,
|
|
&nr5g_sa_band_preference[0],
|
|
&nr5g_sa_band_preference[1],
|
|
&nr5g_sa_band_preference[2],
|
|
&nr5g_sa_band_preference[3],
|
|
&nr5g_sa_band_preference[4],
|
|
&nr5g_sa_band_preference[5],
|
|
&nr5g_sa_band_preference[6],
|
|
&nr5g_sa_band_preference[7],
|
|
NULL) || qmi_message_nas_get_system_selection_preference_output_get_nr5g_nsa_band_preference (
|
|
output,
|
|
&nr5g_nsa_band_preference[0],
|
|
&nr5g_nsa_band_preference[1],
|
|
&nr5g_nsa_band_preference[2],
|
|
&nr5g_nsa_band_preference[3],
|
|
&nr5g_nsa_band_preference[4],
|
|
&nr5g_nsa_band_preference[5],
|
|
&nr5g_nsa_band_preference[6],
|
|
&nr5g_nsa_band_preference[7],
|
|
NULL)) {
|
|
guint i;
|
|
|
|
nr5g_band_preference_size = G_N_ELEMENTS (nr5g_band_preference);
|
|
for (i = 0; i < nr5g_band_preference_size; i++)
|
|
nr5g_band_preference[i] = nr5g_sa_band_preference[i] | nr5g_nsa_band_preference[i];
|
|
}
|
|
|
|
mm_bands = mm_modem_bands_from_qmi_band_preference (band_preference_mask,
|
|
lte_band_preference_mask,
|
|
extended_lte_band_preference_size ? extended_lte_band_preference : NULL,
|
|
extended_lte_band_preference_size,
|
|
nr5g_band_preference_size ? nr5g_band_preference : NULL,
|
|
nr5g_band_preference_size,
|
|
self);
|
|
|
|
if (mm_bands->len == 0) {
|
|
g_clear_pointer (&mm_bands, g_array_unref);
|
|
error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't parse the list of current bands");
|
|
}
|
|
|
|
out:
|
|
|
|
if (output)
|
|
qmi_message_nas_get_system_selection_preference_output_unref (output);
|
|
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else if (mm_bands)
|
|
g_task_return_pointer (task, mm_bands, (GDestroyNotify)g_array_unref);
|
|
else
|
|
g_assert_not_reached ();
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_current_bands (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
qmi_client_nas_get_system_selection_preference (
|
|
QMI_CLIENT_NAS (client),
|
|
NULL, /* no input */
|
|
5,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)load_bands_get_system_selection_preference_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set current bands (Modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_qmi_set_current_bands_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
bands_set_system_selection_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't set system selection preference: ");
|
|
g_task_return_error (task, error);
|
|
} else
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
if (output)
|
|
qmi_message_nas_set_system_selection_preference_output_unref (output);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_set_current_bands (MMIfaceModem *self,
|
|
GArray *bands_array,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
QmiMessageNasSetSystemSelectionPreferenceInput *input;
|
|
Private *priv;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
QmiNasBandPreference qmi_bands = 0;
|
|
QmiNasLteBandPreference qmi_lte_bands = 0;
|
|
guint64 extended_qmi_lte_bands[4] = { 0 };
|
|
guint64 qmi_nr5g_bands[8] = { 0 };
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
/* Handle ANY separately */
|
|
if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) {
|
|
if (!priv->supported_bands) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Cannot handle 'ANY' if supported bands are unknown");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
bands_array = priv->supported_bands;
|
|
}
|
|
|
|
mm_modem_bands_to_qmi_band_preference (bands_array,
|
|
&qmi_bands,
|
|
&qmi_lte_bands,
|
|
priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED ? extended_qmi_lte_bands : NULL,
|
|
G_N_ELEMENTS (extended_qmi_lte_bands),
|
|
qmi_nr5g_bands,
|
|
G_N_ELEMENTS (qmi_nr5g_bands),
|
|
self);
|
|
|
|
input = qmi_message_nas_set_system_selection_preference_input_new ();
|
|
qmi_message_nas_set_system_selection_preference_input_set_band_preference (input, qmi_bands, NULL);
|
|
if (mm_iface_modem_is_3gpp_lte (self)) {
|
|
if (priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED)
|
|
qmi_message_nas_set_system_selection_preference_input_set_extended_lte_band_preference (
|
|
input,
|
|
extended_qmi_lte_bands[0],
|
|
extended_qmi_lte_bands[1],
|
|
extended_qmi_lte_bands[2],
|
|
extended_qmi_lte_bands[3],
|
|
NULL);
|
|
else
|
|
qmi_message_nas_set_system_selection_preference_input_set_lte_band_preference (input, qmi_lte_bands, NULL);
|
|
}
|
|
qmi_message_nas_set_system_selection_preference_input_set_nr5g_sa_band_preference (
|
|
input,
|
|
qmi_nr5g_bands[0],
|
|
qmi_nr5g_bands[1],
|
|
qmi_nr5g_bands[2],
|
|
qmi_nr5g_bands[3],
|
|
qmi_nr5g_bands[4],
|
|
qmi_nr5g_bands[5],
|
|
qmi_nr5g_bands[6],
|
|
qmi_nr5g_bands[7],
|
|
NULL);
|
|
qmi_message_nas_set_system_selection_preference_input_set_nr5g_nsa_band_preference (
|
|
input,
|
|
qmi_nr5g_bands[0],
|
|
qmi_nr5g_bands[1],
|
|
qmi_nr5g_bands[2],
|
|
qmi_nr5g_bands[3],
|
|
qmi_nr5g_bands[4],
|
|
qmi_nr5g_bands[5],
|
|
qmi_nr5g_bands[6],
|
|
qmi_nr5g_bands[7],
|
|
NULL);
|
|
qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL);
|
|
|
|
qmi_client_nas_set_system_selection_preference (
|
|
QMI_CLIENT_NAS (client),
|
|
input,
|
|
5,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)bands_set_system_selection_preference_ready,
|
|
task);
|
|
qmi_message_nas_set_system_selection_preference_input_unref (input);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Reset (Modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_qmi_reset_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
reset_set_operating_mode_reset_ready (QmiClientDms *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
QmiMessageDmsSetOperatingModeOutput *output;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
output = qmi_client_dms_set_operating_mode_finish (client, res, &error);
|
|
if (!output || !qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
mm_obj_msg (self, "rebooting now");
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
if (output)
|
|
qmi_message_dms_set_operating_mode_output_unref (output);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
reset_set_operating_mode_offline_ready (QmiClientDms *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageDmsSetOperatingModeInput *input;
|
|
QmiMessageDmsSetOperatingModeOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_dms_set_operating_mode_finish (client, res, &error);
|
|
if (!output) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_dms_set_operating_mode_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
qmi_message_dms_set_operating_mode_output_unref (output);
|
|
|
|
/* Now, go into reset mode. This will fully reboot the modem, and the current
|
|
* modem object should get disposed. */
|
|
input = qmi_message_dms_set_operating_mode_input_new ();
|
|
qmi_message_dms_set_operating_mode_input_set_mode (input, QMI_DMS_OPERATING_MODE_RESET, NULL);
|
|
qmi_client_dms_set_operating_mode (client,
|
|
input,
|
|
20,
|
|
NULL,
|
|
(GAsyncReadyCallback)reset_set_operating_mode_reset_ready,
|
|
task);
|
|
qmi_message_dms_set_operating_mode_input_unref (input);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_reset (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
QmiMessageDmsSetOperatingModeInput *input;
|
|
GTask *task;
|
|
QmiClient *client;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_DMS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Now, go into offline mode */
|
|
input = qmi_message_dms_set_operating_mode_input_new ();
|
|
qmi_message_dms_set_operating_mode_input_set_mode (input, QMI_DMS_OPERATING_MODE_OFFLINE, NULL);
|
|
qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (client),
|
|
input,
|
|
20,
|
|
NULL,
|
|
(GAsyncReadyCallback)reset_set_operating_mode_offline_ready,
|
|
task);
|
|
qmi_message_dms_set_operating_mode_input_unref (input);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Factory reset (Modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_qmi_factory_reset_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
dms_restore_factory_defaults_ready (QmiClientDms *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageDmsRestoreFactoryDefaultsOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_dms_restore_factory_defaults_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
} else if (!qmi_message_dms_restore_factory_defaults_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't restore factory defaults: ");
|
|
g_task_return_error (task, error);
|
|
} else
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
if (output)
|
|
qmi_message_dms_restore_factory_defaults_output_unref (output);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_factory_reset (MMIfaceModem *self,
|
|
const gchar *code,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
QmiMessageDmsRestoreFactoryDefaultsInput *input;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
GError *error = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_DMS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
input = qmi_message_dms_restore_factory_defaults_input_new ();
|
|
if (!qmi_message_dms_restore_factory_defaults_input_set_service_programming_code (
|
|
input,
|
|
code,
|
|
&error)) {
|
|
qmi_message_dms_restore_factory_defaults_input_unref (input);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "performing a factory reset...");
|
|
qmi_client_dms_restore_factory_defaults (QMI_CLIENT_DMS (client),
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)dms_restore_factory_defaults_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Setup carrier config (Modem interface) */
|
|
|
|
#define SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS 10
|
|
#define GENERIC_CONFIG_FALLBACK "generic"
|
|
|
|
typedef enum {
|
|
SETUP_CARRIER_CONFIG_STEP_FIRST,
|
|
SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED,
|
|
SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED,
|
|
SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT,
|
|
SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT,
|
|
SETUP_CARRIER_CONFIG_STEP_LAST,
|
|
} SetupCarrierConfigStep;
|
|
|
|
|
|
typedef struct {
|
|
SetupCarrierConfigStep step;
|
|
QmiClientPdc *client;
|
|
GKeyFile *keyfile;
|
|
gchar *imsi;
|
|
|
|
gint config_requested_i;
|
|
gchar *config_requested;
|
|
|
|
guint token;
|
|
guint timeout_id;
|
|
gulong set_selected_config_indication_id;
|
|
gulong activate_config_indication_id;
|
|
} SetupCarrierConfigContext;
|
|
|
|
/* Allow to cleanup action setup right away, without being tied
|
|
* to the lifecycle of the GTask */
|
|
static void
|
|
setup_carrier_config_context_cleanup_action (SetupCarrierConfigContext *ctx)
|
|
{
|
|
if (ctx->activate_config_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->activate_config_indication_id);
|
|
ctx->activate_config_indication_id = 0;
|
|
}
|
|
if (ctx->set_selected_config_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->set_selected_config_indication_id);
|
|
ctx->set_selected_config_indication_id = 0;
|
|
}
|
|
if (ctx->timeout_id) {
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_carrier_config_context_free (SetupCarrierConfigContext *ctx)
|
|
{
|
|
setup_carrier_config_context_cleanup_action (ctx);
|
|
|
|
g_free (ctx->config_requested);
|
|
g_free (ctx->imsi);
|
|
g_key_file_unref (ctx->keyfile);
|
|
g_clear_object (&ctx->client);
|
|
g_slice_free (SetupCarrierConfigContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_setup_carrier_config_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void setup_carrier_config_step (GTask *task);
|
|
|
|
static void
|
|
setup_carrier_config_abort (GTask *task,
|
|
GError *error)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
setup_carrier_config_context_cleanup_action (ctx);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
setup_carrier_config_timeout_no_error (GTask *task)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
setup_carrier_config_context_cleanup_action (ctx);
|
|
ctx->step++;
|
|
setup_carrier_config_step (task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
setup_carrier_config_timeout (GTask *task)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation timed out"));
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
activate_config_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcActivateConfigOutput *output,
|
|
GTask *task)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
guint16 error_code = 0;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_activate_config_output_get_indication_result (output, &error_code, &error)) {
|
|
setup_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0) {
|
|
setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't activate config: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
/* Go on */
|
|
setup_carrier_config_context_cleanup_action (ctx);
|
|
ctx->step++;
|
|
setup_carrier_config_step (task);
|
|
}
|
|
|
|
static void
|
|
activate_config_ready (QmiClientPdc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdcActivateConfigOutput *output;
|
|
SetupCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_pdc_activate_config_finish (client, res, &error);
|
|
if (!output || !qmi_message_pdc_activate_config_output_get_result (output, &error)) {
|
|
setup_carrier_config_abort (task, error);
|
|
goto out;
|
|
}
|
|
|
|
/* When we activate the config, if the operation is successful, we'll just
|
|
* see the modem going away completely. So, do not consider an error the timeout
|
|
* waiting for the Activate Config indication, as that is actually a good
|
|
* thing.
|
|
*/
|
|
ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
|
|
(GSourceFunc) setup_carrier_config_timeout_no_error,
|
|
task);
|
|
ctx->activate_config_indication_id = g_signal_connect (ctx->client,
|
|
"activate-config",
|
|
G_CALLBACK (activate_config_indication),
|
|
task);
|
|
out:
|
|
if (output)
|
|
qmi_message_pdc_activate_config_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
set_selected_config_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcSetSelectedConfigOutput *output,
|
|
GTask *task)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
guint16 error_code = 0;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_set_selected_config_output_get_indication_result (output, &error_code, &error)) {
|
|
setup_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0) {
|
|
setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't set selected config: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
/* Go on */
|
|
setup_carrier_config_context_cleanup_action (ctx);
|
|
ctx->step++;
|
|
setup_carrier_config_step (task);
|
|
}
|
|
|
|
static void
|
|
set_selected_config_ready (QmiClientPdc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdcSetSelectedConfigOutput *output;
|
|
SetupCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_pdc_set_selected_config_finish (client, res, &error);
|
|
if (!output || !qmi_message_pdc_set_selected_config_output_get_result (output, &error)) {
|
|
setup_carrier_config_abort (task, error);
|
|
goto out;
|
|
}
|
|
|
|
ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
|
|
(GSourceFunc) setup_carrier_config_timeout,
|
|
task);
|
|
ctx->set_selected_config_indication_id = g_signal_connect (ctx->client,
|
|
"set-selected-config",
|
|
G_CALLBACK (set_selected_config_indication),
|
|
task);
|
|
out:
|
|
if (output)
|
|
qmi_message_pdc_set_selected_config_output_unref (output);
|
|
}
|
|
|
|
static gint
|
|
select_newest_carrier_config (MMSharedQmi *self,
|
|
gint config_a_i,
|
|
gint config_b_i)
|
|
{
|
|
Private *priv;
|
|
ConfigInfo *config_a;
|
|
ConfigInfo *config_b;
|
|
|
|
priv = get_private (self);
|
|
config_a = &g_array_index (priv->config_list, ConfigInfo, config_a_i);
|
|
config_b = &g_array_index (priv->config_list, ConfigInfo, config_b_i);
|
|
|
|
g_assert (!g_strcmp0 (config_a->description, config_b->description));
|
|
|
|
if (config_a->version > config_b->version)
|
|
return config_a_i;
|
|
if (config_b->version > config_a->version)
|
|
return config_b_i;
|
|
/* if both are equal, return the first one found always */
|
|
return config_a_i;
|
|
}
|
|
|
|
static void
|
|
find_requested_carrier_config (GTask *task)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
gchar mccmnc[7];
|
|
gchar *group;
|
|
gint config_fallback_i = -1;
|
|
gchar *config_fallback = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
self = MM_SHARED_QMI (g_task_get_source_object (task));
|
|
priv = get_private (self);
|
|
|
|
/* Only one group expected per file, so get the start one */
|
|
group = g_key_file_get_start_group (ctx->keyfile);
|
|
|
|
/* Match generic configuration */
|
|
config_fallback = g_key_file_get_string (ctx->keyfile, group, GENERIC_CONFIG_FALLBACK, NULL);
|
|
mm_obj_dbg (self, "fallback carrier configuration %sfound in group '%s'", config_fallback ? "" : "not ", group);
|
|
|
|
/* First, try to match 6 MCCMNC digits (3-digit MNCs) */
|
|
strncpy (mccmnc, ctx->imsi, 6);
|
|
mccmnc[6] = '\0';
|
|
ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL);
|
|
if (!ctx->config_requested) {
|
|
/* If not found, try to match 5 MCCMNC digits (2-digit MNCs) */
|
|
mccmnc[5] = '\0';
|
|
ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL);
|
|
}
|
|
mm_obj_dbg (self, "requested carrier configuration %sfound for '%s' in group '%s': %s",
|
|
ctx->config_requested ? "" : "not ", mccmnc, group, ctx->config_requested ? ctx->config_requested : "n/a");
|
|
|
|
if (!ctx->config_requested && !config_fallback) {
|
|
setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
|
|
"no valid configuration found in group '%s'", group));
|
|
goto out;
|
|
}
|
|
|
|
/* Now, look for the configurations among the ones available in the device */
|
|
if (priv->config_list) {
|
|
guint i;
|
|
|
|
for (i = 0; i < priv->config_list->len; i++) {
|
|
ConfigInfo *config;
|
|
|
|
config = &g_array_index (priv->config_list, ConfigInfo, i);
|
|
if (ctx->config_requested && !g_strcmp0 (ctx->config_requested, config->description)) {
|
|
mm_obj_dbg (self, "requested carrier configuration '%s' is available (version 0x%08x, size %u bytes)",
|
|
config->description, config->version, config->total_size);
|
|
if (ctx->config_requested_i < 0)
|
|
ctx->config_requested_i = i;
|
|
else
|
|
ctx->config_requested_i = select_newest_carrier_config (self, ctx->config_requested_i, i);
|
|
}
|
|
if (config_fallback && !g_strcmp0 (config_fallback, config->description)) {
|
|
mm_obj_dbg (self, "fallback carrier configuration '%s' is available (version 0x%08x, size %u bytes)",
|
|
config->description, config->version, config->total_size);
|
|
if (config_fallback_i < 0)
|
|
config_fallback_i = i;
|
|
else
|
|
config_fallback_i = select_newest_carrier_config (self, config_fallback_i, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fail operation if we didn't find the one we want */
|
|
if ((ctx->config_requested_i < 0) && (config_fallback_i < 0)) {
|
|
setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"carrier configurations (requested '%s', fallback '%s') are not available",
|
|
ctx->config_requested, config_fallback));
|
|
goto out;
|
|
}
|
|
|
|
/* If the mapping expects a given config, but the config isn't installed,
|
|
* we fallback to generic */
|
|
if (ctx->config_requested_i < 0) {
|
|
ConfigInfo *config;
|
|
|
|
g_assert (config_fallback_i >= 0);
|
|
|
|
config = &g_array_index (priv->config_list, ConfigInfo, config_fallback_i);
|
|
mm_obj_dbg (self, "using fallback carrier configuration '%s' (version 0x%08x, size %u bytes)",
|
|
config->description, config->version, config->total_size);
|
|
|
|
g_free (ctx->config_requested);
|
|
ctx->config_requested = config_fallback;
|
|
ctx->config_requested_i = config_fallback_i;
|
|
config_fallback = NULL;
|
|
} else {
|
|
ConfigInfo *config;
|
|
|
|
config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i);
|
|
mm_obj_dbg (self, "using requested carrier configuration '%s' (version 0x%08x, size %u bytes)",
|
|
config->description, config->version, config->total_size);
|
|
}
|
|
|
|
ctx->step++;
|
|
setup_carrier_config_step (task);
|
|
|
|
out:
|
|
g_free (config_fallback);
|
|
g_free (group);
|
|
}
|
|
|
|
static void
|
|
setup_carrier_config_step (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetupCarrierConfigContext *ctx;
|
|
Private *priv;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
priv = get_private (self);
|
|
|
|
switch (ctx->step) {
|
|
case SETUP_CARRIER_CONFIG_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED:
|
|
find_requested_carrier_config (task);
|
|
return;
|
|
|
|
case SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED:
|
|
g_assert (ctx->config_requested_i >= 0);
|
|
g_assert (priv->config_active_i >= 0 || priv->config_active_default);
|
|
if (ctx->config_requested_i == priv->config_active_i) {
|
|
mm_obj_msg (self, "carrier config switching not needed: already using '%s'", ctx->config_requested);
|
|
ctx->step = SETUP_CARRIER_CONFIG_STEP_LAST;
|
|
setup_carrier_config_step (task);
|
|
return;
|
|
}
|
|
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT: {
|
|
g_autoptr(QmiMessagePdcSetSelectedConfigInput) input = NULL;
|
|
ConfigInfo *requested_config;
|
|
ConfigInfo *active_config;
|
|
|
|
requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i);
|
|
active_config = (priv->config_active_default ? NULL : &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i));
|
|
mm_obj_warn (self, "carrier config switching needed: '%s' -> '%s'",
|
|
active_config ? active_config->description : DEFAULT_CONFIG_DESCRIPTION, requested_config->description);
|
|
|
|
input = qmi_message_pdc_set_selected_config_input_new ();
|
|
qmi_message_pdc_set_selected_config_input_set_type_with_id_v2 (input, requested_config->config_type, requested_config->id, NULL);
|
|
qmi_message_pdc_set_selected_config_input_set_token (input, ctx->token++, NULL);
|
|
qmi_client_pdc_set_selected_config (ctx->client,
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)set_selected_config_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT: {
|
|
g_autoptr(QmiMessagePdcActivateConfigInput) input = NULL;
|
|
ConfigInfo *requested_config;
|
|
|
|
requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i);
|
|
|
|
input = qmi_message_pdc_activate_config_input_new ();
|
|
qmi_message_pdc_activate_config_input_set_config_type (input, requested_config->config_type, NULL);
|
|
qmi_message_pdc_activate_config_input_set_token (input, ctx->token++, NULL);
|
|
qmi_client_pdc_activate_config (ctx->client,
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) activate_config_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case SETUP_CARRIER_CONFIG_STEP_LAST:
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_setup_carrier_config (MMIfaceModem *self,
|
|
const gchar *imsi,
|
|
const gchar *carrier_config_mapping,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
GError *error = NULL;
|
|
|
|
g_assert (imsi);
|
|
g_assert (carrier_config_mapping);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (SetupCarrierConfigContext);
|
|
ctx->step = SETUP_CARRIER_CONFIG_STEP_FIRST;
|
|
ctx->imsi = g_strdup (imsi);
|
|
ctx->keyfile = g_key_file_new ();
|
|
ctx->config_requested_i = -1;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)setup_carrier_config_context_free);
|
|
|
|
/* Load mapping keyfile */
|
|
if (!g_key_file_load_from_file (ctx->keyfile,
|
|
carrier_config_mapping,
|
|
G_KEY_FILE_NONE,
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Load PDC client */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (!client) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"QMI PDC not supported");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
ctx->client = QMI_CLIENT_PDC (g_object_ref (client));
|
|
|
|
setup_carrier_config_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load carrier config (Modem interface) */
|
|
|
|
#define LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS 5
|
|
|
|
typedef enum {
|
|
LOAD_CARRIER_CONFIG_STEP_FIRST,
|
|
LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS,
|
|
LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT,
|
|
LOAD_CARRIER_CONFIG_STEP_LAST,
|
|
} LoadCarrierConfigStep;
|
|
|
|
typedef struct {
|
|
LoadCarrierConfigStep step;
|
|
|
|
QmiClientPdc *client;
|
|
|
|
GArray *config_list;
|
|
guint configs_loaded;
|
|
gboolean config_active_default;
|
|
gint config_active_i;
|
|
|
|
guint token;
|
|
guint timeout_id;
|
|
gulong list_configs_indication_id;
|
|
gulong get_selected_config_indication_id;
|
|
gulong get_config_info_indication_id;
|
|
} LoadCarrierConfigContext;
|
|
|
|
/* Allow to cleanup action load right away, without being tied
|
|
* to the lifecycle of the GTask */
|
|
static void
|
|
load_carrier_config_context_cleanup_action (LoadCarrierConfigContext *ctx)
|
|
{
|
|
if (ctx->get_selected_config_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->get_selected_config_indication_id);
|
|
ctx->get_selected_config_indication_id = 0;
|
|
}
|
|
if (ctx->get_config_info_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->get_config_info_indication_id);
|
|
ctx->get_config_info_indication_id = 0;
|
|
}
|
|
if (ctx->list_configs_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->list_configs_indication_id);
|
|
ctx->list_configs_indication_id = 0;
|
|
}
|
|
if (ctx->timeout_id) {
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
load_carrier_config_context_free (LoadCarrierConfigContext *ctx)
|
|
{
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
|
|
if (ctx->config_list)
|
|
g_array_unref (ctx->config_list);
|
|
g_clear_object (&ctx->client);
|
|
g_slice_free (LoadCarrierConfigContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_load_carrier_config_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
gchar **carrier_config_name,
|
|
gchar **carrier_config_revision,
|
|
GError **error)
|
|
{
|
|
Private *priv;
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (res), error))
|
|
return FALSE;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
g_assert (priv->config_active_i >= 0 || priv->config_active_default);
|
|
|
|
if (priv->config_active_i >= 0) {
|
|
ConfigInfo *config;
|
|
|
|
config = &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i);
|
|
*carrier_config_name = g_strdup (config->description);
|
|
*carrier_config_revision = g_strdup_printf ("%08X", config->version);
|
|
} else if (priv->config_active_default) {
|
|
*carrier_config_name = g_strdup (DEFAULT_CONFIG_DESCRIPTION);
|
|
*carrier_config_revision = NULL;
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void load_carrier_config_step (GTask *task);
|
|
|
|
static void
|
|
load_carrier_config_abort (GTask *task,
|
|
GError *error)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
load_carrier_config_timeout (GTask *task)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation timed out"));
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
get_selected_config_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcGetSelectedConfigOutput *output,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
LoadCarrierConfigContext *ctx;
|
|
GArray *active_id = NULL;
|
|
GError *error = NULL;
|
|
guint16 error_code = 0;
|
|
guint i;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_get_selected_config_output_get_indication_result (output, &error_code, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0 &&
|
|
error_code != QMI_PROTOCOL_ERROR_NOT_PROVISIONED) { /* No configs active */
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't get selected config: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
qmi_indication_pdc_get_selected_config_output_get_active_id (output, &active_id, NULL);
|
|
if (!active_id) {
|
|
mm_obj_dbg (self, "no carrier config currently selected (default in use)");
|
|
ctx->config_active_default = TRUE;
|
|
goto next;
|
|
}
|
|
|
|
g_assert (ctx->config_list);
|
|
g_assert (ctx->config_list->len);
|
|
|
|
for (i = 0; i < ctx->config_list->len; i++) {
|
|
ConfigInfo *config;
|
|
|
|
config = &g_array_index (ctx->config_list, ConfigInfo, i);
|
|
if ((config->id->len == active_id->len) &&
|
|
!memcmp (config->id->data, active_id->data, active_id->len)) {
|
|
ctx->config_active_i = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ctx->config_list->len) {
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't find currently selected config"));
|
|
return;
|
|
}
|
|
|
|
next:
|
|
|
|
/* Go on */
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
ctx->step++;
|
|
load_carrier_config_step (task);
|
|
}
|
|
|
|
static void
|
|
get_selected_config_ready (QmiClientPdc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdcGetSelectedConfigOutput *output;
|
|
LoadCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_pdc_get_selected_config_finish (client, res, &error);
|
|
if (!output || !qmi_message_pdc_get_selected_config_output_get_result (output, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
goto out;
|
|
}
|
|
|
|
ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
|
|
(GSourceFunc) load_carrier_config_timeout,
|
|
task);
|
|
ctx->get_selected_config_indication_id = g_signal_connect (ctx->client,
|
|
"get-selected-config",
|
|
G_CALLBACK (get_selected_config_indication),
|
|
task);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_pdc_get_selected_config_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
get_config_info_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcGetConfigInfoOutput *output,
|
|
GTask *task)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
ConfigInfo *current_config = NULL;
|
|
guint32 token;
|
|
const gchar *description;
|
|
guint i;
|
|
guint16 error_code = 0;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_get_config_info_output_get_indication_result (output, &error_code, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0) {
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't get config info: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
if (!qmi_indication_pdc_get_config_info_output_get_token (output, &token, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
/* Look for the current config in the list, match by token */
|
|
for (i = 0; i < ctx->config_list->len; i++) {
|
|
current_config = &g_array_index (ctx->config_list, ConfigInfo, i);
|
|
if (current_config->token == token)
|
|
break;
|
|
}
|
|
|
|
/* Ignore if not found in the list */
|
|
if (i == ctx->config_list->len)
|
|
return;
|
|
|
|
/* Ignore if already set */
|
|
if (current_config->description)
|
|
return;
|
|
|
|
/* Store total size, version and description of the current config */
|
|
if (!qmi_indication_pdc_get_config_info_output_get_total_size (output, ¤t_config->total_size, &error) ||
|
|
!qmi_indication_pdc_get_config_info_output_get_version (output, ¤t_config->version, &error) ||
|
|
!qmi_indication_pdc_get_config_info_output_get_description (output, &description, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
current_config->description = g_strdup (description);
|
|
ctx->configs_loaded++;
|
|
|
|
/* If not all loaded, wait for more */
|
|
if (ctx->configs_loaded < ctx->config_list->len)
|
|
return;
|
|
|
|
/* Go on */
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
ctx->step++;
|
|
load_carrier_config_step (task);
|
|
}
|
|
|
|
static void
|
|
list_configs_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcListConfigsOutput *output,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
LoadCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
GArray *configs = NULL;
|
|
guint i;
|
|
guint16 error_code = 0;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_list_configs_output_get_indication_result (output, &error_code, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0) {
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't list configs: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
if (!qmi_indication_pdc_list_configs_output_get_configs (output, &configs, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
/* If no configs are installed, the module is running with the default one */
|
|
if (!configs || !configs->len) {
|
|
ctx->config_active_default = TRUE;
|
|
ctx->step = LOAD_CARRIER_CONFIG_STEP_LAST;
|
|
load_carrier_config_step (task);
|
|
return;
|
|
}
|
|
|
|
/* Preallocate config list and request details for each */
|
|
mm_obj_dbg (self, "found %u carrier configurations...", configs->len);
|
|
ctx->config_list = g_array_sized_new (FALSE, TRUE, sizeof (ConfigInfo), configs->len);
|
|
g_array_set_size (ctx->config_list, configs->len);
|
|
g_array_set_clear_func (ctx->config_list, (GDestroyNotify) config_info_clear);
|
|
|
|
ctx->get_config_info_indication_id = g_signal_connect (ctx->client,
|
|
"get-config-info",
|
|
G_CALLBACK (get_config_info_indication),
|
|
task);
|
|
|
|
for (i = 0; i < configs->len; i++) {
|
|
ConfigInfo *current_info;
|
|
QmiIndicationPdcListConfigsOutputConfigsElement *element;
|
|
g_autoptr(QmiMessagePdcGetConfigInfoInput) input = NULL;
|
|
|
|
element = &g_array_index (configs, QmiIndicationPdcListConfigsOutputConfigsElement, i);
|
|
|
|
current_info = &g_array_index (ctx->config_list, ConfigInfo, i);
|
|
current_info->token = ctx->token++;
|
|
current_info->id = g_array_ref (element->id);
|
|
current_info->config_type = element->config_type;
|
|
|
|
input = qmi_message_pdc_get_config_info_input_new ();
|
|
qmi_message_pdc_get_config_info_input_set_type_with_id_v2 (input, element->config_type, current_info->id, NULL);
|
|
qmi_message_pdc_get_config_info_input_set_token (input, current_info->token, NULL);
|
|
qmi_client_pdc_get_config_info (ctx->client, input, 10, NULL, NULL, NULL); /* ignore response! */
|
|
}
|
|
}
|
|
|
|
static void
|
|
list_configs_ready (QmiClientPdc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdcListConfigsOutput *output;
|
|
LoadCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_pdc_list_configs_finish (client, res, &error);
|
|
if (!output || !qmi_message_pdc_list_configs_output_get_result (output, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
goto out;
|
|
}
|
|
|
|
ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
|
|
(GSourceFunc) load_carrier_config_timeout,
|
|
task);
|
|
ctx->list_configs_indication_id = g_signal_connect (ctx->client,
|
|
"list-configs",
|
|
G_CALLBACK (list_configs_indication),
|
|
task);
|
|
out:
|
|
if (output)
|
|
qmi_message_pdc_list_configs_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
load_carrier_config_step (GTask *task)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
Private *priv;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
priv = get_private (g_task_get_source_object (task));
|
|
|
|
switch (ctx->step) {
|
|
case LOAD_CARRIER_CONFIG_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS: {
|
|
QmiMessagePdcListConfigsInput *input;
|
|
|
|
input = qmi_message_pdc_list_configs_input_new ();
|
|
qmi_message_pdc_list_configs_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL);
|
|
qmi_message_pdc_list_configs_input_set_token (input, ctx->token++, NULL);
|
|
qmi_client_pdc_list_configs (ctx->client,
|
|
input,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)list_configs_ready,
|
|
task);
|
|
qmi_message_pdc_list_configs_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
case LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT: {
|
|
QmiMessagePdcGetSelectedConfigInput *input;
|
|
|
|
input = qmi_message_pdc_get_selected_config_input_new ();
|
|
qmi_message_pdc_get_selected_config_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL);
|
|
qmi_message_pdc_get_selected_config_input_set_token (input, ctx->token++, NULL);
|
|
qmi_client_pdc_get_selected_config (ctx->client,
|
|
input,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)get_selected_config_ready,
|
|
task);
|
|
qmi_message_pdc_get_selected_config_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
case LOAD_CARRIER_CONFIG_STEP_LAST:
|
|
/* We will now store the loaded information so that we can later on use it
|
|
* if needed during the automatic carrier config switching operation */
|
|
g_assert (priv->config_active_i < 0 && !priv->config_active_default);
|
|
g_assert (ctx->config_active_i >= 0 || ctx->config_active_default);
|
|
priv->config_list = ctx->config_list ? g_array_ref (ctx->config_list) : NULL;
|
|
priv->config_active_i = ctx->config_active_i;
|
|
priv->config_active_default = ctx->config_active_default;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_carrier_config (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (LoadCarrierConfigContext);
|
|
ctx->step = LOAD_CARRIER_CONFIG_STEP_FIRST;
|
|
ctx->config_active_i = -1;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_carrier_config_context_free);
|
|
|
|
/* Load PDC client */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (!client) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"QMI PDC not supported");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
ctx->client = QMI_CLIENT_PDC (g_object_ref (client));
|
|
|
|
load_carrier_config_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load SIM slots (modem interface) */
|
|
|
|
typedef struct {
|
|
QmiClientUim *client_uim;
|
|
GPtrArray *sim_slots;
|
|
guint active_slot_number;
|
|
guint active_logical_id;
|
|
} LoadSimSlotsContext;
|
|
|
|
static void
|
|
load_sim_slots_context_free (LoadSimSlotsContext *ctx)
|
|
{
|
|
g_clear_pointer (&ctx->sim_slots, g_ptr_array_unref);
|
|
g_clear_object (&ctx->client_uim);
|
|
g_slice_free (LoadSimSlotsContext, ctx);
|
|
}
|
|
|
|
static void
|
|
sim_slot_free (MMBaseSim *sim)
|
|
{
|
|
if (sim)
|
|
g_object_unref (sim);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_load_sim_slots_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GPtrArray **sim_slots,
|
|
guint *primary_sim_slot,
|
|
GError **error)
|
|
{
|
|
LoadSimSlotsContext *ctx;
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (res), error))
|
|
return FALSE;
|
|
|
|
ctx = g_task_get_task_data (G_TASK (res));
|
|
|
|
if (sim_slots)
|
|
*sim_slots = g_steal_pointer (&ctx->sim_slots);
|
|
if (primary_sim_slot)
|
|
*primary_sim_slot = ctx->active_slot_number;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
uim_get_slot_status_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL;
|
|
LoadSimSlotsContext *ctx;
|
|
MMIfaceModem *self;
|
|
Private *priv;
|
|
g_autoptr(GError) error = NULL;
|
|
GArray *physical_slots = NULL;
|
|
GArray *ext_information = NULL;
|
|
GArray *slot_eids = NULL;
|
|
guint i;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
output = qmi_client_uim_get_slot_status_finish (client, res, &error);
|
|
if (!output ||
|
|
!qmi_message_uim_get_slot_status_output_get_result (output, &error) ||
|
|
!qmi_message_uim_get_slot_status_output_get_physical_slot_status (output, &physical_slots, &error)) {
|
|
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_DEVICE_UNSUPPORTED) ||
|
|
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND) ||
|
|
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED))
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"QMI SIM slot switch operation not supported");
|
|
else
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Store the slot status before loading all sim slots.
|
|
* We will use this to check if a hotswap requires a reprobe. */
|
|
if (priv->slots_status)
|
|
g_array_unref (priv->slots_status);
|
|
priv->slots_status = g_array_ref (physical_slots);
|
|
|
|
/* It's fine if we don't have EID information, but it should be well-formed if present. If it's malformed,
|
|
* there is probably a modem firmware bug. */
|
|
if (qmi_message_uim_get_slot_status_output_get_physical_slot_information (output, &ext_information, NULL) &&
|
|
qmi_message_uim_get_slot_status_output_get_slot_eid (output, &slot_eids, NULL) &&
|
|
(ext_information->len != physical_slots->len || slot_eids->len != physical_slots->len)) {
|
|
g_task_return_new_error (task,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"UIM Get Slot Status returned malformed response");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->sim_slots = g_ptr_array_new_full (physical_slots->len, (GDestroyNotify) sim_slot_free);
|
|
|
|
for (i = 0; i < physical_slots->len; i++) {
|
|
QmiPhysicalSlotStatusSlot *slot_status;
|
|
QmiPhysicalSlotInformationSlot *slot_info;
|
|
MMBaseSim *sim;
|
|
g_autofree gchar *raw_iccid = NULL;
|
|
g_autofree gchar *iccid = NULL;
|
|
g_autofree gchar *eid = NULL;
|
|
g_autoptr(GError) inner_error = NULL;
|
|
gboolean sim_active = FALSE;
|
|
|
|
/* Store active slot info */
|
|
slot_status = &g_array_index (physical_slots, QmiPhysicalSlotStatusSlot, i);
|
|
if (slot_status->physical_slot_status == QMI_UIM_SLOT_STATE_ACTIVE) {
|
|
sim_active = TRUE;
|
|
ctx->active_logical_id = slot_status->logical_slot;
|
|
ctx->active_slot_number = i + 1;
|
|
}
|
|
|
|
if (!slot_status->iccid->len) {
|
|
mm_obj_dbg (self, "not creating SIM object: no SIM in slot %u", i + 1);
|
|
g_ptr_array_add (ctx->sim_slots, NULL);
|
|
continue;
|
|
}
|
|
|
|
/* This is supposed to be BCD, but some carriers have non-spec-compliant ICCIDs that use
|
|
* A-F characters as part of the operator-specific part of the ICCID. Parse it as hex and
|
|
* let mm_3gpp_parse_iccid take care of handling the A-F characters properly. */
|
|
raw_iccid = mm_utils_bin2hexstr ((const guint8 *)slot_status->iccid->data, slot_status->iccid->len);
|
|
if (!raw_iccid) {
|
|
mm_obj_warn (self, "not creating SIM object: failed to convert ICCID from BCD");
|
|
g_ptr_array_add (ctx->sim_slots, NULL);
|
|
continue;
|
|
}
|
|
|
|
iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error);
|
|
if (!iccid) {
|
|
mm_obj_warn (self, "not creating SIM object: couldn't parse SIM iccid: %s", inner_error->message);
|
|
g_ptr_array_add (ctx->sim_slots, NULL);
|
|
continue;
|
|
}
|
|
|
|
if (ext_information && slot_eids) {
|
|
slot_info = &g_array_index (ext_information, QmiPhysicalSlotInformationSlot, i);
|
|
if (slot_info->is_euicc) {
|
|
QmiSlotEidElement *slot_eid_element;
|
|
|
|
slot_eid_element = &g_array_index (slot_eids, QmiSlotEidElement, i);
|
|
if (slot_eid_element->eid->len)
|
|
eid = mm_decode_eid (slot_eid_element->eid->data, slot_eid_element->eid->len);
|
|
if (!eid)
|
|
mm_obj_dbg (self, "SIM in slot %d is marked as eUICC, but has malformed EID", i + 1);
|
|
}
|
|
}
|
|
|
|
sim = mm_sim_qmi_new_initialized (MM_BASE_MODEM (self),
|
|
TRUE, /* consider DMS UIM deprecated if we're creating SIM slots */
|
|
i + 1, /* slot number is the array index starting at 1 */
|
|
sim_active,
|
|
iccid,
|
|
NULL, /* imsi unknown */
|
|
eid, /* may be NULL, which is fine */
|
|
NULL, /* operator id unknown */
|
|
NULL, /* operator name unknown */
|
|
NULL); /* emergency numbers unknown */
|
|
g_ptr_array_add (ctx->sim_slots, sim);
|
|
}
|
|
g_assert_cmpuint (ctx->sim_slots->len, ==, physical_slots->len);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_load_sim_slots (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
LoadSimSlotsContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_UIM, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (LoadSimSlotsContext);
|
|
ctx->client_uim = QMI_CLIENT_UIM (g_object_ref (client));
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) load_sim_slots_context_free);
|
|
|
|
qmi_client_uim_get_slot_status (ctx->client_uim,
|
|
NULL,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_get_slot_status_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set Primary SIM slot (modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_qmi_set_primary_sim_slot_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
uim_switch_slot_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageUimSwitchSlotOutput) output = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
MMIfaceModem *self;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
output = qmi_client_uim_switch_slot_finish (client, res, &error);
|
|
if (!output || !qmi_message_uim_switch_slot_output_get_result (output, &error)) {
|
|
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT))
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_EXISTS,
|
|
"SIM slot switch operation not needed");
|
|
else
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
} else {
|
|
mm_obj_msg (self, "SIM slot switch operation request successful");
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
uim_switch_get_slot_status_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL;
|
|
g_autoptr(QmiMessageUimSwitchSlotInput) input = NULL;
|
|
MMIfaceModem *self;
|
|
g_autoptr(GError) error = NULL;
|
|
GArray *physical_slots = NULL;
|
|
guint i;
|
|
guint active_logical_id = 0;
|
|
guint active_slot_number;
|
|
guint slot_number;
|
|
|
|
self = g_task_get_source_object (task);
|
|
slot_number = GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
output = qmi_client_uim_get_slot_status_finish (client, res, &error);
|
|
if (!output ||
|
|
!qmi_message_uim_get_slot_status_output_get_result (output, &error) ||
|
|
!qmi_message_uim_get_slot_status_output_get_physical_slot_status (output, &physical_slots, &error)) {
|
|
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_DEVICE_UNSUPPORTED) ||
|
|
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND) ||
|
|
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED))
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"QMI SIM slot switch operation not supported");
|
|
else
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < physical_slots->len; i++) {
|
|
QmiPhysicalSlotStatusSlot *slot_status;
|
|
|
|
/* We look for the currently ACTIVE SIM card only! */
|
|
slot_status = &g_array_index (physical_slots, QmiPhysicalSlotStatusSlot, i);
|
|
if (slot_status->physical_slot_status != QMI_UIM_SLOT_STATE_ACTIVE)
|
|
continue;
|
|
|
|
active_logical_id = slot_status->logical_slot;
|
|
active_slot_number = i + 1;
|
|
}
|
|
|
|
if (!active_logical_id) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't find active slot logical ID");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (active_slot_number == slot_number) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_EXISTS,
|
|
"SIM slot switch operation not needed");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "requesting active logical id %d switch to SIM slot %u", active_logical_id, slot_number);
|
|
|
|
input = qmi_message_uim_switch_slot_input_new ();
|
|
qmi_message_uim_switch_slot_input_set_logical_slot (input, (guint8) active_logical_id, NULL);
|
|
qmi_message_uim_switch_slot_input_set_physical_slot (input, slot_number, NULL);
|
|
qmi_client_uim_switch_slot (client,
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_switch_slot_ready,
|
|
task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_set_primary_sim_slot (MMIfaceModem *self,
|
|
guint sim_slot,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_UIM, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, GUINT_TO_POINTER (sim_slot), NULL);
|
|
|
|
qmi_client_uim_get_slot_status (QMI_CLIENT_UIM (client),
|
|
NULL,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_switch_get_slot_status_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* UIM refresh indication handling */
|
|
|
|
#define REFRESH_START_TIMEOUT_SECS 3
|
|
|
|
static void
|
|
uim_refresh_complete (QmiClientUim *client,
|
|
QmiUimSessionType session_type)
|
|
{
|
|
g_autoptr(QmiMessageUimRefreshCompleteInput) refresh_complete_input = NULL;
|
|
GArray *placeholder_aid;
|
|
|
|
placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8));
|
|
|
|
refresh_complete_input = qmi_message_uim_refresh_complete_input_new ();
|
|
qmi_message_uim_refresh_complete_input_set_session (
|
|
refresh_complete_input,
|
|
session_type,
|
|
placeholder_aid, /* ignored */
|
|
NULL);
|
|
qmi_message_uim_refresh_complete_input_set_info (
|
|
refresh_complete_input,
|
|
TRUE,
|
|
NULL);
|
|
|
|
qmi_client_uim_refresh_complete (
|
|
client,
|
|
refresh_complete_input,
|
|
10,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
g_array_unref (placeholder_aid);
|
|
}
|
|
|
|
static gboolean
|
|
uim_start_refresh_timeout (MMSharedQmi *self)
|
|
{
|
|
Private *priv;
|
|
|
|
priv = get_private (self);
|
|
priv->uim_refresh_start_timeout_id = 0;
|
|
|
|
mm_obj_dbg (self, "refresh start timed out; trigger SIM change check");
|
|
|
|
mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), NULL, NULL);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
uim_refresh_indication_cb (QmiClientUim *client,
|
|
QmiIndicationUimRefreshOutput *output,
|
|
MMSharedQmi *self)
|
|
{
|
|
QmiUimRefreshStage stage;
|
|
QmiUimRefreshMode mode;
|
|
QmiUimSessionType session_type;
|
|
Private *priv;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
priv = get_private (self);
|
|
|
|
if (!qmi_indication_uim_refresh_output_get_event (output,
|
|
&stage,
|
|
&mode,
|
|
&session_type,
|
|
NULL,
|
|
NULL,
|
|
&error)) {
|
|
mm_obj_warn (self, "couldn't process UIM refresh indication: %s", error->message);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "refresh indication received: session type '%s', stage '%s', mode '%s'",
|
|
qmi_uim_session_type_get_string (session_type),
|
|
qmi_uim_refresh_stage_get_string (stage),
|
|
qmi_uim_refresh_mode_get_string (mode));
|
|
|
|
/* Support only the first slot for now. Primary GW provisioning is used in old modems. */
|
|
if (session_type != QMI_UIM_SESSION_TYPE_CARD_SLOT_1 &&
|
|
session_type != QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING) {
|
|
mm_obj_warn (self, "refresh session type not supported: %s", qmi_uim_session_type_get_string (session_type));
|
|
return;
|
|
}
|
|
|
|
/* Currently we handle UICC Reset type refresh, which can be used
|
|
* in profile switch scenarios, and Init Full FCN type refresh for
|
|
* SIM IMSI switch scenarios. In other cases we just trigger 'refresh
|
|
* complete' during start phase. Signal to notify about potential SIM
|
|
* profile switch is triggered when the refresh is ending. If it were
|
|
* triggered in start phase, reading SIM files seems to fail with
|
|
* an internal error.
|
|
*
|
|
* It's possible that 'end-with-success' stage never appears. For that,
|
|
* we start a timer at 'start' stage and if it expires, the SIM change
|
|
* check is triggered anyway. */
|
|
if (stage == QMI_UIM_REFRESH_STAGE_START) {
|
|
if (mode == QMI_UIM_REFRESH_MODE_RESET || mode == QMI_UIM_REFRESH_MODE_INIT_FULL_FCN) {
|
|
if (!priv->uim_refresh_start_timeout_id)
|
|
priv->uim_refresh_start_timeout_id = g_timeout_add_seconds (REFRESH_START_TIMEOUT_SECS,
|
|
(GSourceFunc)uim_start_refresh_timeout,
|
|
self);
|
|
} else
|
|
uim_refresh_complete (client, session_type);
|
|
} else if (stage == QMI_UIM_REFRESH_STAGE_END_WITH_SUCCESS) {
|
|
if (mode == QMI_UIM_REFRESH_MODE_RESET || mode == QMI_UIM_REFRESH_MODE_INIT_FULL_FCN) {
|
|
if (priv->uim_refresh_start_timeout_id) {
|
|
g_source_remove (priv->uim_refresh_start_timeout_id);
|
|
priv->uim_refresh_start_timeout_id = 0;
|
|
}
|
|
mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* UIM slot status indication handling */
|
|
|
|
/* Modifies the sim at slot == index+1, based on the content of slot_status.
|
|
* Primarily used when a hotswap occurs on the inactive slot */
|
|
static void
|
|
update_sim_from_slot_status (MMSharedQmi *self,
|
|
QmiPhysicalSlotStatusSlot *slot_status,
|
|
guint slot_index)
|
|
{
|
|
g_autoptr(MMBaseSim) sim = NULL;
|
|
g_autofree gchar *raw_iccid = NULL;
|
|
g_autofree gchar *iccid = NULL;
|
|
g_autoptr(GError) inner_error = NULL;
|
|
|
|
mm_obj_dbg (self, "Updating sim at slot %d", slot_index + 1);
|
|
/* If sim is not present, ask mm_iface_modem to clear the sim object */
|
|
if (slot_status->physical_card_status != QMI_UIM_PHYSICAL_CARD_STATE_PRESENT) {
|
|
mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, NULL);
|
|
return;
|
|
}
|
|
|
|
raw_iccid = mm_utils_bin2hexstr ((const guint8 *)slot_status->iccid->data, slot_status->iccid->len);
|
|
if (!raw_iccid) {
|
|
mm_obj_warn (self, "not creating SIM object: failed to convert ICCID from BCD");
|
|
return;
|
|
}
|
|
iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error);
|
|
if (!iccid) {
|
|
mm_obj_warn (self, "not creating SIM object: couldn't parse SIM iccid: %s", inner_error->message);
|
|
return;
|
|
}
|
|
sim = mm_sim_qmi_new_initialized (MM_BASE_MODEM (self),
|
|
TRUE, /* consider DMS UIM deprecated if we're creating SIM slots */
|
|
slot_index + 1, /* slot number is the array index starting at 1 */
|
|
slot_status->physical_slot_status, /* is_active */
|
|
iccid,
|
|
NULL, /* imsi unknown */
|
|
NULL, /* eid unknown */
|
|
NULL, /* operator id unknown */
|
|
NULL, /* operator name unknown */
|
|
NULL); /* emergency numbers unknown */
|
|
|
|
mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, sim);
|
|
}
|
|
|
|
/* Checks for equality of two slots. */
|
|
static gboolean
|
|
slot_status_equal (QmiPhysicalSlotStatusSlot *slot_a,
|
|
QmiPhysicalSlotStatusSlot *slot_b)
|
|
{
|
|
guint j;
|
|
|
|
if (slot_a->physical_slot_status != slot_b->physical_slot_status)
|
|
return FALSE;
|
|
if (slot_a->physical_card_status != slot_b->physical_card_status)
|
|
return FALSE;
|
|
if (slot_a->iccid->len != slot_b->iccid->len)
|
|
return FALSE;
|
|
for (j = 0; j < slot_a->iccid->len; j++) {
|
|
if (g_array_index (slot_a->iccid, guint8, j) != g_array_index (slot_b->iccid, guint8, j))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Checks for equality of QmiPhysicalSlotStatusSlot arrays.
|
|
* The number of elements in each array is expected to be the number of sim slots in the modem.*/
|
|
static gboolean
|
|
slot_array_status_equal (GArray *slots_status1,
|
|
GArray *slots_status2,
|
|
gboolean check_active_slots_only)
|
|
{
|
|
guint i;
|
|
|
|
if (!slots_status1 && !slots_status2)
|
|
return TRUE;
|
|
if (!slots_status1 || !slots_status2 || slots_status1->len != slots_status2->len)
|
|
return FALSE;
|
|
for (i = 0; i < slots_status1->len; i++) {
|
|
/* Compare slot at index i from slots_status1 and slots_status2 */
|
|
QmiPhysicalSlotStatusSlot *slot_a;
|
|
QmiPhysicalSlotStatusSlot *slot_b;
|
|
|
|
slot_a = &g_array_index (slots_status1, QmiPhysicalSlotStatusSlot, i);
|
|
slot_b = &g_array_index (slots_status2, QmiPhysicalSlotStatusSlot, i);
|
|
|
|
/* Check that slot_a and slot_b have the same slot status (i.e. active or inactive) */
|
|
if (slot_a->physical_slot_status != slot_b->physical_slot_status)
|
|
return FALSE;
|
|
/* Once slot_a and slot_b are confirmed to have the same physical slot status,
|
|
* we will ignore inactive slots if check_active_slots_only is set. */
|
|
if (check_active_slots_only && slot_a->physical_slot_status != QMI_UIM_SLOT_STATE_ACTIVE) {
|
|
g_assert (slot_a->physical_slot_status == slot_b->physical_slot_status);
|
|
continue;
|
|
}
|
|
if (!slot_status_equal (slot_a, slot_b))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
uim_slot_status_indication_cb (QmiClientUim *client,
|
|
QmiIndicationUimSlotStatusOutput *output,
|
|
MMSharedQmi *self)
|
|
{
|
|
GArray *new_slots_status = NULL;
|
|
Private *priv;
|
|
guint i;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
priv = get_private (self);
|
|
mm_obj_dbg (self, "received slot status indication");
|
|
|
|
if (!priv->slots_status) {
|
|
mm_obj_dbg (self, "initial slot status is not loaded yet");
|
|
return;
|
|
}
|
|
|
|
if (!qmi_indication_uim_slot_status_output_get_physical_slot_status (output,
|
|
&new_slots_status,
|
|
&error)) {
|
|
mm_obj_warn (self, "could not process slot status indication: %s", error->message);
|
|
return;
|
|
}
|
|
|
|
/* A slot status indication means that
|
|
* 1) The physical slot to logical slot mapping has changed as a
|
|
* result of switching the slot. or,
|
|
* 2) A card has been removed from, or inserted to, the physical slot. or,
|
|
* 3) A physical slot is powered up or down. */
|
|
|
|
/* Reprobe if the active slot changed or the information in an
|
|
* active slot's status changed */
|
|
if (!slot_array_status_equal (priv->slots_status, new_slots_status, TRUE)) {
|
|
mm_obj_dbg (self, "An active slot had a status change, will reprobe the modem");
|
|
mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
|
|
return;
|
|
}
|
|
|
|
/* An inactive slot changed, we won't be reprobing the modem.
|
|
* Instead, we will ask mm_iface_modem to update the sim object.
|
|
* Iterate over each slot to identify the slots that changed state.*/
|
|
for (i = 0; i < priv->slots_status->len; i++) {
|
|
QmiPhysicalSlotStatusSlot *old_slot;
|
|
QmiPhysicalSlotStatusSlot *new_slot;
|
|
|
|
old_slot = &g_array_index (priv->slots_status, QmiPhysicalSlotStatusSlot, i);
|
|
new_slot = &g_array_index (new_slots_status, QmiPhysicalSlotStatusSlot, i);
|
|
|
|
if (!slot_status_equal (old_slot, new_slot)) {
|
|
mm_obj_dbg (self, "Slot %d (inactive) had a status change. Will update sims, but not reprobe", i + 1);
|
|
update_sim_from_slot_status (self, new_slot, i);
|
|
}
|
|
}
|
|
|
|
g_clear_pointer (&priv->slots_status, g_array_unref);
|
|
priv->slots_status = g_array_ref (new_slots_status);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SIM hot swap setup */
|
|
|
|
typedef enum {
|
|
SETUP_SIM_HOT_SWAP_STEP_FIRST,
|
|
SETUP_SIM_HOT_SWAP_STEP_UIM_REGISTER_SLOT_STATUS,
|
|
SETUP_SIM_HOT_SWAP_STEP_UIM_CHECK_SLOT_STATUS,
|
|
SETUP_SIM_HOT_SWAP_STEP_UIM_SLOT_STATUS_INDICATION,
|
|
SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ALL,
|
|
SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ICCID,
|
|
SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_IMSI,
|
|
SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_INDICATION,
|
|
SETUP_SIM_HOT_SWAP_STEP_LAST,
|
|
} SetupSimHotSwapStep;
|
|
|
|
typedef struct {
|
|
SetupSimHotSwapStep step;
|
|
gboolean register_slot_status_supported;
|
|
gboolean get_slot_status_supported;
|
|
gboolean refresh_all_supported;
|
|
gboolean refresh_file_supported;
|
|
QmiClient *uim_client;
|
|
gulong uim_slot_status_indication_id;
|
|
gulong uim_refresh_indication_id;
|
|
} SetupSimHotSwapContext;
|
|
|
|
static void setup_sim_hot_swap_step (GTask *task);
|
|
|
|
static void
|
|
setup_sim_hot_swap_context_free (SetupSimHotSwapContext *ctx)
|
|
{
|
|
if (ctx->uim_client && ctx->uim_slot_status_indication_id)
|
|
g_signal_handler_disconnect (ctx->uim_client, ctx->uim_slot_status_indication_id);
|
|
if (ctx->uim_client && ctx->uim_refresh_indication_id)
|
|
g_signal_handler_disconnect (ctx->uim_client, ctx->uim_refresh_indication_id);
|
|
g_clear_object (&ctx->uim_client);
|
|
g_slice_free (SetupSimHotSwapContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_setup_sim_hot_swap_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
uim_refresh_register_file_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
g_autoptr(QmiMessageUimRefreshRegisterOutput) output = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
SetupSimHotSwapContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_uim_refresh_register_finish (client, res, &error);
|
|
if (!output || !qmi_message_uim_refresh_register_output_get_result (output, &error))
|
|
mm_obj_dbg (self, "file refresh registration using 'refresh register' failed: %s", error->message);
|
|
else {
|
|
mm_obj_dbg (self, "file refresh registered using 'refresh register'");
|
|
ctx->refresh_file_supported = TRUE;
|
|
}
|
|
|
|
/* Go on to next step */
|
|
ctx->step++;
|
|
setup_sim_hot_swap_step (task);
|
|
}
|
|
|
|
static void
|
|
uim_refresh_register_file (GTask *task,
|
|
guint16 file_id,
|
|
const guint16 *file_path,
|
|
gsize file_path_len)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetupSimHotSwapContext *ctx;
|
|
QmiMessageUimRefreshRegisterInputInfoFilesElement file_element;
|
|
guint8 val;
|
|
gsize i;
|
|
g_autoptr(QmiMessageUimRefreshRegisterInput) refresh_register_input = NULL;
|
|
g_autoptr(GArray) placeholder_aid = NULL;
|
|
g_autoptr(GArray) file = NULL;
|
|
g_autoptr(GArray) file_element_path = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* This is the last resort if 'refresh register all' does not work. It works
|
|
* on some older modems. Those modems may not also support QMI_UIM_SESSION_TYPE_CARD_SLOT_1
|
|
* so we'll use QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING */
|
|
|
|
mm_obj_dbg (self, "register for refresh file indication");
|
|
|
|
placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8));
|
|
|
|
file = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageUimRefreshRegisterInputInfoFilesElement), 1);
|
|
|
|
file_element_path = g_array_sized_new (FALSE, FALSE, sizeof (guint8), file_path_len * 2);
|
|
for (i = 0; i < file_path_len; ++i) {
|
|
val = file_path[i] & 0xFF;
|
|
g_array_append_val (file_element_path, val);
|
|
val = (file_path[i] >> 8) & 0xFF;
|
|
g_array_append_val (file_element_path, val);
|
|
}
|
|
|
|
|
|
memset (&file_element, 0, sizeof (file_element));
|
|
file_element.file_id = file_id;
|
|
file_element.path = file_element_path;
|
|
g_array_append_val (file, file_element);
|
|
|
|
refresh_register_input = qmi_message_uim_refresh_register_input_new ();
|
|
qmi_message_uim_refresh_register_input_set_info (refresh_register_input,
|
|
TRUE,
|
|
FALSE,
|
|
file,
|
|
NULL);
|
|
qmi_message_uim_refresh_register_input_set_session (refresh_register_input,
|
|
QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING,
|
|
placeholder_aid,
|
|
NULL);
|
|
|
|
qmi_client_uim_refresh_register (QMI_CLIENT_UIM (ctx->uim_client),
|
|
refresh_register_input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_refresh_register_file_ready,
|
|
task);
|
|
}
|
|
|
|
/* Refresh registration and event handling.
|
|
* This is used for detecting ICCID and IMSI change.
|
|
*/
|
|
|
|
static void
|
|
uim_refresh_register_all_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageUimRefreshRegisterAllOutput) output = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
MMIfaceModem *self;
|
|
SetupSimHotSwapContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_uim_refresh_register_all_finish (client, res, &error);
|
|
if (!output || !qmi_message_uim_refresh_register_all_output_get_result (output, &error)) {
|
|
mm_obj_dbg (self, "refresh register all operation failed: %s", error->message);
|
|
} else {
|
|
/* Jump to setup refresh indication signal */
|
|
mm_obj_dbg (self, "registered for all SIM refresh events");
|
|
ctx->refresh_all_supported = TRUE;
|
|
}
|
|
|
|
ctx->step++;
|
|
setup_sim_hot_swap_step (task);
|
|
}
|
|
|
|
static void
|
|
uim_check_get_slot_status_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
MMIfaceModem *self;
|
|
SetupSimHotSwapContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_uim_get_slot_status_finish (client, res, &error);
|
|
if (!output || !qmi_message_uim_get_slot_status_output_get_result (output, &error)) {
|
|
mm_obj_dbg (self, "slot status retrieval failed: %s", error->message);
|
|
} else {
|
|
/* Go on to next step */
|
|
mm_obj_dbg (self, "slot status retrieval succeeded");
|
|
ctx->get_slot_status_supported = TRUE;
|
|
}
|
|
|
|
ctx->step++;
|
|
setup_sim_hot_swap_step (task);
|
|
}
|
|
|
|
static void
|
|
uim_register_events_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageUimRegisterEventsOutput) output = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
MMIfaceModem *self;
|
|
SetupSimHotSwapContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* If event registration fails, go on with initialization. In that case
|
|
* we cannot use slot status indications to detect eUICC profile switches. */
|
|
output = qmi_client_uim_register_events_finish (client, res, &error);
|
|
if (!output || !qmi_message_uim_register_events_output_get_result (output, &error)) {
|
|
mm_obj_dbg (self, "not registered for slot status indications: %s", error->message);
|
|
} else {
|
|
mm_obj_dbg (self, "registered for slot status indications");
|
|
ctx->register_slot_status_supported = TRUE;
|
|
}
|
|
|
|
/* Go on to next step */
|
|
ctx->step++;
|
|
setup_sim_hot_swap_step (task);
|
|
}
|
|
|
|
static void
|
|
setup_sim_hot_swap_step (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
SetupSimHotSwapContext *ctx;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
switch (ctx->step) {
|
|
case SETUP_SIM_HOT_SWAP_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_UIM_REGISTER_SLOT_STATUS: {
|
|
g_autoptr(QmiMessageUimRegisterEventsInput) register_events_input = NULL;
|
|
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for slot status indications...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
register_events_input = qmi_message_uim_register_events_input_new ();
|
|
qmi_message_uim_register_events_input_set_event_registration_mask (register_events_input,
|
|
QMI_UIM_EVENT_REGISTRATION_FLAG_PHYSICAL_SLOT_STATUS,
|
|
NULL);
|
|
qmi_client_uim_register_events (QMI_CLIENT_UIM (ctx->uim_client),
|
|
register_events_input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_register_events_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_UIM_CHECK_SLOT_STATUS:
|
|
if (ctx->register_slot_status_supported) {
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): checking slot status support...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
/* Successful registration does not mean that the modem actually sends
|
|
* physical slot status indications; invoke Get Slot Status to find out if
|
|
* the modem really supports slot status. */
|
|
qmi_client_uim_get_slot_status (QMI_CLIENT_UIM (ctx->uim_client),
|
|
NULL,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_check_get_slot_status_ready,
|
|
task);
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to check for slot status support...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_UIM_SLOT_STATUS_INDICATION:
|
|
if (ctx->register_slot_status_supported && ctx->get_slot_status_supported) {
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): monitoring slot status indications...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
ctx->uim_slot_status_indication_id = g_signal_connect (ctx->uim_client,
|
|
"slot-status",
|
|
G_CALLBACK (uim_slot_status_indication_cb),
|
|
self);
|
|
} else
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to monitor for slot status indications...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ALL: {
|
|
g_autoptr(QmiMessageUimRefreshRegisterAllInput) refresh_register_all_input = NULL;
|
|
g_autoptr(GArray) placeholder_aid = NULL;
|
|
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for all refresh events...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8));
|
|
refresh_register_all_input = qmi_message_uim_refresh_register_all_input_new ();
|
|
|
|
qmi_message_uim_refresh_register_all_input_set_info (refresh_register_all_input,
|
|
TRUE,
|
|
NULL);
|
|
qmi_message_uim_refresh_register_all_input_set_session (refresh_register_all_input,
|
|
QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING,
|
|
placeholder_aid,
|
|
NULL);
|
|
|
|
qmi_client_uim_refresh_register_all (QMI_CLIENT_UIM (ctx->uim_client),
|
|
refresh_register_all_input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_refresh_register_all_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ICCID:
|
|
/* If refresh all not supported, register for a single file refresh notification */
|
|
if (!ctx->refresh_all_supported) {
|
|
const guint16 file_path[] = { 0x3F00 };
|
|
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for SIM ICCID refresh events...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
uim_refresh_register_file (task, 0x2FE2, file_path, G_N_ELEMENTS (file_path));
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to register for SIM ICCID refresh events",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_IMSI:
|
|
/* If refresh all not supported, register for a single file refresh notification */
|
|
if (!ctx->refresh_all_supported) {
|
|
const guint16 file_path[] = { 0x3F00, 0x7FFF };
|
|
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for SIM IMSI refresh events...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
uim_refresh_register_file (task, 0x6F07, file_path, G_N_ELEMENTS (file_path));
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to register for SIM IMSI refresh events...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_INDICATION:
|
|
if (ctx->refresh_all_supported || ctx->refresh_file_supported) {
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): monitoring refresh indications...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
ctx->uim_refresh_indication_id =
|
|
g_signal_connect (ctx->uim_client,
|
|
"refresh",
|
|
G_CALLBACK (uim_refresh_indication_cb),
|
|
self);
|
|
} else
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to monitor for refresh indications...",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
|
|
ctx->step++;
|
|
/* fall-through */
|
|
|
|
case SETUP_SIM_HOT_SWAP_STEP_LAST:
|
|
if (ctx->refresh_all_supported ||
|
|
ctx->refresh_file_supported ||
|
|
(ctx->register_slot_status_supported && ctx->get_slot_status_supported)) {
|
|
/* at least one method was supported, transfer state to the private
|
|
* info and return success */
|
|
g_assert (!priv->uim_client);
|
|
priv->uim_client = g_steal_pointer (&ctx->uim_client);
|
|
if (ctx->uim_slot_status_indication_id) {
|
|
g_assert (!priv->uim_slot_status_indication_id);
|
|
priv->uim_slot_status_indication_id = ctx->uim_slot_status_indication_id;
|
|
ctx->uim_slot_status_indication_id = 0;
|
|
}
|
|
if (ctx->uim_refresh_indication_id) {
|
|
g_assert (!priv->uim_refresh_indication_id);
|
|
priv->uim_refresh_indication_id = ctx->uim_refresh_indication_id;
|
|
ctx->uim_refresh_indication_id = 0;
|
|
}
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): successfully finished",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
g_task_return_boolean (task, TRUE);
|
|
} else {
|
|
mm_obj_dbg (self, "setup SIM hot swap (%u/%u): failed",
|
|
ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST);
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't setup SIM hot swap");
|
|
}
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_setup_sim_hot_swap (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
SetupSimHotSwapContext *ctx;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_UIM, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (SetupSimHotSwapContext);
|
|
ctx->step = SETUP_SIM_HOT_SWAP_STEP_FIRST;
|
|
ctx->uim_client = g_object_ref (client);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)setup_sim_hot_swap_context_free);
|
|
|
|
setup_sim_hot_swap_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Set packet service state (3GPP interface) */
|
|
|
|
typedef struct {
|
|
QmiClientNas *client;
|
|
MMModem3gppPacketServiceState packet_service_state;
|
|
} SetPacketServiceStateContext;
|
|
|
|
static void
|
|
set_packet_service_state_context_free (SetPacketServiceStateContext *ctx)
|
|
{
|
|
g_object_unref (ctx->client);
|
|
g_slice_free (SetPacketServiceStateContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_set_packet_service_state_finish (MMIfaceModem3gpp *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
set_packet_service_state_ia_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(QmiMessageNasAttachDetachOutput) output = NULL;
|
|
|
|
output = qmi_client_nas_attach_detach_finish (client, res, &error);
|
|
if ((!output || !qmi_message_nas_attach_detach_output_get_result (output, &error)) &&
|
|
!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_prefix_error (&error, "Couldn't set packet service state: ");
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
} else
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
set_packet_service_state_ia (GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageNasAttachDetachInput) input = NULL;
|
|
SetPacketServiceStateContext *ctx;
|
|
|
|
input = qmi_message_nas_attach_detach_input_new ();
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED)
|
|
qmi_message_nas_attach_detach_input_set_action (input, QMI_NAS_PS_ATTACH_ACTION_ATTACH, NULL);
|
|
else if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED)
|
|
qmi_message_nas_attach_detach_input_set_action (input, QMI_NAS_PS_ATTACH_ACTION_DETACH, NULL);
|
|
else
|
|
g_assert_not_reached ();
|
|
|
|
qmi_client_nas_attach_detach (
|
|
ctx->client,
|
|
input,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)set_packet_service_state_ia_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
set_packet_service_state_sssp_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(QmiMessageNasSetSystemSelectionPreferenceOutput) output = NULL;
|
|
|
|
output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
|
|
if ((!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) &&
|
|
!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_prefix_error (&error, "Couldn't set packet service state: ");
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
} else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
set_packet_service_state_sssp (GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageNasSetSystemSelectionPreferenceInput) input = NULL;
|
|
SetPacketServiceStateContext *ctx;
|
|
|
|
input = qmi_message_nas_set_system_selection_preference_input_new ();
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED)
|
|
qmi_message_nas_set_system_selection_preference_input_set_service_domain_preference (
|
|
input, QMI_NAS_SERVICE_DOMAIN_PREFERENCE_PS_ATTACH, NULL);
|
|
else if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED)
|
|
qmi_message_nas_set_system_selection_preference_input_set_service_domain_preference (
|
|
input, QMI_NAS_SERVICE_DOMAIN_PREFERENCE_PS_DETACH, NULL);
|
|
else
|
|
g_assert_not_reached ();
|
|
|
|
qmi_client_nas_set_system_selection_preference (
|
|
ctx->client,
|
|
input,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)set_packet_service_state_sssp_ready,
|
|
task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_set_packet_service_state (MMIfaceModem3gpp *self,
|
|
MMModem3gppPacketServiceState packet_service_state,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
SetPacketServiceStateContext *ctx;
|
|
QmiClient *client = NULL;
|
|
Private *priv = NULL;
|
|
|
|
g_assert ((packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) ||
|
|
(packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED));
|
|
|
|
/* Get NAS client */
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_NAS, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (SetPacketServiceStateContext);
|
|
ctx->client = QMI_CLIENT_NAS (g_object_ref (client));
|
|
ctx->packet_service_state = packet_service_state;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)set_packet_service_state_context_free);
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
if (priv->feature_nas_ssp == FEATURE_SUPPORTED)
|
|
set_packet_service_state_sssp (task);
|
|
else
|
|
set_packet_service_state_ia (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: Set SUPL server */
|
|
|
|
typedef struct {
|
|
QmiClient *client;
|
|
gchar *supl;
|
|
glong indication_id;
|
|
guint timeout_id;
|
|
} SetSuplServerContext;
|
|
|
|
static void
|
|
set_supl_server_context_free (SetSuplServerContext *ctx)
|
|
{
|
|
if (ctx->client) {
|
|
if (ctx->timeout_id)
|
|
g_source_remove (ctx->timeout_id);
|
|
if (ctx->indication_id)
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
g_object_unref (ctx->client);
|
|
}
|
|
g_free (ctx->supl);
|
|
g_slice_free (SetSuplServerContext, ctx);
|
|
}
|
|
|
|
static GArray *
|
|
parse_as_utf16_url (const gchar *supl)
|
|
{
|
|
GArray *url;
|
|
gchar *utf16;
|
|
gsize utf16_len;
|
|
|
|
utf16 = g_convert (supl, -1, "UTF-16BE", "UTF-8", NULL, &utf16_len, NULL);
|
|
url = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), utf16_len),
|
|
utf16, utf16_len);
|
|
g_free (utf16);
|
|
return url;
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_location_set_supl_server_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pds_set_agps_config_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdsSetAgpsConfigOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_pds_set_agps_config_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_pds_set_agps_config_output_get_result (output, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
qmi_message_pds_set_agps_config_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
pds_set_supl_server (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetSuplServerContext *ctx;
|
|
QmiMessagePdsSetAgpsConfigInput *input;
|
|
guint32 ip = 0;
|
|
guint16 port;
|
|
GArray *url;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!mm_parse_supl_address (ctx->supl, NULL, &ip, &port, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
input = qmi_message_pds_set_agps_config_input_new ();
|
|
|
|
/* For multimode devices, prefer UMTS by default */
|
|
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self)))
|
|
qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL);
|
|
else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self)))
|
|
qmi_message_pds_set_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL);
|
|
|
|
if (ip)
|
|
qmi_message_pds_set_agps_config_input_set_location_server_address (input, GUINT32_FROM_BE (ip), port, NULL);
|
|
else {
|
|
url = parse_as_utf16_url (ctx->supl);
|
|
qmi_message_pds_set_agps_config_input_set_location_server_url (input, url, NULL);
|
|
g_array_unref (url);
|
|
}
|
|
|
|
qmi_client_pds_set_agps_config (
|
|
QMI_CLIENT_PDS (ctx->client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)pds_set_agps_config_ready,
|
|
task);
|
|
qmi_message_pds_set_agps_config_input_unref (input);
|
|
}
|
|
|
|
static gboolean
|
|
loc_location_set_server_indication_timed_out (GTask *task)
|
|
{
|
|
SetSuplServerContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Failed to receive indication with the server update result");
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
loc_location_set_server_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocSetServerOutput *output,
|
|
GTask *task)
|
|
{
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
|
|
if (!qmi_indication_loc_set_server_output_get_indication_status (output, &status, &error)) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
goto out;
|
|
}
|
|
|
|
mm_error_from_qmi_loc_indication_status (status, &error);
|
|
|
|
out:
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
loc_set_server_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetSuplServerContext *ctx;
|
|
QmiMessageLocSetServerOutput *output;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_loc_set_server_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_set_server_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_set_server_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"set-server",
|
|
G_CALLBACK (loc_location_set_server_indication_cb),
|
|
task);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)loc_location_set_server_indication_timed_out,
|
|
task);
|
|
|
|
qmi_message_loc_set_server_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
loc_set_supl_server (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetSuplServerContext *ctx;
|
|
QmiMessageLocSetServerInput *input;
|
|
guint32 ip = 0;
|
|
guint16 port;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!mm_parse_supl_address (ctx->supl, NULL, &ip, &port, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
input = qmi_message_loc_set_server_input_new ();
|
|
|
|
/* For multimode devices, prefer UMTS by default */
|
|
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self)))
|
|
qmi_message_loc_set_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_UMTS_SLP, NULL);
|
|
else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self)))
|
|
qmi_message_loc_set_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_CDMA_PDE, NULL);
|
|
|
|
if (ip)
|
|
qmi_message_loc_set_server_input_set_ipv4 (input, GUINT32_FROM_BE (ip), (guint32) port, NULL);
|
|
else
|
|
qmi_message_loc_set_server_input_set_url (input, ctx->supl, NULL);
|
|
|
|
qmi_client_loc_set_server (
|
|
QMI_CLIENT_LOC (ctx->client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)loc_set_server_ready,
|
|
task);
|
|
qmi_message_loc_set_server_input_unref (input);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_location_set_supl_server (MMIfaceModemLocation *self,
|
|
const gchar *supl,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
SetSuplServerContext *ctx;
|
|
QmiClient *client = NULL;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (SetSuplServerContext);
|
|
ctx->supl = g_strdup (supl);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)set_supl_server_context_free);
|
|
|
|
/* Prefer PDS */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDS,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
ctx->client = g_object_ref (client);
|
|
pds_set_supl_server (task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise LOC */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_LOC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
ctx->client = g_object_ref (client);
|
|
loc_set_supl_server (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't find any PDS/LOC client");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: Load SUPL server */
|
|
|
|
typedef struct {
|
|
QmiClient *client;
|
|
glong indication_id;
|
|
guint timeout_id;
|
|
} LoadSuplServerContext;
|
|
|
|
static void
|
|
load_supl_server_context_free (LoadSuplServerContext *ctx)
|
|
{
|
|
if (ctx->client) {
|
|
if (ctx->timeout_id)
|
|
g_source_remove (ctx->timeout_id);
|
|
if (ctx->indication_id)
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
g_object_unref (ctx->client);
|
|
}
|
|
g_slice_free (LoadSuplServerContext, ctx);
|
|
}
|
|
|
|
gchar *
|
|
mm_shared_qmi_location_load_supl_server_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pds_get_agps_config_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdsGetAgpsConfigOutput *output;
|
|
GError *error = NULL;
|
|
guint32 ip = 0;
|
|
guint32 port = 0;
|
|
GArray *url = NULL;
|
|
gchar *str = NULL;
|
|
|
|
output = qmi_client_pds_get_agps_config_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!qmi_message_pds_get_agps_config_output_get_result (output, &error))
|
|
goto out;
|
|
|
|
/* Prefer IP/PORT to URL */
|
|
if (qmi_message_pds_get_agps_config_output_get_location_server_address (
|
|
output,
|
|
&ip,
|
|
&port,
|
|
NULL) &&
|
|
ip != 0 &&
|
|
port != 0) {
|
|
struct in_addr a = { .s_addr = GUINT32_FROM_BE (ip) };
|
|
gchar buf[INET_ADDRSTRLEN + 1];
|
|
|
|
memset (buf, 0, sizeof (buf));
|
|
|
|
if (!inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)) {
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Cannot convert numeric IP address (%u) to string", ip);
|
|
goto out;
|
|
}
|
|
|
|
str = g_strdup_printf ("%s:%u", buf, port);
|
|
goto out;
|
|
}
|
|
|
|
if (qmi_message_pds_get_agps_config_output_get_location_server_url (
|
|
output,
|
|
&url,
|
|
NULL) &&
|
|
url->len > 0) {
|
|
str = g_convert (url->data, url->len, "UTF-8", "UTF-16BE", NULL, NULL, NULL);
|
|
}
|
|
|
|
if (!str)
|
|
str = g_strdup ("");
|
|
|
|
out:
|
|
if (error) {
|
|
g_free (str);
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
g_assert (str);
|
|
g_task_return_pointer (task, str, g_free);
|
|
}
|
|
g_object_unref (task);
|
|
|
|
if (output)
|
|
qmi_message_pds_get_agps_config_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
pds_load_supl_server (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
LoadSuplServerContext *ctx;
|
|
QmiMessagePdsGetAgpsConfigInput *input;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
input = qmi_message_pds_get_agps_config_input_new ();
|
|
|
|
/* For multimode devices, prefer UMTS by default */
|
|
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self)))
|
|
qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL);
|
|
else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self)))
|
|
qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL);
|
|
|
|
qmi_client_pds_get_agps_config (
|
|
QMI_CLIENT_PDS (ctx->client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)pds_get_agps_config_ready,
|
|
task);
|
|
qmi_message_pds_get_agps_config_input_unref (input);
|
|
}
|
|
|
|
static gboolean
|
|
loc_location_get_server_indication_timed_out (GTask *task)
|
|
{
|
|
LoadSuplServerContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Failed to receive indication with the current server settings");
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
loc_location_get_server_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocGetServerOutput *output,
|
|
GTask *task)
|
|
{
|
|
QmiLocIndicationStatus status;
|
|
const gchar *url = NULL;
|
|
guint32 ipv4_address = 0;
|
|
guint16 ipv4_port = 0;
|
|
GError *error = NULL;
|
|
gchar *str = NULL;
|
|
|
|
if (!qmi_indication_loc_get_server_output_get_indication_status (output, &status, &error)) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!mm_error_from_qmi_loc_indication_status (status, &error))
|
|
goto out;
|
|
|
|
/* Prefer IP/PORT to URL */
|
|
|
|
if (qmi_indication_loc_get_server_output_get_ipv4 (
|
|
output,
|
|
&ipv4_address,
|
|
&ipv4_port,
|
|
NULL) &&
|
|
ipv4_address != 0 && ipv4_port != 0) {
|
|
struct in_addr a = { .s_addr = GUINT32_FROM_BE (ipv4_address) };
|
|
gchar buf[INET_ADDRSTRLEN + 1];
|
|
|
|
memset (buf, 0, sizeof (buf));
|
|
|
|
if (!inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)) {
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Cannot convert numeric IP address (%u) to string", ipv4_address);
|
|
goto out;
|
|
}
|
|
|
|
str = g_strdup_printf ("%s:%u", buf, ipv4_port);
|
|
goto out;
|
|
}
|
|
|
|
if (qmi_indication_loc_get_server_output_get_url (
|
|
output,
|
|
&url,
|
|
NULL) &&
|
|
url && url [0]) {
|
|
str = g_strdup (url);
|
|
}
|
|
|
|
if (!str)
|
|
str = g_strdup ("");
|
|
|
|
out:
|
|
if (error) {
|
|
g_free (str);
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
g_assert (str);
|
|
g_task_return_pointer (task, str, g_free);
|
|
}
|
|
g_object_unref (task);
|
|
|
|
}
|
|
|
|
static void
|
|
loc_get_server_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadSuplServerContext *ctx;
|
|
QmiMessageLocGetServerOutput *output;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_loc_get_server_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_get_server_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_get_server_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"get-server",
|
|
G_CALLBACK (loc_location_get_server_indication_cb),
|
|
task);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)loc_location_get_server_indication_timed_out,
|
|
task);
|
|
|
|
qmi_message_loc_get_server_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
loc_load_supl_server (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
LoadSuplServerContext *ctx;
|
|
QmiMessageLocGetServerInput *input;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
input = qmi_message_loc_get_server_input_new ();
|
|
|
|
/* For multimode devices, prefer UMTS by default */
|
|
if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self)))
|
|
qmi_message_loc_get_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_UMTS_SLP, NULL);
|
|
else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self)))
|
|
qmi_message_loc_get_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_CDMA_PDE, NULL);
|
|
|
|
qmi_message_loc_get_server_input_set_server_address_type (
|
|
input,
|
|
(QMI_LOC_SERVER_ADDRESS_TYPE_IPV4 | QMI_LOC_SERVER_ADDRESS_TYPE_URL),
|
|
NULL);
|
|
|
|
qmi_client_loc_get_server (
|
|
QMI_CLIENT_LOC (ctx->client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)loc_get_server_ready,
|
|
task);
|
|
qmi_message_loc_get_server_input_unref (input);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
QmiClient *client;
|
|
GTask *task;
|
|
LoadSuplServerContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (LoadSuplServerContext);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_supl_server_context_free);
|
|
|
|
/* Prefer PDS */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDS,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
ctx->client = g_object_ref (client);
|
|
pds_load_supl_server (task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise LOC */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_LOC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
ctx->client = g_object_ref (client);
|
|
loc_load_supl_server (task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't find any PDS/LOC client");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: internal helper: stop gps engine */
|
|
|
|
static gboolean
|
|
stop_gps_engine_finish (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pds_gps_service_state_stop_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiMessagePdsSetGpsServiceStateOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_pds_set_gps_service_state_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_pds_set_gps_service_state_output_get_result (output, &error)) {
|
|
if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_prefix_error (&error, "Couldn't set GPS service state: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_pds_set_gps_service_state_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
qmi_message_pds_set_gps_service_state_output_unref (output);
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
if (priv->pds_client) {
|
|
if (priv->pds_location_event_report_indication_id != 0) {
|
|
g_signal_handler_disconnect (priv->pds_client, priv->pds_location_event_report_indication_id);
|
|
priv->pds_location_event_report_indication_id = 0;
|
|
}
|
|
g_clear_object (&priv->pds_client);
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
loc_stop_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiMessageLocStopOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_loc_stop_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_stop_output_get_result (output, &error)
|
|
&& !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_PERMISSION)) {
|
|
g_prefix_error (&error, "Couldn't stop GPS engine: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_stop_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
g_clear_error (&error);
|
|
qmi_message_loc_stop_output_unref (output);
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
if (priv->loc_client) {
|
|
if (priv->loc_location_nmea_indication_id != 0) {
|
|
g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id);
|
|
priv->loc_location_nmea_indication_id = 0;
|
|
}
|
|
g_clear_object (&priv->loc_client);
|
|
}
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
stop_gps_engine (MMSharedQmi *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
priv = get_private (self);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
if (!mm_location_cache_save (priv->location_cache, &error))
|
|
mm_obj_warn (self, "%s", error->message);
|
|
|
|
if (priv->pds_client) {
|
|
QmiMessagePdsSetGpsServiceStateInput *input;
|
|
|
|
input = qmi_message_pds_set_gps_service_state_input_new ();
|
|
qmi_message_pds_set_gps_service_state_input_set_state (input, FALSE, NULL);
|
|
qmi_client_pds_set_gps_service_state (
|
|
QMI_CLIENT_PDS (priv->pds_client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)pds_gps_service_state_stop_ready,
|
|
task);
|
|
qmi_message_pds_set_gps_service_state_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
if (priv->loc_client) {
|
|
QmiMessageLocStopInput *input;
|
|
|
|
input = qmi_message_loc_stop_input_new ();
|
|
qmi_message_loc_stop_input_set_session_id (input, DEFAULT_LOC_SESSION_ID, NULL);
|
|
qmi_client_loc_stop (QMI_CLIENT_LOC (priv->loc_client),
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) loc_stop_ready,
|
|
task);
|
|
qmi_message_loc_stop_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't find any PDS/LOC client");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: internal helpers: NMEA indication callbacks */
|
|
|
|
static void
|
|
pds_location_event_report_indication_cb (QmiClientPds *client,
|
|
QmiIndicationPdsEventReportOutput *output,
|
|
MMSharedQmi *self)
|
|
{
|
|
QmiPdsPositionSessionStatus session_status;
|
|
const gchar *nmea;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
if (qmi_indication_pds_event_report_output_get_position_session_status (
|
|
output,
|
|
&session_status,
|
|
NULL)) {
|
|
mm_obj_dbg (self, "[GPS] session status changed: '%s'",
|
|
qmi_pds_position_session_status_get_string (session_status));
|
|
}
|
|
|
|
if (qmi_indication_pds_event_report_output_get_nmea_position (
|
|
output,
|
|
&nmea,
|
|
NULL)) {
|
|
mm_obj_dbg (self, "[NMEA] %s", nmea);
|
|
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea);
|
|
mm_location_cache_update_from_nmea (priv->location_cache, nmea);
|
|
}
|
|
}
|
|
|
|
static void
|
|
loc_location_nmea_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocNmeaOutput *output,
|
|
MMSharedQmi *self)
|
|
{
|
|
const gchar *nmea = NULL;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
qmi_indication_loc_nmea_output_get_nmea_string (output, &nmea, NULL);
|
|
if (!nmea)
|
|
return;
|
|
|
|
mm_obj_dbg (self, "[NMEA] %s", nmea);
|
|
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea);
|
|
mm_location_cache_update_from_nmea (priv->location_cache, nmea);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: internal helper: setup minimum required NMEA traces */
|
|
|
|
typedef struct {
|
|
QmiClientLoc *client;
|
|
GCancellable *req_cancel;
|
|
guint timeout_id;
|
|
gulong indication_id;
|
|
} SetupRequiredNmeaTracesContext;
|
|
|
|
static void
|
|
setup_required_nmea_traces_cleanup_action (SetupRequiredNmeaTracesContext *ctx)
|
|
{
|
|
if (ctx->indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
ctx->indication_id = 0;
|
|
}
|
|
if (ctx->timeout_id) {
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
}
|
|
if (ctx->req_cancel) {
|
|
g_cancellable_cancel (ctx->req_cancel);
|
|
g_object_unref (g_steal_pointer (&ctx->req_cancel));
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_required_nmea_traces_context_free (SetupRequiredNmeaTracesContext *ctx)
|
|
{
|
|
setup_required_nmea_traces_cleanup_action (ctx);
|
|
g_clear_object (&ctx->client);
|
|
g_slice_free (SetupRequiredNmeaTracesContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
setup_required_nmea_traces_finish (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static gboolean
|
|
setup_required_nmea_traces_timeout (GTask *task)
|
|
{
|
|
SetupRequiredNmeaTracesContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->timeout_id);
|
|
setup_required_nmea_traces_cleanup_action (ctx);
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation timed out");
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
loc_set_nmea_types_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocSetNmeaTypesOutput *output,
|
|
GTask *task)
|
|
{
|
|
SetupRequiredNmeaTracesContext *ctx;
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->indication_id);
|
|
setup_required_nmea_traces_cleanup_action (ctx);
|
|
|
|
if (!qmi_indication_loc_set_nmea_types_output_get_indication_status (output, &status, &error) ||
|
|
!mm_error_from_qmi_loc_indication_status (status, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
loc_set_nmea_types_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetupRequiredNmeaTracesContext *ctx;
|
|
GError *error = NULL;
|
|
g_autoptr(QmiMessageLocSetNmeaTypesOutput) output = NULL;
|
|
|
|
output = qmi_client_loc_set_nmea_types_finish (client, res, &error);
|
|
if (!output || !qmi_message_loc_set_nmea_types_output_get_result (output, &error)) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
return;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
setup_required_nmea_traces_cleanup_action (ctx);
|
|
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
}
|
|
|
|
static void
|
|
loc_get_nmea_types_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocGetNmeaTypesOutput *output,
|
|
GTask *task)
|
|
{
|
|
SetupRequiredNmeaTracesContext *ctx;
|
|
QmiLocIndicationStatus status;
|
|
QmiLocNmeaType nmea_types_mask = 0;
|
|
QmiLocNmeaType desired_nmea_types_mask = (QMI_LOC_NMEA_TYPE_GGA | QMI_LOC_NMEA_TYPE_GSA | QMI_LOC_NMEA_TYPE_GSV);
|
|
GError *error = NULL;
|
|
g_autoptr(QmiMessageLocSetNmeaTypesInput) input = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->indication_id);
|
|
setup_required_nmea_traces_cleanup_action (ctx);
|
|
|
|
if (!qmi_indication_loc_get_nmea_types_output_get_indication_status (output, &status, &error) ||
|
|
!mm_error_from_qmi_loc_indication_status (status, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
qmi_indication_loc_get_nmea_types_output_get_nmea_types (output, &nmea_types_mask, NULL);
|
|
|
|
/* The task ownership is shared by the request, the signal, and the
|
|
* timeout; the one which is scheduled first will cancel the others.
|
|
*/
|
|
g_assert (!ctx->req_cancel);
|
|
ctx->req_cancel = g_cancellable_new ();
|
|
input = qmi_message_loc_set_nmea_types_input_new ();
|
|
qmi_message_loc_set_nmea_types_input_set_nmea_types (input, (nmea_types_mask | desired_nmea_types_mask), NULL);
|
|
qmi_client_loc_set_nmea_types (ctx->client,
|
|
input,
|
|
10,
|
|
ctx->req_cancel,
|
|
(GAsyncReadyCallback)loc_set_nmea_types_ready,
|
|
task);
|
|
|
|
g_assert (!ctx->indication_id);
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"set-nmea-types",
|
|
G_CALLBACK (loc_set_nmea_types_indication_cb),
|
|
task);
|
|
g_assert (!ctx->timeout_id);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)setup_required_nmea_traces_timeout,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
loc_get_nmea_types_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetupRequiredNmeaTracesContext *ctx;
|
|
GError *error = NULL;
|
|
g_autoptr(QmiMessageLocGetNmeaTypesOutput) output = NULL;
|
|
|
|
output = qmi_client_loc_get_nmea_types_finish (client, res, &error);
|
|
if (!output || !qmi_message_loc_get_nmea_types_output_get_result (output, &error)) {
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
return;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
setup_required_nmea_traces_cleanup_action (ctx);
|
|
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_required_nmea_traces (MMSharedQmi *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
QmiClient *client;
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* If using PDS, no further setup required */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDS,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise LOC */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_LOC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
SetupRequiredNmeaTracesContext *ctx;
|
|
|
|
ctx = g_slice_new0 (SetupRequiredNmeaTracesContext);
|
|
ctx->client = QMI_CLIENT_LOC (g_object_ref (client));
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)setup_required_nmea_traces_context_free);
|
|
|
|
/* The task ownership is shared by the request, the signal, and the
|
|
* timeout; the one which is scheduled first will cancel the others.
|
|
*/
|
|
g_assert (!ctx->req_cancel);
|
|
ctx->req_cancel = g_cancellable_new ();
|
|
qmi_client_loc_get_nmea_types (ctx->client,
|
|
NULL,
|
|
10,
|
|
ctx->req_cancel,
|
|
(GAsyncReadyCallback)loc_get_nmea_types_ready,
|
|
task);
|
|
|
|
g_assert (!ctx->indication_id);
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"get-nmea-types",
|
|
G_CALLBACK (loc_get_nmea_types_indication_cb),
|
|
task);
|
|
g_assert (!ctx->timeout_id);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)setup_required_nmea_traces_timeout,
|
|
task);
|
|
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't find any PDS/LOC client");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: internal helper: start gps engine */
|
|
|
|
static gboolean
|
|
start_gps_engine_finish (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pds_ser_location_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiMessagePdsSetEventReportOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_pds_set_event_report_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_pds_set_event_report_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't set event report: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_pds_set_event_report_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
qmi_message_pds_set_event_report_output_unref (output);
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
g_assert (!priv->pds_client);
|
|
g_assert (priv->pds_location_event_report_indication_id == 0);
|
|
priv->pds_client = QMI_CLIENT (g_object_ref (client));
|
|
priv->pds_location_event_report_indication_id =
|
|
g_signal_connect (priv->pds_client,
|
|
"event-report",
|
|
G_CALLBACK (pds_location_event_report_indication_cb),
|
|
self);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
pds_auto_tracking_state_start_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdsSetEventReportInput *input;
|
|
QmiMessagePdsSetAutoTrackingStateOutput *output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_pds_set_auto_tracking_state_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_pds_set_auto_tracking_state_output_get_result (output, &error)) {
|
|
if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_prefix_error (&error, "Couldn't set auto-tracking state: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_pds_set_auto_tracking_state_output_unref (output);
|
|
return;
|
|
}
|
|
g_error_free (error);
|
|
}
|
|
|
|
qmi_message_pds_set_auto_tracking_state_output_unref (output);
|
|
|
|
/* Only gather standard NMEA traces */
|
|
input = qmi_message_pds_set_event_report_input_new ();
|
|
qmi_message_pds_set_event_report_input_set_nmea_position_reporting (input, TRUE, NULL);
|
|
qmi_client_pds_set_event_report (
|
|
client,
|
|
input,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)pds_ser_location_ready,
|
|
task);
|
|
qmi_message_pds_set_event_report_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
pds_gps_service_state_start_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdsSetAutoTrackingStateInput *input;
|
|
QmiMessagePdsSetGpsServiceStateOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_pds_set_gps_service_state_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_pds_set_gps_service_state_output_get_result (output, &error)) {
|
|
if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
|
|
g_prefix_error (&error, "Couldn't set GPS service state: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_pds_set_gps_service_state_output_unref (output);
|
|
return;
|
|
}
|
|
g_error_free (error);
|
|
}
|
|
|
|
qmi_message_pds_set_gps_service_state_output_unref (output);
|
|
|
|
/* Enable auto-tracking for a continuous fix */
|
|
input = qmi_message_pds_set_auto_tracking_state_input_new ();
|
|
qmi_message_pds_set_auto_tracking_state_input_set_state (input, TRUE, NULL);
|
|
qmi_client_pds_set_auto_tracking_state (
|
|
client,
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)pds_auto_tracking_state_start_ready,
|
|
task);
|
|
qmi_message_pds_set_auto_tracking_state_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
loc_register_events_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiMessageLocRegisterEventsOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_loc_register_events_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_register_events_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't not register tracking events: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_register_events_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
qmi_message_loc_register_events_output_unref (output);
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
g_assert (!priv->loc_client);
|
|
g_assert (!priv->loc_location_nmea_indication_id);
|
|
priv->loc_client = QMI_CLIENT (g_object_ref (client));
|
|
priv->loc_location_nmea_indication_id =
|
|
g_signal_connect (client,
|
|
"nmea",
|
|
G_CALLBACK (loc_location_nmea_indication_cb),
|
|
self);
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
loc_start_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageLocRegisterEventsInput *input;
|
|
QmiMessageLocStartOutput *output;
|
|
guint64 event_mask;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_loc_start_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_start_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't start GPS engine: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_start_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
qmi_message_loc_start_output_unref (output);
|
|
|
|
input = qmi_message_loc_register_events_input_new ();
|
|
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA |
|
|
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST |
|
|
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_POSITION_REQUEST;
|
|
qmi_message_loc_register_events_input_set_event_registration_mask (
|
|
input, event_mask, NULL);
|
|
qmi_client_loc_register_events (client,
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) loc_register_events_ready,
|
|
task);
|
|
qmi_message_loc_register_events_input_unref (input);
|
|
}
|
|
|
|
|
|
static void
|
|
start_gps_engine (MMSharedQmi *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
QmiClient *client;
|
|
GTask *task;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* Prefer PDS */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDS,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
QmiMessagePdsSetGpsServiceStateInput *input;
|
|
|
|
input = qmi_message_pds_set_gps_service_state_input_new ();
|
|
qmi_message_pds_set_gps_service_state_input_set_state (input, TRUE, NULL);
|
|
qmi_client_pds_set_gps_service_state (
|
|
QMI_CLIENT_PDS (client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)pds_gps_service_state_start_ready,
|
|
task);
|
|
qmi_message_pds_set_gps_service_state_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise LOC */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_LOC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
QmiMessageLocStartInput *input;
|
|
|
|
input = qmi_message_loc_start_input_new ();
|
|
qmi_message_loc_start_input_set_session_id (input, DEFAULT_LOC_SESSION_ID, NULL);
|
|
qmi_message_loc_start_input_set_intermediate_report_state (input, QMI_LOC_INTERMEDIATE_REPORT_STATE_DISABLE, NULL);
|
|
qmi_message_loc_start_input_set_minimum_interval_between_position_reports (input, 1000, NULL);
|
|
qmi_message_loc_start_input_set_fix_recurrence_type (input, QMI_LOC_FIX_RECURRENCE_TYPE_REQUEST_PERIODIC_FIXES, NULL);
|
|
qmi_client_loc_start (QMI_CLIENT_LOC (client),
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) loc_start_ready,
|
|
task);
|
|
qmi_message_loc_start_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't find any PDS/LOC client");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: internal helper: select operation mode (msa/msb/standalone) */
|
|
|
|
typedef enum {
|
|
GPS_OPERATION_MODE_UNKNOWN,
|
|
GPS_OPERATION_MODE_STANDALONE,
|
|
GPS_OPERATION_MODE_AGPS_MSA,
|
|
GPS_OPERATION_MODE_AGPS_MSB,
|
|
} GpsOperationMode;
|
|
|
|
typedef struct {
|
|
QmiClient *client;
|
|
GpsOperationMode mode;
|
|
glong indication_id;
|
|
guint timeout_id;
|
|
} SetGpsOperationModeContext;
|
|
|
|
static void
|
|
set_gps_operation_mode_context_free (SetGpsOperationModeContext *ctx)
|
|
{
|
|
if (ctx->client) {
|
|
if (ctx->timeout_id)
|
|
g_source_remove (ctx->timeout_id);
|
|
if (ctx->indication_id)
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
g_object_unref (ctx->client);
|
|
}
|
|
g_slice_free (SetGpsOperationModeContext, ctx);
|
|
}
|
|
|
|
static gboolean
|
|
set_gps_operation_mode_finish (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pds_set_default_tracking_session_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetGpsOperationModeContext *ctx;
|
|
QmiMessagePdsSetDefaultTrackingSessionOutput *output;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_pds_set_default_tracking_session_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_pds_set_default_tracking_session_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't set default tracking session: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_pds_set_default_tracking_session_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
qmi_message_pds_set_default_tracking_session_output_unref (output);
|
|
|
|
switch (ctx->mode) {
|
|
case GPS_OPERATION_MODE_AGPS_MSA:
|
|
mm_obj_dbg (self, "MSA A-GPS operation mode enabled");
|
|
break;
|
|
case GPS_OPERATION_MODE_AGPS_MSB:
|
|
mm_obj_dbg (self, "MSB A-GPS operation mode enabled");
|
|
break;
|
|
case GPS_OPERATION_MODE_STANDALONE:
|
|
mm_obj_dbg (self, "standalone mode enabled (A-GPS disabled)");
|
|
break;
|
|
case GPS_OPERATION_MODE_UNKNOWN:
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
pds_get_default_tracking_session_ready (QmiClientPds *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetGpsOperationModeContext *ctx;
|
|
QmiMessagePdsSetDefaultTrackingSessionInput *input;
|
|
QmiMessagePdsGetDefaultTrackingSessionOutput *output;
|
|
GError *error = NULL;
|
|
QmiPdsOperatingMode session_operation;
|
|
guint8 data_timeout;
|
|
guint32 interval;
|
|
guint32 accuracy_threshold;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_pds_get_default_tracking_session_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_pds_get_default_tracking_session_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't get default tracking session: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_pds_get_default_tracking_session_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
qmi_message_pds_get_default_tracking_session_output_get_info (
|
|
output,
|
|
&session_operation,
|
|
&data_timeout,
|
|
&interval,
|
|
&accuracy_threshold,
|
|
NULL);
|
|
|
|
qmi_message_pds_get_default_tracking_session_output_unref (output);
|
|
|
|
if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSA) {
|
|
if (session_operation == QMI_PDS_OPERATING_MODE_MS_ASSISTED) {
|
|
mm_obj_dbg (self, "MSA A-GPS already enabled");
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "need to enable MSA A-GPS");
|
|
session_operation = QMI_PDS_OPERATING_MODE_MS_ASSISTED;
|
|
} else if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSB) {
|
|
if (session_operation == QMI_PDS_OPERATING_MODE_MS_BASED) {
|
|
mm_obj_dbg (self, "MSB A-GPS already enabled");
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "need to enable MSB A-GPS");
|
|
session_operation = QMI_PDS_OPERATING_MODE_MS_BASED;
|
|
} else if (ctx->mode == GPS_OPERATION_MODE_STANDALONE) {
|
|
if (session_operation == QMI_PDS_OPERATING_MODE_STANDALONE) {
|
|
mm_obj_dbg (self, "A-GPS already disabled");
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "need to disable A-GPS");
|
|
session_operation = QMI_PDS_OPERATING_MODE_STANDALONE;
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
input = qmi_message_pds_set_default_tracking_session_input_new ();
|
|
qmi_message_pds_set_default_tracking_session_input_set_info (
|
|
input,
|
|
session_operation,
|
|
data_timeout,
|
|
interval,
|
|
accuracy_threshold,
|
|
NULL);
|
|
qmi_client_pds_set_default_tracking_session (
|
|
client,
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)pds_set_default_tracking_session_ready,
|
|
task);
|
|
qmi_message_pds_set_default_tracking_session_input_unref (input);
|
|
}
|
|
|
|
static gboolean
|
|
loc_location_operation_mode_indication_timed_out (GTask *task)
|
|
{
|
|
SetGpsOperationModeContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Failed to receive operation mode indication");
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
loc_location_set_operation_mode_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocSetOperationModeOutput *output,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetGpsOperationModeContext *ctx;
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_loc_set_operation_mode_output_get_indication_status (output, &status, &error)) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!mm_error_from_qmi_loc_indication_status (status, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
switch (ctx->mode) {
|
|
case GPS_OPERATION_MODE_AGPS_MSA:
|
|
mm_obj_dbg (self, "MSA A-GPS operation mode enabled");
|
|
break;
|
|
case GPS_OPERATION_MODE_AGPS_MSB:
|
|
mm_obj_dbg (self, "MSB A-GPS operation mode enabled");
|
|
break;
|
|
case GPS_OPERATION_MODE_STANDALONE:
|
|
mm_obj_dbg (self, "standalone mode enabled (A-GPS disabled)");
|
|
break;
|
|
case GPS_OPERATION_MODE_UNKNOWN:
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
loc_set_operation_mode_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetGpsOperationModeContext *ctx;
|
|
QmiMessageLocSetOperationModeOutput *output;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_loc_set_operation_mode_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_set_operation_mode_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_set_operation_mode_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"set-operation-mode",
|
|
G_CALLBACK (loc_location_set_operation_mode_indication_cb),
|
|
task);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)loc_location_operation_mode_indication_timed_out,
|
|
task);
|
|
|
|
qmi_message_loc_set_operation_mode_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
loc_location_get_operation_mode_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocGetOperationModeOutput *output,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
SetGpsOperationModeContext *ctx;
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
QmiLocOperationMode mode = QMI_LOC_OPERATION_MODE_DEFAULT;
|
|
QmiMessageLocSetOperationModeInput *input;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_loc_get_operation_mode_output_get_indication_status (output, &status, &error)) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!mm_error_from_qmi_loc_indication_status (status, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
qmi_indication_loc_get_operation_mode_output_get_operation_mode (output, &mode, NULL);
|
|
|
|
if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSA) {
|
|
if (mode == QMI_LOC_OPERATION_MODE_MSA) {
|
|
mm_obj_dbg (self, "MSA A-GPS already enabled");
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "need to enable MSA A-GPS");
|
|
mode = QMI_LOC_OPERATION_MODE_MSA;
|
|
} else if (ctx->mode == GPS_OPERATION_MODE_AGPS_MSB) {
|
|
if (mode == QMI_LOC_OPERATION_MODE_MSB) {
|
|
mm_obj_dbg (self, "MSB A-GPS already enabled");
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "need to enable MSB A-GPS");
|
|
mode = QMI_LOC_OPERATION_MODE_MSB;
|
|
} else if (ctx->mode == GPS_OPERATION_MODE_STANDALONE) {
|
|
if (mode == QMI_LOC_OPERATION_MODE_STANDALONE) {
|
|
mm_obj_dbg (self, "A-GPS already disabled");
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
mm_obj_dbg (self, "need to disable A-GPS");
|
|
mode = QMI_LOC_OPERATION_MODE_STANDALONE;
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
if (ctx->timeout_id) {
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
}
|
|
|
|
if (ctx->indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
ctx->indication_id = 0;
|
|
}
|
|
|
|
input = qmi_message_loc_set_operation_mode_input_new ();
|
|
qmi_message_loc_set_operation_mode_input_set_operation_mode (input, mode, NULL);
|
|
qmi_client_loc_set_operation_mode (
|
|
QMI_CLIENT_LOC (ctx->client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)loc_set_operation_mode_ready,
|
|
task);
|
|
qmi_message_loc_set_operation_mode_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
loc_get_operation_mode_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
SetGpsOperationModeContext *ctx;
|
|
QmiMessageLocGetOperationModeOutput *output;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_loc_get_operation_mode_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_get_operation_mode_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_get_operation_mode_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"get-operation-mode",
|
|
G_CALLBACK (loc_location_get_operation_mode_indication_cb),
|
|
task);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)loc_location_operation_mode_indication_timed_out,
|
|
task);
|
|
|
|
qmi_message_loc_get_operation_mode_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
set_gps_operation_mode (MMSharedQmi *self,
|
|
GpsOperationMode mode,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SetGpsOperationModeContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_slice_new0 (SetGpsOperationModeContext);
|
|
ctx->mode = mode;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)set_gps_operation_mode_context_free);
|
|
|
|
/* Prefer PDS */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDS,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
ctx->client = g_object_ref (client);
|
|
qmi_client_pds_get_default_tracking_session (
|
|
QMI_CLIENT_PDS (ctx->client),
|
|
NULL,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)pds_get_default_tracking_session_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise LOC */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_LOC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (client) {
|
|
ctx->client = g_object_ref (client);
|
|
qmi_client_loc_get_operation_mode (
|
|
QMI_CLIENT_LOC (ctx->client),
|
|
NULL,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)loc_get_operation_mode_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Couldn't find any PDS/LOC client");
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: disable */
|
|
|
|
gboolean
|
|
mm_shared_qmi_disable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
stop_gps_engine_ready (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource source;
|
|
Private *priv;
|
|
GError *error = NULL;
|
|
|
|
if (!stop_gps_engine_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
priv = get_private (self);
|
|
|
|
priv->enabled_sources &= ~source;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
set_gps_operation_mode_standalone_ready (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource source;
|
|
Private *priv;
|
|
GError *error = NULL;
|
|
|
|
if (!set_gps_operation_mode_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
priv = get_private (self);
|
|
|
|
priv->enabled_sources &= ~source;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_disable_location_gathering (MMIfaceModemLocation *_self,
|
|
MMModemLocationSource source,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
GTask *task;
|
|
MMModemLocationSource tmp;
|
|
|
|
self = MM_SHARED_QMI (_self);
|
|
priv = get_private (self);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
|
|
|
|
/* NOTE: no parent disable_location_gathering() implementation */
|
|
|
|
if (!(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))) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_assert (!(priv->pds_client && priv->loc_client));
|
|
|
|
/* Disable A-GPS? */
|
|
if (source == MM_MODEM_LOCATION_SOURCE_AGPS_MSA || source == MM_MODEM_LOCATION_SOURCE_AGPS_MSB) {
|
|
set_gps_operation_mode (self,
|
|
GPS_OPERATION_MODE_STANDALONE,
|
|
(GAsyncReadyCallback)set_gps_operation_mode_standalone_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* If no more GPS sources enabled, stop GPS */
|
|
tmp = priv->enabled_sources;
|
|
tmp &= ~source;
|
|
if (!(tmp & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))) {
|
|
stop_gps_engine (self,
|
|
(GAsyncReadyCallback)stop_gps_engine_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Otherwise, we have more GPS sources enabled, we shouldn't stop GPS, just
|
|
* return */
|
|
priv->enabled_sources &= ~source;
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: enable */
|
|
|
|
gboolean
|
|
mm_shared_qmi_enable_location_gathering_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
start_gps_engine_ready (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource source;
|
|
Private *priv;
|
|
GError *error = NULL;
|
|
|
|
if (!start_gps_engine_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
priv = get_private (self);
|
|
|
|
priv->enabled_sources |= source;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
setup_required_nmea_traces_ready (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
/* don't treat this error as fatal */
|
|
if (!setup_required_nmea_traces_finish (self, res, &error))
|
|
mm_obj_warn (self, "couldn't setup required NMEA traces: %s", error->message);
|
|
|
|
start_gps_engine (self,
|
|
(GAsyncReadyCallback)start_gps_engine_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
set_gps_operation_mode_agps_ready (MMSharedQmi *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource source;
|
|
Private *priv;
|
|
GError *error = NULL;
|
|
|
|
if (!set_gps_operation_mode_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
priv = get_private (self);
|
|
|
|
priv->enabled_sources |= source;
|
|
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
parent_enable_location_gathering_ready (MMIfaceModemLocation *_self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self = MM_SHARED_QMI (_self);
|
|
Private *priv;
|
|
MMModemLocationSource source;
|
|
GError *error = NULL;
|
|
|
|
priv = get_private (self);
|
|
|
|
if (!priv->iface_modem_location_parent->enable_location_gathering_finish (_self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task));
|
|
|
|
/* We only consider GPS related sources in this shared QMI implementation */
|
|
if (!(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))) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Enabling MSA A-GPS? */
|
|
if (source == MM_MODEM_LOCATION_SOURCE_AGPS_MSA) {
|
|
set_gps_operation_mode (self,
|
|
GPS_OPERATION_MODE_AGPS_MSA,
|
|
(GAsyncReadyCallback)set_gps_operation_mode_agps_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Enabling MSB A-GPS? */
|
|
if (source == MM_MODEM_LOCATION_SOURCE_AGPS_MSB) {
|
|
set_gps_operation_mode (self,
|
|
GPS_OPERATION_MODE_AGPS_MSB,
|
|
(GAsyncReadyCallback)set_gps_operation_mode_agps_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* Only setup NMEA traces and start GPS engine if not done already */
|
|
if (!(priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))) {
|
|
setup_required_nmea_traces (self,
|
|
(GAsyncReadyCallback)setup_required_nmea_traces_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
/* GPS already started, we're done */
|
|
priv->enabled_sources |= source;
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_enable_location_gathering (MMIfaceModemLocation *self,
|
|
MMModemLocationSource source,
|
|
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 (source), NULL);
|
|
|
|
priv = get_private (MM_SHARED_QMI (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);
|
|
|
|
/* Chain up parent's gathering enable */
|
|
priv->iface_modem_location_parent->enable_location_gathering (
|
|
self,
|
|
source,
|
|
(GAsyncReadyCallback)parent_enable_location_gathering_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: load capabilities */
|
|
|
|
MMModemLocationSource
|
|
mm_shared_qmi_location_load_capabilities_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
gssize value;
|
|
|
|
value = g_task_propagate_int (G_TASK (res), &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return MM_MODEM_LOCATION_SOURCE_NONE;
|
|
}
|
|
return (MMModemLocationSource)value;
|
|
}
|
|
|
|
static void
|
|
parent_load_capabilities_ready (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMModemLocationSource sources;
|
|
GError *error = NULL;
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_QMI (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;
|
|
}
|
|
|
|
/* Now our own checks */
|
|
|
|
/* If we have support for the PDS or LOC client, GPS and A-GPS location is supported */
|
|
if ((mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDS, MM_PORT_QMI_FLAG_DEFAULT, NULL)) ||
|
|
(mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_LOC, MM_PORT_QMI_FLAG_DEFAULT, NULL)))
|
|
sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA |
|
|
MM_MODEM_LOCATION_SOURCE_GPS_RAW |
|
|
MM_MODEM_LOCATION_SOURCE_AGPS_MSA |
|
|
MM_MODEM_LOCATION_SOURCE_AGPS_MSB);
|
|
|
|
/* So we're done, complete */
|
|
g_task_return_int (task, sources);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
location_load_capabilities (MMIfaceModemLocation *self,
|
|
GTask *task)
|
|
{
|
|
Private *priv;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
priv->iface_modem_location_parent->load_capabilities (self,
|
|
(GAsyncReadyCallback)parent_load_capabilities_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
loc_unlock_location_engine_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMIfaceModemLocation *self;
|
|
QmiMessageLocSetEngineLockOutput *output;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_loc_set_engine_lock_finish (client, res, &error);
|
|
if (!output) {
|
|
mm_obj_warn (client, "QMI operation failed: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
if (!qmi_message_loc_set_engine_lock_output_get_result (output, &error)
|
|
&& !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_PERMISSION)) {
|
|
mm_obj_warn (client, "Couldn't unlock GPS engine: %s", error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
qmi_message_loc_set_engine_lock_output_unref (output);
|
|
|
|
self = g_task_get_source_object (task);
|
|
location_load_capabilities (self, task);
|
|
}
|
|
|
|
static void
|
|
unlock_location_engine (MMIfaceModemLocation *self,
|
|
GTask *task)
|
|
{
|
|
QmiClient *client;
|
|
QmiMessageLocSetEngineLockInput *input;
|
|
|
|
/* Prefer PDS, no need to unlock engine */
|
|
if (mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDS,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL))
|
|
goto out;
|
|
|
|
/* Otherwise LOC */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_LOC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (!client)
|
|
goto out;
|
|
|
|
input = qmi_message_loc_set_engine_lock_input_new ();
|
|
qmi_message_loc_set_engine_lock_input_set_lock_type (input, QMI_LOC_LOCK_TYPE_MT, NULL);
|
|
qmi_client_loc_set_engine_lock (QMI_CLIENT_LOC (client),
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) loc_unlock_location_engine_ready,
|
|
task);
|
|
qmi_message_loc_set_engine_lock_input_unref (input);
|
|
return;
|
|
out:
|
|
location_load_capabilities (self, task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_location_load_capabilities (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
Private *priv;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
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);
|
|
|
|
unlock_location_engine (self, task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: load supported assistance data */
|
|
|
|
gchar **
|
|
mm_shared_qmi_location_load_assistance_data_servers_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_location_load_assistance_data_servers (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
Private *priv;
|
|
GTask *task;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
g_task_return_pointer (task, g_strdupv (priv->loc_assistance_data_servers), (GDestroyNotify) g_strfreev);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: load supported assistance data */
|
|
|
|
typedef struct {
|
|
QmiClientLoc *client;
|
|
glong indication_id;
|
|
guint timeout_id;
|
|
} LoadSupportedAssistanceDataContext;
|
|
|
|
static void
|
|
load_supported_assistance_data_context_free (LoadSupportedAssistanceDataContext *ctx)
|
|
{
|
|
if (ctx->client) {
|
|
if (ctx->timeout_id)
|
|
g_source_remove (ctx->timeout_id);
|
|
if (ctx->indication_id)
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
g_object_unref (ctx->client);
|
|
}
|
|
g_slice_free (LoadSupportedAssistanceDataContext, ctx);
|
|
}
|
|
|
|
MMModemLocationAssistanceDataType
|
|
mm_shared_qmi_location_load_supported_assistance_data_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_ASSISTANCE_DATA_TYPE_NONE;
|
|
}
|
|
return (MMModemLocationAssistanceDataType)value;
|
|
}
|
|
|
|
static gboolean
|
|
loc_location_get_predicted_orbits_data_source_indication_timed_out (GTask *task)
|
|
{
|
|
LoadSupportedAssistanceDataContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Failed to receive indication with the predicted orbits data source");
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: Inject UTC time */
|
|
|
|
typedef struct {
|
|
guint timeout_id;
|
|
gulong indication_id;
|
|
QmiClientLoc *client;
|
|
} InjectTimeContext;
|
|
|
|
static void
|
|
inject_time_context_cleanup_action (InjectTimeContext *ctx)
|
|
{
|
|
if (ctx->timeout_id) {
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
}
|
|
if (ctx->indication_id) {
|
|
if (ctx->client)
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
ctx->indication_id = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
inject_time_context_free (InjectTimeContext *ctx)
|
|
{
|
|
inject_time_context_cleanup_action (ctx);
|
|
g_clear_object (&ctx->client);
|
|
g_slice_free (InjectTimeContext, ctx);
|
|
}
|
|
|
|
static void
|
|
inject_time_finish_task (GTask *_task,
|
|
GError *error)
|
|
{
|
|
InjectTimeContext *ctx;
|
|
g_autoptr(GTask) task = _task;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
inject_time_context_cleanup_action (ctx);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
loc_location_inject_time_indication_timed_out (GTask *task)
|
|
{
|
|
inject_time_finish_task (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation timed out"));
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
loc_location_inject_time_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocInjectUtcTimeOutput *output,
|
|
GTask *task)
|
|
{
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
|
|
if (!qmi_indication_loc_inject_utc_time_output_get_indication_status (output, &status, &error))
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
else
|
|
mm_error_from_qmi_loc_indication_status (status, &error);
|
|
inject_time_finish_task (task, error);
|
|
}
|
|
|
|
static void
|
|
loc_location_inject_time_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageLocInjectUtcTimeOutput) output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_loc_inject_utc_time_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
inject_time_finish_task (task, error);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_inject_utc_time_output_get_result (output, &error)) {
|
|
inject_time_finish_task (task, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
loc_location_inject_time_req_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocInjectTimeRequestOutput *output,
|
|
MMSharedQmi *self)
|
|
{
|
|
g_autoptr(QmiMessageLocInjectUtcTimeInput) input = NULL;
|
|
guint64 utc_ms = 0;
|
|
GTask *task;
|
|
InjectTimeContext *ctx;
|
|
|
|
task = g_task_new (self, NULL, NULL, NULL);
|
|
ctx = g_slice_new0 (InjectTimeContext);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)inject_time_context_free);
|
|
|
|
ctx->client = g_object_ref (client);
|
|
|
|
input = qmi_message_loc_inject_utc_time_input_new ();
|
|
|
|
utc_ms = g_get_real_time () / 1000;
|
|
qmi_message_loc_inject_utc_time_input_set_utc_time (input, utc_ms, NULL);
|
|
qmi_message_loc_inject_utc_time_input_set_time_uncertainty (input, 1000, NULL);
|
|
qmi_message_loc_inject_utc_time_input_set_time_source (input, QMI_LOC_INJECTED_TIME_SOURCE_UNKNOWN, NULL);
|
|
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc) loc_location_inject_time_indication_timed_out,
|
|
task);
|
|
|
|
ctx->indication_id = g_signal_connect (client,
|
|
"inject-utc-time",
|
|
G_CALLBACK (loc_location_inject_time_indication_cb),
|
|
task);
|
|
|
|
qmi_client_loc_inject_utc_time (
|
|
QMI_CLIENT_LOC (ctx->client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)loc_location_inject_time_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: Inject position */
|
|
|
|
typedef struct {
|
|
guint timeout_id;
|
|
gulong indication_id;
|
|
QmiClientLoc *client;
|
|
} InjectPositionContext;
|
|
|
|
static void
|
|
inject_position_context_cleanup_action (InjectPositionContext *ctx)
|
|
{
|
|
if (ctx->timeout_id) {
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
}
|
|
if (ctx->indication_id) {
|
|
if (ctx->client)
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
ctx->indication_id = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
inject_position_context_free (InjectPositionContext *ctx)
|
|
{
|
|
inject_position_context_cleanup_action (ctx);
|
|
g_clear_object (&ctx->client);
|
|
g_slice_free (InjectPositionContext, ctx);
|
|
}
|
|
|
|
static void
|
|
inject_position_finish_task (GTask *_task,
|
|
GError *error)
|
|
{
|
|
InjectPositionContext *ctx;
|
|
g_autoptr(GTask) task = _task;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
inject_position_context_cleanup_action (ctx);
|
|
if (error)
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
loc_location_inject_position_indication_timed_out (GTask *task)
|
|
{
|
|
inject_position_finish_task (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation timed out"));
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
loc_location_inject_position_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocInjectPositionOutput *output,
|
|
GTask *task)
|
|
{
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
|
|
if (!qmi_indication_loc_inject_position_output_get_indication_status (output, &status, &error))
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
else
|
|
mm_error_from_qmi_loc_indication_status (status, &error);
|
|
inject_position_finish_task (task, error);
|
|
}
|
|
|
|
static void
|
|
loc_location_inject_position_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageLocInjectPositionOutput) output = NULL;
|
|
GError *error = NULL;
|
|
|
|
output = qmi_client_loc_inject_position_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
inject_position_finish_task (task, error);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_inject_position_output_get_result (output, &error)) {
|
|
inject_position_finish_task (task, error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
loc_location_inject_position_req_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocInjectPositionRequestOutput *output,
|
|
MMSharedQmi *self)
|
|
{
|
|
g_autoptr(QmiMessageLocInjectPositionInput) input = NULL;
|
|
InjectPositionContext *ctx;
|
|
Private *priv;
|
|
GTask *task;
|
|
gdouble lat;
|
|
gdouble lon;
|
|
guint64 utc_ms = 0;
|
|
gdouble last_lat;
|
|
gdouble last_lon;
|
|
guint64 timestamp;
|
|
|
|
qmi_indication_loc_inject_position_request_output_get_latitude (output, &lat, NULL);
|
|
qmi_indication_loc_inject_position_request_output_get_longitude (output, &lon, NULL);
|
|
qmi_indication_loc_inject_position_request_output_get_utc_timestamp (output, ×tamp, NULL);
|
|
|
|
/* check whether modem already has position */
|
|
if ((lat != 0.0) && (lon != 0.0) && (timestamp != 0))
|
|
return;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
mm_location_cache_get_lat_lon (priv->location_cache, &last_lat, &last_lon);
|
|
if ((last_lat == MM_LOCATION_LATITUDE_UNKNOWN) ||
|
|
(last_lon == MM_LOCATION_LONGITUDE_UNKNOWN))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, NULL, NULL);
|
|
ctx = g_slice_new0 (InjectPositionContext);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)inject_position_context_free);
|
|
|
|
ctx->client = g_object_ref (client);
|
|
|
|
input = qmi_message_loc_inject_position_input_new ();
|
|
|
|
qmi_message_loc_inject_position_input_set_latitude (input, last_lat, NULL);
|
|
qmi_message_loc_inject_position_input_set_longitude (input, last_lon, NULL);
|
|
qmi_message_loc_inject_position_input_set_position_source (input, QMI_LOC_POSITION_SOURCE_OTHER, NULL);
|
|
|
|
// it's not recommended to advertise accuracy better than 1000 meters to the modem
|
|
qmi_message_loc_inject_position_input_set_horizontal_uncertainty_circular (input, 1000, NULL);
|
|
qmi_message_loc_inject_position_input_set_horizontal_confidence (input, 68, NULL);
|
|
|
|
utc_ms = g_get_real_time () / 1000;
|
|
qmi_message_loc_inject_position_input_set_utc_timestamp (input, utc_ms, NULL);
|
|
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc) loc_location_inject_position_indication_timed_out,
|
|
task);
|
|
|
|
ctx->indication_id = g_signal_connect (client,
|
|
"inject-position",
|
|
G_CALLBACK (loc_location_inject_position_indication_cb),
|
|
task);
|
|
|
|
qmi_client_loc_inject_position (
|
|
QMI_CLIENT_LOC (ctx->client),
|
|
input,
|
|
10,
|
|
NULL, /* cancellable */
|
|
(GAsyncReadyCallback)loc_location_inject_position_ready,
|
|
task);
|
|
}
|
|
|
|
|
|
static void
|
|
loc_register_event_inject_req_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(QmiMessageLocRegisterEventsOutput) output = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
|
|
output = qmi_client_loc_register_events_finish (client, res, &error);
|
|
if (!output) {
|
|
mm_obj_warn (client, "QMI operation failed: %s", error->message);
|
|
goto out;
|
|
}
|
|
|
|
if (!qmi_message_loc_register_events_output_get_result (output, &error)) {
|
|
mm_obj_warn (client, "Couldn't not register tracking events: %s", error->message);
|
|
goto out;
|
|
}
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
g_assert (!priv->loc_assistance_inject_time_req_indication_id);
|
|
priv->loc_assistance_inject_time_req_indication_id =
|
|
g_signal_connect (client,
|
|
"inject-time-request",
|
|
G_CALLBACK (loc_location_inject_time_req_indication_cb),
|
|
self);
|
|
|
|
g_assert (!priv->loc_assistance_inject_position_req_indication_id);
|
|
priv->loc_assistance_inject_position_req_indication_id =
|
|
g_signal_connect (client,
|
|
"inject-position-request",
|
|
G_CALLBACK (loc_location_inject_position_req_indication_cb),
|
|
self);
|
|
|
|
out:
|
|
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_XTRA);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
loc_location_get_predicted_orbits_data_source_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocGetPredictedOrbitsDataSourceOutput *output,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
GArray *server_list = NULL;
|
|
gboolean supported = FALSE;
|
|
|
|
if (!qmi_indication_loc_get_predicted_orbits_data_source_output_get_indication_status (output, &status, &error)) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
goto out;
|
|
}
|
|
|
|
if (!mm_error_from_qmi_loc_indication_status (status, &error))
|
|
goto out;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
if (qmi_indication_loc_get_predicted_orbits_data_source_output_get_server_list (
|
|
output,
|
|
&server_list,
|
|
NULL) &&
|
|
server_list->len > 0) {
|
|
guint i;
|
|
GPtrArray *tmp;
|
|
|
|
tmp = g_ptr_array_sized_new (server_list->len + 1);
|
|
for (i = 0; i < server_list->len; i++) {
|
|
const gchar *server;
|
|
|
|
server = g_array_index (server_list, gchar *, i);
|
|
g_ptr_array_add (tmp, g_strdup (server));
|
|
}
|
|
g_ptr_array_add (tmp, NULL);
|
|
|
|
|
|
g_strfreev (priv->loc_assistance_data_servers);
|
|
priv->loc_assistance_data_servers = (gchar **) g_ptr_array_free (tmp, FALSE);
|
|
|
|
supported = TRUE;
|
|
}
|
|
|
|
if (qmi_indication_loc_get_predicted_orbits_data_source_output_get_allowed_sizes (
|
|
output,
|
|
&priv->loc_assistance_data_max_file_size,
|
|
&priv->loc_assistance_data_max_part_size,
|
|
NULL) &&
|
|
priv->loc_assistance_data_max_file_size > 0 &&
|
|
priv->loc_assistance_data_max_part_size > 0) {
|
|
supported = TRUE;
|
|
}
|
|
|
|
out:
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
} else if (!supported) {
|
|
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_NONE);
|
|
g_object_unref (task);
|
|
} else {
|
|
g_autoptr(QmiMessageLocRegisterEventsInput) input = NULL;
|
|
guint64 event_mask;
|
|
|
|
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST |
|
|
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_POSITION_REQUEST;
|
|
input = qmi_message_loc_register_events_input_new ();
|
|
qmi_message_loc_register_events_input_set_event_registration_mask (
|
|
input, event_mask, NULL);
|
|
qmi_client_loc_register_events (client,
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) loc_register_event_inject_req_ready,
|
|
task);
|
|
}
|
|
}
|
|
|
|
static void
|
|
loc_location_get_predicted_orbits_data_source_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadSupportedAssistanceDataContext *ctx;
|
|
QmiMessageLocGetPredictedOrbitsDataSourceOutput *output;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_loc_get_predicted_orbits_data_source_finish (client, res, &error);
|
|
if (!output) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!qmi_message_loc_get_predicted_orbits_data_source_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
qmi_message_loc_get_predicted_orbits_data_source_output_unref (output);
|
|
return;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"get-predicted-orbits-data-source",
|
|
G_CALLBACK (loc_location_get_predicted_orbits_data_source_indication_cb),
|
|
task);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)loc_location_get_predicted_orbits_data_source_indication_timed_out,
|
|
task);
|
|
|
|
qmi_message_loc_get_predicted_orbits_data_source_output_unref (output);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_location_load_supported_assistance_data (MMIfaceModemLocation *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
LoadSupportedAssistanceDataContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
/* If no LOC client, no assistance data right away */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_LOC, MM_PORT_QMI_FLAG_DEFAULT, NULL);
|
|
if (!client) {
|
|
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_NONE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_slice_new0 (LoadSupportedAssistanceDataContext);
|
|
ctx->client = QMI_CLIENT_LOC (g_object_ref (client));
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)load_supported_assistance_data_context_free);
|
|
|
|
qmi_client_loc_get_predicted_orbits_data_source (ctx->client,
|
|
NULL,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)loc_location_get_predicted_orbits_data_source_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: inject assistance data */
|
|
|
|
#define MAX_BYTES_PER_REQUEST 1024
|
|
|
|
typedef struct {
|
|
QmiClientLoc *client;
|
|
guint8 *data;
|
|
goffset data_size;
|
|
gulong total_parts;
|
|
guint32 part_size;
|
|
glong indication_id;
|
|
guint timeout_id;
|
|
goffset i;
|
|
gulong n_part;
|
|
} InjectAssistanceDataContext;
|
|
|
|
static void
|
|
inject_assistance_data_context_free (InjectAssistanceDataContext *ctx)
|
|
{
|
|
if (ctx->client) {
|
|
if (ctx->timeout_id)
|
|
g_source_remove (ctx->timeout_id);
|
|
if (ctx->indication_id)
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
g_object_unref (ctx->client);
|
|
}
|
|
g_free (ctx->data);
|
|
g_slice_free (InjectAssistanceDataContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_location_inject_assistance_data_finish (MMIfaceModemLocation *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static gboolean
|
|
loc_location_inject_data_indication_timed_out (GTask *task)
|
|
{
|
|
InjectAssistanceDataContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Failed to receive indication with the server update result");
|
|
g_object_unref (task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void inject_xtra_data_next (GTask *task);
|
|
|
|
static void
|
|
loc_location_inject_xtra_data_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocInjectXtraDataOutput *output,
|
|
GTask *task)
|
|
{
|
|
InjectAssistanceDataContext *ctx;
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
|
|
if (!qmi_indication_loc_inject_xtra_data_output_get_indication_status (output, &status, &error)) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
goto out;
|
|
}
|
|
|
|
mm_error_from_qmi_loc_indication_status (status, &error);
|
|
|
|
out:
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
ctx->indication_id = 0;
|
|
|
|
inject_xtra_data_next (task);
|
|
}
|
|
|
|
static void
|
|
inject_xtra_data_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageLocInjectXtraDataOutput *output;
|
|
InjectAssistanceDataContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_loc_inject_xtra_data_finish (client, res, &error);
|
|
if (!output || !qmi_message_loc_inject_xtra_data_output_get_result (output, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
goto out;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"inject-xtra-data",
|
|
G_CALLBACK (loc_location_inject_xtra_data_indication_cb),
|
|
task);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)loc_location_inject_data_indication_timed_out,
|
|
task);
|
|
out:
|
|
if (output)
|
|
qmi_message_loc_inject_xtra_data_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
inject_xtra_data_next (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
QmiMessageLocInjectXtraDataInput *input;
|
|
InjectAssistanceDataContext *ctx;
|
|
goffset total_bytes_left;
|
|
gsize count;
|
|
GArray *data;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_assert (ctx->data_size >= ctx->i);
|
|
total_bytes_left = ctx->data_size - ctx->i;
|
|
if (total_bytes_left == 0) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->n_part++;
|
|
count = (total_bytes_left >= ctx->part_size) ? ctx->part_size : total_bytes_left;
|
|
|
|
input = qmi_message_loc_inject_xtra_data_input_new ();
|
|
qmi_message_loc_inject_xtra_data_input_set_total_size (
|
|
input,
|
|
(guint32)ctx->data_size,
|
|
NULL);
|
|
qmi_message_loc_inject_xtra_data_input_set_total_parts (
|
|
input,
|
|
(guint16)ctx->total_parts,
|
|
NULL);
|
|
qmi_message_loc_inject_xtra_data_input_set_part_number (
|
|
input,
|
|
(guint16)ctx->n_part,
|
|
NULL);
|
|
data = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), count), &(ctx->data[ctx->i]), count);
|
|
qmi_message_loc_inject_xtra_data_input_set_part_data (
|
|
input,
|
|
data,
|
|
NULL);
|
|
g_array_unref (data);
|
|
|
|
ctx->i += count;
|
|
|
|
mm_obj_dbg (self, "injecting xtra data: %" G_GSIZE_FORMAT " bytes (%u/%u)",
|
|
count, (guint) ctx->n_part, (guint) ctx->total_parts);
|
|
qmi_client_loc_inject_xtra_data (ctx->client,
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) inject_xtra_data_ready,
|
|
task);
|
|
|
|
qmi_message_loc_inject_xtra_data_input_unref (input);
|
|
}
|
|
|
|
static void
|
|
inject_xtra_data (GTask *task)
|
|
{
|
|
InjectAssistanceDataContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_assert (ctx->timeout_id == 0);
|
|
g_assert (ctx->indication_id == 0);
|
|
|
|
ctx->n_part = 0;
|
|
ctx->i = 0;
|
|
|
|
inject_xtra_data_next (task);
|
|
}
|
|
|
|
static void inject_assistance_data_next (GTask *task);
|
|
|
|
static void
|
|
loc_location_inject_predicted_orbits_data_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocInjectPredictedOrbitsDataOutput *output,
|
|
GTask *task)
|
|
{
|
|
InjectAssistanceDataContext *ctx;
|
|
QmiLocIndicationStatus status;
|
|
GError *error = NULL;
|
|
|
|
if (!qmi_indication_loc_inject_predicted_orbits_data_output_get_indication_status (output, &status, &error)) {
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
goto out;
|
|
}
|
|
|
|
mm_error_from_qmi_loc_indication_status (status, &error);
|
|
|
|
out:
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
|
|
ctx->indication_id = 0;
|
|
|
|
inject_assistance_data_next (task);
|
|
}
|
|
|
|
static void
|
|
inject_predicted_orbits_data_ready (QmiClientLoc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessageLocInjectPredictedOrbitsDataOutput *output;
|
|
InjectAssistanceDataContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_loc_inject_predicted_orbits_data_finish (client, res, &error);
|
|
if (!output || !qmi_message_loc_inject_predicted_orbits_data_output_get_result (output, &error)) {
|
|
/* Try with InjectXtra if InjectPredictedOrbits is unsupported */
|
|
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED)) {
|
|
g_error_free (error);
|
|
inject_xtra_data (task);
|
|
goto out;
|
|
}
|
|
g_prefix_error (&error, "QMI operation failed: ");
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
goto out;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx->indication_id = g_signal_connect (ctx->client,
|
|
"inject-predicted-orbits-data",
|
|
G_CALLBACK (loc_location_inject_predicted_orbits_data_indication_cb),
|
|
task);
|
|
ctx->timeout_id = g_timeout_add_seconds (10,
|
|
(GSourceFunc)loc_location_inject_data_indication_timed_out,
|
|
task);
|
|
out:
|
|
if (output)
|
|
qmi_message_loc_inject_predicted_orbits_data_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
inject_assistance_data_next (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
QmiMessageLocInjectPredictedOrbitsDataInput *input;
|
|
InjectAssistanceDataContext *ctx;
|
|
goffset total_bytes_left;
|
|
gsize count;
|
|
GArray *data;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_assert (ctx->data_size >= ctx->i);
|
|
total_bytes_left = ctx->data_size - ctx->i;
|
|
if (total_bytes_left == 0) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->n_part++;
|
|
count = (total_bytes_left >= ctx->part_size) ? ctx->part_size : total_bytes_left;
|
|
|
|
input = qmi_message_loc_inject_predicted_orbits_data_input_new ();
|
|
qmi_message_loc_inject_predicted_orbits_data_input_set_format_type (
|
|
input,
|
|
QMI_LOC_PREDICTED_ORBITS_DATA_FORMAT_XTRA,
|
|
NULL);
|
|
qmi_message_loc_inject_predicted_orbits_data_input_set_total_size (
|
|
input,
|
|
(guint32)ctx->data_size,
|
|
NULL);
|
|
qmi_message_loc_inject_predicted_orbits_data_input_set_total_parts (
|
|
input,
|
|
(guint16)ctx->total_parts,
|
|
NULL);
|
|
qmi_message_loc_inject_predicted_orbits_data_input_set_part_number (
|
|
input,
|
|
(guint16)ctx->n_part,
|
|
NULL);
|
|
data = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), count), &(ctx->data[ctx->i]), count);
|
|
qmi_message_loc_inject_predicted_orbits_data_input_set_part_data (
|
|
input,
|
|
data,
|
|
NULL);
|
|
g_array_unref (data);
|
|
|
|
ctx->i += count;
|
|
|
|
mm_obj_dbg (self, "injecting predicted orbits data: %" G_GSIZE_FORMAT " bytes (%u/%u)",
|
|
count, (guint) ctx->n_part, (guint) ctx->total_parts);
|
|
qmi_client_loc_inject_predicted_orbits_data (ctx->client,
|
|
input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) inject_predicted_orbits_data_ready,
|
|
task);
|
|
|
|
qmi_message_loc_inject_predicted_orbits_data_input_unref (input);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_location_inject_assistance_data (MMIfaceModemLocation *self,
|
|
const guint8 *data,
|
|
gsize data_size,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
InjectAssistanceDataContext *ctx;
|
|
QmiClient *client;
|
|
GTask *task;
|
|
Private *priv;
|
|
|
|
if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_LOC, &client,
|
|
callback, user_data))
|
|
return;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (InjectAssistanceDataContext);
|
|
ctx->client = QMI_CLIENT_LOC (g_object_ref (client));
|
|
ctx->data = g_memdup (data, data_size);
|
|
ctx->data_size = data_size;
|
|
ctx->part_size = ((priv->loc_assistance_data_max_part_size > 0) ? priv->loc_assistance_data_max_part_size : MAX_BYTES_PER_REQUEST);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) inject_assistance_data_context_free);
|
|
|
|
if ((ctx->data_size > (G_MAXUINT16 * ctx->part_size)) ||
|
|
((priv->loc_assistance_data_max_file_size > 0) && (ctx->data_size > priv->loc_assistance_data_max_file_size))) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_TOO_MANY,
|
|
"Assistance data file is too big");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
ctx->total_parts = (ctx->data_size / ctx->part_size);
|
|
if (ctx->data_size % ctx->part_size)
|
|
ctx->total_parts++;
|
|
g_assert (ctx->total_parts <= G_MAXUINT16);
|
|
|
|
mm_obj_dbg (self, "injecting gpsOneXTRA data (%" G_GOFFSET_FORMAT " bytes)...", ctx->data_size);
|
|
|
|
inject_assistance_data_next (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
QmiClient *
|
|
mm_shared_qmi_peek_client (MMSharedQmi *self,
|
|
QmiService service,
|
|
MMPortQmiFlag flag,
|
|
GError **error)
|
|
{
|
|
g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_client);
|
|
return MM_SHARED_QMI_GET_INTERFACE (self)->peek_client (self, service, flag, error);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_ensure_client (MMSharedQmi *self,
|
|
QmiService service,
|
|
QmiClient **o_client,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
QmiClient *client;
|
|
|
|
client = mm_shared_qmi_peek_client (self, service, MM_PORT_QMI_FLAG_DEFAULT, &error);
|
|
if (!client) {
|
|
g_task_report_error (self, callback, user_data, mm_shared_qmi_ensure_client, error);
|
|
return FALSE;
|
|
}
|
|
|
|
*o_client = client;
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
shared_qmi_init (gpointer g_iface)
|
|
{
|
|
}
|
|
|
|
GType
|
|
mm_shared_qmi_get_type (void)
|
|
{
|
|
static GType shared_qmi_type = 0;
|
|
|
|
if (!G_UNLIKELY (shared_qmi_type)) {
|
|
static const GTypeInfo info = {
|
|
sizeof (MMSharedQmi), /* class_size */
|
|
shared_qmi_init, /* base_init */
|
|
NULL, /* base_finalize */
|
|
};
|
|
|
|
shared_qmi_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedQmi", &info, 0);
|
|
g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM);
|
|
g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM_3GPP);
|
|
g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM_LOCATION);
|
|
}
|
|
|
|
return shared_qmi_type;
|
|
}
|