Files
ModemManager/src/mm-shared-qmi.c
T
Andrey Skvortsov 8b6116fe6c shared-qmi: handle inject position requests from the modem
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>
2026-01-10 02:04:10 +03:00

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,
&registration_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,
&lte_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, &current_config->total_size, &error) ||
!qmi_indication_pdc_get_config_info_output_get_version (output, &current_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, &timestamp, 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;
}