
==636642== Syscall param socketcall.sendto(msg) points to uninitialised byte(s) ==636642== at 0x5282BAC: send (in /usr/lib/libpthread-2.33.so) ==636642== by 0x4EF20AF: UnknownInlinedFun (gsocket.c:3426) ==636642== by 0x4EF20AF: g_socket_send_with_blocking (gsocket.c:3529) ==636642== by 0x4EDF176: g_output_stream_write (goutputstream.c:242) ==636642== by 0x4EDF2D4: g_output_stream_write_all (goutputstream.c:301) ==636642== by 0x4A5CDAC: endpoint_send (qmi-endpoint-qmux.c:409) ==636642== by 0x4A5B963: qmi_endpoint_send (qmi-endpoint.c:225) ==636642== by 0x4A55408: qmi_device_command_abortable (qmi-device.c:2897) ==636642== by 0x4A554C3: qmi_device_command_full (qmi-device.c:2923) ==636642== by 0x4B52664: qmi_client_nas_set_system_selection_preference (qmi-nas.c:61099) ==636642== by 0x1F6FDC: register_in_network_sssp (mm-shared-qmi.c:455) ==636642== by 0x1F71E6: mm_shared_qmi_3gpp_register_in_network (mm-shared-qmi.c:505) ==636642== by 0x19FCF0: mm_iface_modem_3gpp_register_in_network (mm-iface-modem-3gpp.c:484) ==636642== Address 0x880a3b3 is 19 bytes inside a block of size 32 alloc'd ==636642== at 0x484383F: realloc (vg_replace_malloc.c:1192) ==636642== by 0x50DC130: g_realloc (gmem.c:171) ==636642== by 0x50A067C: g_array_maybe_expand (garray.c:1009) ==636642== by 0x50A0A0C: UnknownInlinedFun (garray.c:520) ==636642== by 0x50A0A0C: g_array_append_vals (garray.c:509) ==636642== by 0x50A0A9E: g_byte_array_append (garray.c:2430) ==636642== by 0x4A4B70D: qmi_message_tlv_write_guint8 (qmi-message.c:686) ==636642== by 0x4B14CA8: __qmi_message_nas_set_system_selection_preference_request_create (qmi-nas.c:33477) ==636642== by 0x4B525B6: qmi_client_nas_set_system_selection_preference (qmi-nas.c:61087) ==636642== by 0x1F6FDC: register_in_network_sssp (mm-shared-qmi.c:455) ==636642== by 0x1F71E6: mm_shared_qmi_3gpp_register_in_network (mm-shared-qmi.c:505) ==636642== by 0x19FCF0: mm_iface_modem_3gpp_register_in_network (mm-iface-modem-3gpp.c:484) ==636642== by 0x1B0BD4: check_next_registration (mm-iface-modem-simple.c:154)
6937 lines
255 KiB
C
6937 lines
255 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details:
|
|
*
|
|
* Copyright (C) 2018 Aleksander Morgado <aleksander@aleksander.es>
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <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-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 */
|
|
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;
|
|
gboolean disable_4g_only_mode;
|
|
GArray *supported_bands;
|
|
|
|
/* Location helpers */
|
|
MMIfaceModemLocation *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;
|
|
|
|
/* Carrier config helpers */
|
|
gboolean config_active_default;
|
|
GArray *config_list;
|
|
gint config_active_i;
|
|
|
|
/* Slot status monitoring */
|
|
QmiClient *uim_client;
|
|
gulong uim_slot_status_indication_id;
|
|
GArray *slots_status;
|
|
gulong uim_refresh_indication_id;
|
|
guint uim_refresh_start_timeout_id;
|
|
} 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_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);
|
|
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)
|
|
{
|
|
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;
|
|
|
|
/* 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);
|
|
g_assert (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;
|
|
|
|
g_assert (!ctx->cancellable || ctx->cancellable_id);
|
|
g_cancellable_disconnect (ctx->cancellable, ctx->cancellable_id);
|
|
ctx->cancellable_id = 0;
|
|
|
|
/* the 3GPP interface will take care of checking if the registration is
|
|
* the one we asked for */
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
register_in_network_ready (GTask *task,
|
|
QmiIndicationNasServingSystemOutput *output)
|
|
{
|
|
RegisterInNetworkContext *ctx;
|
|
QmiNasRegistrationState registration_state;
|
|
|
|
/* ignore indication updates reporting "searching" */
|
|
qmi_indication_nas_serving_system_output_get_serving_system (
|
|
output,
|
|
®istration_state,
|
|
NULL, /* cs_attach_state */
|
|
NULL, /* ps_attach_state */
|
|
NULL, /* selected_network */
|
|
NULL, /* radio_interfaces */
|
|
NULL);
|
|
if (registration_state == QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
|
|
return;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
g_assert (ctx->client);
|
|
g_assert (ctx->serving_system_indication_id);
|
|
g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id);
|
|
ctx->serving_system_indication_id = 0;
|
|
|
|
g_assert (ctx->timeout_id);
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
g_assert (!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.
|
|
*/
|
|
|
|
if (ctx->cancellable)
|
|
ctx->cancellable_id = g_cancellable_connect (ctx->cancellable,
|
|
G_CALLBACK (register_in_network_cancelled),
|
|
task,
|
|
NULL);
|
|
|
|
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);
|
|
|
|
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;
|
|
MMQmiCapabilitiesContext 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);
|
|
priv->current_capabilities = mm_modem_capability_from_qmi_capabilities_context (&ctx->capabilities_context, self);
|
|
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 swiching
|
|
* 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;
|
|
MMModemCapability mask;
|
|
MMModemCapability single;
|
|
GArray *supported_combinations;
|
|
guint i;
|
|
|
|
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 */
|
|
mask = MM_MODEM_CAPABILITY_NONE;
|
|
for (i = 0; i < priv->supported_radio_interfaces->len; i++)
|
|
mask |= mm_modem_capability_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
|
|
|
|
supported_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), 3);
|
|
|
|
/* Add all possible supported capability combinations.
|
|
* In order to avoid unnecessary modem reboots, we will only implement capabilities
|
|
* switching only when switching GSM/UMTS+CDMA/EVDO multimode devices, and only if
|
|
* we have support for the commands doing it.
|
|
*/
|
|
if (priv->feature_nas_tp == FEATURE_SUPPORTED || priv->feature_nas_ssp == FEATURE_SUPPORTED) {
|
|
if (mask == (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)) {
|
|
/* Multimode GSM/UMTS+CDMA/EVDO device switched to GSM/UMTS only */
|
|
single = MM_MODEM_CAPABILITY_GSM_UMTS;
|
|
g_array_append_val (supported_combinations, single);
|
|
/* Multimode GSM/UMTS+CDMA/EVDO device switched to CDMA/EVDO only */
|
|
single = MM_MODEM_CAPABILITY_CDMA_EVDO;
|
|
g_array_append_val (supported_combinations, single);
|
|
} else if (mask == (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE)) {
|
|
/* Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to GSM/UMTS+LTE only */
|
|
single = MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE;
|
|
g_array_append_val (supported_combinations, single);
|
|
/* Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to CDMA/EVDO+LTE only */
|
|
single = MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE;
|
|
g_array_append_val (supported_combinations, single);
|
|
/*
|
|
* Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to LTE only.
|
|
*
|
|
* This case is required because we use the same methods and operations to
|
|
* switch capabilities and modes. For the LTE capability there is a direct
|
|
* related 4G mode, and so we cannot select a '4G only' mode in this device
|
|
* because we wouldn't be able to know the full list of current capabilities
|
|
* if the device was rebooted, as we would only see LTE capability. So,
|
|
* handle this special case so that the LTE/4G-only mode can exclusively be
|
|
* selected as capability switching in this kind of devices.
|
|
*/
|
|
priv->disable_4g_only_mode = TRUE;
|
|
single = MM_MODEM_CAPABILITY_LTE;
|
|
g_array_append_val (supported_combinations, single);
|
|
}
|
|
}
|
|
|
|
/* Add the full mask itself */
|
|
single = mask;
|
|
g_array_append_val (supported_combinations, single);
|
|
|
|
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;
|
|
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 void
|
|
get_technology_preference_ready (QmiClientNas *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
LoadCurrentModesResult *result = NULL;
|
|
QmiMessageNasGetTechnologyPreferenceOutput *output = NULL;
|
|
GError *error = NULL;
|
|
MMModemMode allowed;
|
|
QmiNasRadioTechnologyPreference preference_mask;
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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;
|
|
GArray *combinations;
|
|
MMModemModeCombination mode;
|
|
Private *priv;
|
|
MMModemMode mask_all;
|
|
guint i;
|
|
GArray *all;
|
|
GArray *filtered;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
g_assert (priv->supported_radio_interfaces);
|
|
|
|
/* Build all, based on the supported radio interfaces */
|
|
mask_all = MM_MODEM_MODE_NONE;
|
|
for (i = 0; i < priv->supported_radio_interfaces->len; i++)
|
|
mask_all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
|
|
mode.allowed = mask_all;
|
|
mode.preferred = MM_MODEM_MODE_NONE;
|
|
all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
|
|
g_array_append_val (all, mode);
|
|
|
|
/* If SSP and TP are not supported, ignore supported mode management */
|
|
if (priv->feature_nas_ssp == FEATURE_UNSUPPORTED && priv->feature_nas_tp == FEATURE_UNSUPPORTED) {
|
|
g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
|
|
|
|
#define ADD_MODE_PREFERENCE(MODE1, MODE2, MODE3, MODE4) do { \
|
|
mode.allowed = MODE1; \
|
|
if (MODE2 != MM_MODEM_MODE_NONE) { \
|
|
mode.allowed |= MODE2; \
|
|
if (MODE3 != MM_MODEM_MODE_NONE) { \
|
|
mode.allowed |= MODE3; \
|
|
if (MODE4 != MM_MODEM_MODE_NONE) \
|
|
mode.allowed |= MODE4; \
|
|
} \
|
|
if (priv->feature_nas_ssp != FEATURE_UNSUPPORTED) { \
|
|
if (MODE3 != MM_MODEM_MODE_NONE) { \
|
|
if (MODE4 != MM_MODEM_MODE_NONE) { \
|
|
mode.preferred = MODE4; \
|
|
g_array_append_val (combinations, mode); \
|
|
} \
|
|
mode.preferred = MODE3; \
|
|
g_array_append_val (combinations, mode); \
|
|
} \
|
|
mode.preferred = MODE2; \
|
|
g_array_append_val (combinations, mode); \
|
|
mode.preferred = MODE1; \
|
|
g_array_append_val (combinations, mode); \
|
|
} else { \
|
|
mode.preferred = MM_MODEM_MODE_NONE; \
|
|
g_array_append_val (combinations, mode); \
|
|
} \
|
|
} else { \
|
|
mode.allowed = MODE1; \
|
|
mode.preferred = MM_MODEM_MODE_NONE; \
|
|
g_array_append_val (combinations, mode); \
|
|
} \
|
|
} while (0)
|
|
|
|
/* 2G-only, 3G-only */
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
|
|
/* 4G-only mode is not possible in multimode GSM/UMTS+CDMA/EVDO+LTE
|
|
* devices. This configuration may be selected as "LTE only" capability
|
|
* instead. */
|
|
if (!priv->disable_4g_only_mode)
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
|
|
/* 2G, 3G, 4G combinations */
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE);
|
|
|
|
/* 5G related mode combinations are only supported when NAS SSP is supported,
|
|
* as there is no 5G support in NAS TP. */
|
|
if (priv->feature_nas_ssp != FEATURE_UNSUPPORTED) {
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G, MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_4G, MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_4G, MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE);
|
|
ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_4G, MM_MODEM_MODE_5G);
|
|
}
|
|
|
|
/* Filter out unsupported modes */
|
|
filtered = mm_filter_supported_modes (all, combinations, self);
|
|
g_array_unref (all);
|
|
g_array_unref (combinations);
|
|
|
|
g_task_return_pointer (task, filtered, (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;
|
|
|
|
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);
|
|
|
|
mm_bands = mm_modem_bands_from_qmi_band_capabilities (qmi_bands, qmi_lte_bands, extended_qmi_lte_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;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error);
|
|
if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) {
|
|
g_prefix_error (&error, "Couldn't get system selection preference: ");
|
|
goto out;
|
|
}
|
|
|
|
qmi_message_nas_get_system_selection_preference_output_get_band_preference (
|
|
output,
|
|
&band_preference_mask,
|
|
NULL);
|
|
|
|
qmi_message_nas_get_system_selection_preference_output_get_lte_band_preference (
|
|
output,
|
|
<e_band_preference_mask,
|
|
NULL);
|
|
|
|
if ((priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED) &&
|
|
qmi_message_nas_get_system_selection_preference_output_get_extended_lte_band_preference (
|
|
output,
|
|
&extended_lte_band_preference[0],
|
|
&extended_lte_band_preference[1],
|
|
&extended_lte_band_preference[2],
|
|
&extended_lte_band_preference[3],
|
|
NULL))
|
|
extended_lte_band_preference_size = G_N_ELEMENTS (extended_lte_band_preference);
|
|
|
|
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,
|
|
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 };
|
|
|
|
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),
|
|
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_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_info (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_info (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_info (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: {
|
|
QmiMessagePdcSetSelectedConfigInput *input;
|
|
ConfigInfo *requested_config;
|
|
ConfigInfo *active_config;
|
|
QmiConfigTypeAndId type_and_id;
|
|
|
|
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);
|
|
|
|
type_and_id.config_type = requested_config->config_type;;
|
|
type_and_id.id = requested_config->id;
|
|
|
|
input = qmi_message_pdc_set_selected_config_input_new ();
|
|
qmi_message_pdc_set_selected_config_input_set_type_with_id (input, &type_and_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);
|
|
qmi_message_pdc_set_selected_config_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
case SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT: {
|
|
QmiMessagePdcActivateConfigInput *input;
|
|
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);
|
|
qmi_message_pdc_activate_config_input_unref (input);
|
|
return;
|
|
}
|
|
|
|
case SETUP_CARRIER_CONFIG_STEP_LAST:
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_setup_carrier_config (MMIfaceModem *self,
|
|
const gchar *imsi,
|
|
const gchar *carrier_config_mapping,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SetupCarrierConfigContext *ctx;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
GError *error = NULL;
|
|
|
|
g_assert (imsi);
|
|
g_assert (carrier_config_mapping);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (SetupCarrierConfigContext);
|
|
ctx->step = SETUP_CARRIER_CONFIG_STEP_FIRST;
|
|
ctx->imsi = g_strdup (imsi);
|
|
ctx->keyfile = g_key_file_new ();
|
|
ctx->config_requested_i = -1;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)setup_carrier_config_context_free);
|
|
|
|
/* Load mapping keyfile */
|
|
if (!g_key_file_load_from_file (ctx->keyfile,
|
|
carrier_config_mapping,
|
|
G_KEY_FILE_NONE,
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Load PDC client */
|
|
client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
|
|
QMI_SERVICE_PDC,
|
|
MM_PORT_QMI_FLAG_DEFAULT,
|
|
NULL);
|
|
if (!client) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"QMI PDC not supported");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
ctx->client = QMI_CLIENT_PDC (g_object_ref (client));
|
|
|
|
setup_carrier_config_step (task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load carrier config (Modem interface) */
|
|
|
|
#define LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS 5
|
|
|
|
typedef enum {
|
|
LOAD_CARRIER_CONFIG_STEP_FIRST,
|
|
LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS,
|
|
LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT,
|
|
LOAD_CARRIER_CONFIG_STEP_LAST,
|
|
} LoadCarrierConfigStep;
|
|
|
|
typedef struct {
|
|
LoadCarrierConfigStep step;
|
|
|
|
QmiClientPdc *client;
|
|
|
|
GArray *config_list;
|
|
guint configs_loaded;
|
|
gboolean config_active_default;
|
|
gint config_active_i;
|
|
|
|
guint token;
|
|
guint timeout_id;
|
|
gulong list_configs_indication_id;
|
|
gulong get_selected_config_indication_id;
|
|
gulong get_config_info_indication_id;
|
|
} LoadCarrierConfigContext;
|
|
|
|
/* Allow to cleanup action load right away, without being tied
|
|
* to the lifecycle of the GTask */
|
|
static void
|
|
load_carrier_config_context_cleanup_action (LoadCarrierConfigContext *ctx)
|
|
{
|
|
if (ctx->get_selected_config_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->get_selected_config_indication_id);
|
|
ctx->get_selected_config_indication_id = 0;
|
|
}
|
|
if (ctx->get_config_info_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->get_config_info_indication_id);
|
|
ctx->get_config_info_indication_id = 0;
|
|
}
|
|
if (ctx->list_configs_indication_id) {
|
|
g_signal_handler_disconnect (ctx->client, ctx->list_configs_indication_id);
|
|
ctx->list_configs_indication_id = 0;
|
|
}
|
|
if (ctx->timeout_id) {
|
|
g_source_remove (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
load_carrier_config_context_free (LoadCarrierConfigContext *ctx)
|
|
{
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
|
|
if (ctx->config_list)
|
|
g_array_unref (ctx->config_list);
|
|
g_clear_object (&ctx->client);
|
|
g_slice_free (LoadCarrierConfigContext, ctx);
|
|
}
|
|
|
|
gboolean
|
|
mm_shared_qmi_load_carrier_config_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
gchar **carrier_config_name,
|
|
gchar **carrier_config_revision,
|
|
GError **error)
|
|
{
|
|
Private *priv;
|
|
|
|
if (!g_task_propagate_boolean (G_TASK (res), error))
|
|
return FALSE;
|
|
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
g_assert (priv->config_active_i >= 0 || priv->config_active_default);
|
|
|
|
if (priv->config_active_i >= 0) {
|
|
ConfigInfo *config;
|
|
|
|
config = &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i);
|
|
*carrier_config_name = g_strdup (config->description);
|
|
*carrier_config_revision = g_strdup_printf ("%08X", config->version);
|
|
} else if (priv->config_active_default) {
|
|
*carrier_config_name = g_strdup (DEFAULT_CONFIG_DESCRIPTION);
|
|
*carrier_config_revision = NULL;
|
|
} else
|
|
g_assert_not_reached ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void load_carrier_config_step (GTask *task);
|
|
|
|
static void
|
|
load_carrier_config_abort (GTask *task,
|
|
GError *error)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static gboolean
|
|
load_carrier_config_timeout (GTask *task)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
g_assert (ctx->timeout_id);
|
|
ctx->timeout_id = 0;
|
|
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
|
|
"Operation timed out"));
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
get_selected_config_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcGetSelectedConfigOutput *output,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
LoadCarrierConfigContext *ctx;
|
|
GArray *active_id = NULL;
|
|
GError *error = NULL;
|
|
guint16 error_code = 0;
|
|
guint i;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_get_selected_config_output_get_indication_result (output, &error_code, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0 &&
|
|
error_code != QMI_PROTOCOL_ERROR_NOT_PROVISIONED) { /* No configs active */
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't get selected config: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
qmi_indication_pdc_get_selected_config_output_get_active_id (output, &active_id, NULL);
|
|
if (!active_id) {
|
|
mm_obj_dbg (self, "no carrier config currently selected (default in use)");
|
|
ctx->config_active_default = TRUE;
|
|
goto next;
|
|
}
|
|
|
|
g_assert (ctx->config_list);
|
|
g_assert (ctx->config_list->len);
|
|
|
|
for (i = 0; i < ctx->config_list->len; i++) {
|
|
ConfigInfo *config;
|
|
|
|
config = &g_array_index (ctx->config_list, ConfigInfo, i);
|
|
if ((config->id->len == active_id->len) &&
|
|
!memcmp (config->id->data, active_id->data, active_id->len)) {
|
|
ctx->config_active_i = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ctx->config_list->len) {
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't find currently selected config"));
|
|
return;
|
|
}
|
|
|
|
next:
|
|
|
|
/* Go on */
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
ctx->step++;
|
|
load_carrier_config_step (task);
|
|
}
|
|
|
|
static void
|
|
get_selected_config_ready (QmiClientPdc *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
QmiMessagePdcGetSelectedConfigOutput *output;
|
|
LoadCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
output = qmi_client_pdc_get_selected_config_finish (client, res, &error);
|
|
if (!output || !qmi_message_pdc_get_selected_config_output_get_result (output, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
goto out;
|
|
}
|
|
|
|
ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS,
|
|
(GSourceFunc) load_carrier_config_timeout,
|
|
task);
|
|
ctx->get_selected_config_indication_id = g_signal_connect (ctx->client,
|
|
"get-selected-config",
|
|
G_CALLBACK (get_selected_config_indication),
|
|
task);
|
|
|
|
out:
|
|
if (output)
|
|
qmi_message_pdc_get_selected_config_output_unref (output);
|
|
}
|
|
|
|
static void
|
|
get_config_info_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcGetConfigInfoOutput *output,
|
|
GTask *task)
|
|
{
|
|
LoadCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
ConfigInfo *current_config = NULL;
|
|
guint32 token;
|
|
const gchar *description;
|
|
guint i;
|
|
guint16 error_code = 0;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_get_config_info_output_get_indication_result (output, &error_code, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0) {
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't get config info: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
if (!qmi_indication_pdc_get_config_info_output_get_token (output, &token, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
/* Look for the current config in the list, match by token */
|
|
for (i = 0; i < ctx->config_list->len; i++) {
|
|
current_config = &g_array_index (ctx->config_list, ConfigInfo, i);
|
|
if (current_config->token == token)
|
|
break;
|
|
}
|
|
|
|
/* Ignore if not found in the list */
|
|
if (i == ctx->config_list->len)
|
|
return;
|
|
|
|
/* Ignore if already set */
|
|
if (current_config->description)
|
|
return;
|
|
|
|
/* Store total size, version and description of the current config */
|
|
if (!qmi_indication_pdc_get_config_info_output_get_total_size (output, ¤t_config->total_size, &error) ||
|
|
!qmi_indication_pdc_get_config_info_output_get_version (output, ¤t_config->version, &error) ||
|
|
!qmi_indication_pdc_get_config_info_output_get_description (output, &description, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
current_config->description = g_strdup (description);
|
|
ctx->configs_loaded++;
|
|
|
|
/* If not all loaded, wait for more */
|
|
if (ctx->configs_loaded < ctx->config_list->len)
|
|
return;
|
|
|
|
/* Go on */
|
|
load_carrier_config_context_cleanup_action (ctx);
|
|
ctx->step++;
|
|
load_carrier_config_step (task);
|
|
}
|
|
|
|
static void
|
|
list_configs_indication (QmiClientPdc *client,
|
|
QmiIndicationPdcListConfigsOutput *output,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
LoadCarrierConfigContext *ctx;
|
|
GError *error = NULL;
|
|
GArray *configs = NULL;
|
|
guint i;
|
|
guint16 error_code = 0;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
if (!qmi_indication_pdc_list_configs_output_get_indication_result (output, &error_code, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
if (error_code != 0) {
|
|
load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"couldn't list configs: %s",
|
|
qmi_protocol_error_get_string ((QmiProtocolError) error_code)));
|
|
return;
|
|
}
|
|
|
|
if (!qmi_indication_pdc_list_configs_output_get_configs (output, &configs, &error)) {
|
|
load_carrier_config_abort (task, error);
|
|
return;
|
|
}
|
|
|
|
/* If no configs are installed, the module is running with the default one */
|
|
if (!configs || !configs->len) {
|
|
ctx->config_active_default = TRUE;
|
|
ctx->step = LOAD_CARRIER_CONFIG_STEP_LAST;
|
|
load_carrier_config_step (task);
|
|
return;
|
|
}
|
|
|
|
/* Preallocate config list and request details for each */
|
|
mm_obj_dbg (self, "found %u carrier configurations...", configs->len);
|
|
ctx->config_list = g_array_sized_new (FALSE, TRUE, sizeof (ConfigInfo), configs->len);
|
|
g_array_set_size (ctx->config_list, configs->len);
|
|
g_array_set_clear_func (ctx->config_list, (GDestroyNotify) config_info_clear);
|
|
|
|
ctx->get_config_info_indication_id = g_signal_connect (ctx->client,
|
|
"get-config-info",
|
|
G_CALLBACK (get_config_info_indication),
|
|
task);
|
|
|
|
for (i = 0; i < configs->len; i++) {
|
|
ConfigInfo *current_info;
|
|
QmiIndicationPdcListConfigsOutputConfigsElement *element;
|
|
QmiConfigTypeAndId type_with_id;
|
|
QmiMessagePdcGetConfigInfoInput *input;
|
|
|
|
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 ();
|
|
type_with_id.config_type = element->config_type;
|
|
type_with_id.id = current_info->id;
|
|
qmi_message_pdc_get_config_info_input_set_type_with_id (input, &type_with_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! */
|
|
qmi_message_pdc_get_config_info_input_unref (input);
|
|
}
|
|
}
|
|
|
|
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;
|
|
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)) {
|
|
g_task_return_error (task, 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_information (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) {
|
|
GArray *slot_eid;
|
|
|
|
slot_eid = g_array_index (slot_eids, GArray *, i);
|
|
if (slot_eid->len)
|
|
eid = mm_qmi_uim_decode_eid (slot_eid->data, slot_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_info (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;
|
|
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)) {
|
|
g_task_return_error (task, 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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SIM hot swap detection */
|
|
|
|
#define REFRESH_START_TIMEOUT_SECS 3
|
|
|
|
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_complete (QmiClientUim *client,
|
|
QmiUimSessionType session_type)
|
|
{
|
|
g_autoptr(QmiMessageUimRefreshCompleteInput) refresh_complete_input = NULL;
|
|
GArray *dummy_aid;
|
|
|
|
dummy_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,
|
|
dummy_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 (dummy_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), 0, NULL, 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 only UICC Reset type refresh, which can be used
|
|
* in profile 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) {
|
|
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) {
|
|
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), 0, NULL, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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 (!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_base_modem_process_sim_event (MM_BASE_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);
|
|
}
|
|
|
|
static void
|
|
uim_refresh_register_iccid_change_ready (QmiClientUim *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
g_autoptr(QmiMessageUimRefreshRegisterOutput) output = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (self);
|
|
|
|
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, "refresh registration using 'refresh register' failed: %s", error->message);
|
|
g_clear_object (&priv->uim_client);
|
|
g_task_return_new_error (task, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED,
|
|
"SIM hot swap detection not supported by modem");
|
|
} else {
|
|
mm_obj_dbg (self, "registered for SIM refresh events using 'refresh register'");
|
|
priv->uim_refresh_indication_id =
|
|
g_signal_connect (client,
|
|
"refresh",
|
|
G_CALLBACK (uim_refresh_indication_cb),
|
|
self);
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
g_object_unref (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 */
|
|
static void
|
|
uim_refresh_register_iccid_change (GTask *task)
|
|
{
|
|
MMSharedQmi *self;
|
|
Private *priv;
|
|
QmiMessageUimRefreshRegisterInputInfoFilesElement file_element;
|
|
guint8 val;
|
|
g_autoptr(QmiMessageUimRefreshRegisterInput) refresh_register_input = NULL;
|
|
g_autoptr(GArray) dummy_aid = NULL;
|
|
g_autoptr(GArray) file = NULL;
|
|
g_autoptr(GArray) file_element_path = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
mm_obj_dbg (self, "register for refresh file indication");
|
|
|
|
dummy_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), 2);
|
|
val = 0x00;
|
|
g_array_append_val (file_element_path, val);
|
|
val = 0x3F;
|
|
g_array_append_val (file_element_path, val);
|
|
|
|
|
|
memset (&file_element, 0, sizeof (file_element));
|
|
file_element.file_id = 0x2FE2; /* ICCID */
|
|
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,
|
|
dummy_aid,
|
|
NULL);
|
|
|
|
qmi_client_uim_refresh_register (QMI_CLIENT_UIM (priv->uim_client),
|
|
refresh_register_input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_refresh_register_iccid_change_ready,
|
|
task);
|
|
}
|
|
|
|
/* Refresh registration and event handling.
|
|
* This is used only as fallback in case slot status indications do not work
|
|
* in the particular modem (determined by UIM Get Slot Status failing) for
|
|
* detecting ICCID changing due to a profile switch.
|
|
*
|
|
* We assume that devices not supporting UIM Get Slot Status only have a
|
|
* single slot, for which we register refresh events.
|
|
*/
|
|
|
|
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;
|
|
Private *priv;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
output = qmi_client_uim_refresh_register_all_finish (client, res, &error);
|
|
if (!output || !qmi_message_uim_refresh_register_all_output_get_result (output, &error)) {
|
|
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED) ||
|
|
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND)) {
|
|
/* As last resort, if 'refresh register all' fails, try a plain 'refresh register'.
|
|
* Some older modems may not support 'refresh register all'. */
|
|
uim_refresh_register_iccid_change (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "refresh register all operation failed: %s", error->message);
|
|
g_clear_object (&priv->uim_client);
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
} else {
|
|
mm_obj_dbg (self, "registered for all SIM refresh events");
|
|
priv->uim_refresh_indication_id =
|
|
g_signal_connect (client,
|
|
"refresh",
|
|
G_CALLBACK (uim_refresh_indication_cb),
|
|
self);
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
uim_slot_status_not_supported (GTask *task)
|
|
{
|
|
MMIfaceModem *self;
|
|
Private *priv;
|
|
g_autoptr(QmiMessageUimRefreshRegisterAllInput) refresh_register_all_input = NULL;
|
|
g_autoptr(GArray) dummy_aid = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
g_assert (!priv->uim_refresh_indication_id);
|
|
|
|
mm_obj_dbg (self, "slot status not supported by modem: register for refresh indications");
|
|
|
|
dummy_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_CARD_SLOT_1,
|
|
dummy_aid,
|
|
NULL);
|
|
|
|
qmi_client_uim_refresh_register_all (QMI_CLIENT_UIM (priv->uim_client),
|
|
refresh_register_all_input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_refresh_register_all_ready,
|
|
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;
|
|
Private *priv;
|
|
|
|
self = g_task_get_source_object (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)) {
|
|
if (priv->uim_slot_status_indication_id) {
|
|
g_signal_handler_disconnect (client, priv->uim_slot_status_indication_id);
|
|
priv->uim_slot_status_indication_id = 0;
|
|
}
|
|
|
|
if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED) ||
|
|
g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND)) {
|
|
uim_slot_status_not_supported (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "slot status retrieval failed: %s", error->message);
|
|
g_clear_object (&priv->uim_client);
|
|
g_task_return_error (task, g_steal_pointer (&error));
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "slot status retrieval succeeded: monitoring slot status indications");
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (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;
|
|
Private *priv;
|
|
|
|
self = g_task_get_source_object (task);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
/* 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)) {
|
|
g_assert (!priv->uim_slot_status_indication_id);
|
|
priv->uim_slot_status_indication_id = g_signal_connect (priv->uim_client,
|
|
"slot-status",
|
|
G_CALLBACK (uim_slot_status_indication_cb),
|
|
self);
|
|
mm_obj_dbg (self, "registered for slot status indications");
|
|
|
|
/* 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 (client,
|
|
NULL,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_check_get_slot_status_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
mm_obj_dbg (self, "not registered for slot status indications: %s", error->message);
|
|
uim_slot_status_not_supported (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_setup_sim_hot_swap (MMIfaceModem *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(QmiMessageUimRegisterEventsInput) register_events_input = NULL;
|
|
GTask *task;
|
|
QmiClient *client = NULL;
|
|
Private *priv;
|
|
|
|
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);
|
|
priv = get_private (MM_SHARED_QMI (self));
|
|
|
|
g_assert (!priv->uim_slot_status_indication_id);
|
|
g_assert (!priv->uim_client);
|
|
priv->uim_client = g_object_ref (client);
|
|
|
|
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 (priv->uim_client),
|
|
register_events_input,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback) uim_register_events_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* FCC unlock (Modem interface) */
|
|
|
|
gboolean
|
|
mm_shared_qmi_fcc_unlock_finish (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
dms_set_fcc_authentication_ready (QmiClientDms *client,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
GError *error = NULL;
|
|
g_autoptr(QmiMessageDmsSetFccAuthenticationOutput) output = NULL;
|
|
|
|
output = qmi_client_dms_set_fcc_authentication_finish (client, res, &error);
|
|
if (!output || !qmi_message_dms_set_fcc_authentication_output_get_result (output, &error))
|
|
g_task_return_error (task, error);
|
|
else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
void
|
|
mm_shared_qmi_fcc_unlock (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_set_fcc_authentication (QMI_CLIENT_DMS (client),
|
|
NULL,
|
|
5,
|
|
NULL,
|
|
(GAsyncReadyCallback)dms_set_fcc_authentication_ready,
|
|
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_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;
|
|
guint16 port;
|
|
GArray *url;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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 (mm_parse_supl_address (ctx->supl, NULL, &ip, &port, NULL))
|
|
qmi_message_pds_set_agps_config_input_set_location_server_address (input, 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;
|
|
guint16 port;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
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 (mm_parse_supl_address (ctx->supl, NULL, &ip, &port, NULL))
|
|
qmi_message_loc_set_server_input_set_ipv4 (input, 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 = 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_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 = 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_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_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;
|
|
}
|
|
|
|
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;
|
|
|
|
priv = get_private (self);
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
loc_location_nmea_indication_cb (QmiClientLoc *client,
|
|
QmiIndicationLocNmeaOutput *output,
|
|
MMSharedQmi *self)
|
|
{
|
|
const gchar *nmea = NULL;
|
|
|
|
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);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Location: internal helper: setup minimum required NMEA traces */
|
|
|
|
typedef struct {
|
|
QmiClientLoc *client;
|
|
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;
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx = g_task_get_task_data (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_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);
|
|
|
|
/* If the configured NMEA types already include GGA, GSV and GSA, we're fine. For raw
|
|
* GPS sources GGA is the only required one, the other two are given for completeness */
|
|
if ((nmea_types_mask & desired_nmea_types_mask) == desired_nmea_types_mask) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
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,
|
|
NULL,
|
|
(GAsyncReadyCallback)loc_set_nmea_types_ready,
|
|
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)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* The task ownership is shared between signal and timeout; the one which is
|
|
* scheduled first will cancel the other. */
|
|
ctx = g_task_get_task_data (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);
|
|
}
|
|
|
|
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);
|
|
|
|
qmi_client_loc_get_nmea_types (ctx->client,
|
|
NULL,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)loc_get_nmea_types_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: 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;
|
|
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 ();
|
|
qmi_message_loc_register_events_input_set_event_registration_mask (
|
|
input, QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA, 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);
|
|
}
|
|
|
|
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);
|
|
|
|
priv->iface_modem_location_parent->load_capabilities (self,
|
|
(GAsyncReadyCallback)parent_load_capabilities_ready,
|
|
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;
|
|
}
|
|
|
|
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_assert (!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);
|
|
else if (!supported)
|
|
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_NONE);
|
|
else
|
|
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_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_info (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_info (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;
|
|
}
|