/* -*- 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 * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. */ #include #include #include #include #include #define _LIBMM_INSIDE_MM #include #include #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 */ gboolean multimode; MMModemCapability current_capabilities; GArray *supported_radio_interfaces; Feature feature_nas_tp; Feature feature_nas_ssp; Feature feature_nas_ssp_extended_lte_band_preference; Feature feature_nas_ssp_acquisition_order_preference; GArray *feature_nas_ssp_acquisition_order_preference_array; GArray *supported_bands; /* Location helpers */ 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 */ GArray *slots_status; QmiClient *uim_client; gulong uim_slot_status_indication_id; 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); if (ctx->cancellable_id) ctx->cancellable_id = 0; g_assert (ctx->timeout_id); g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; g_assert (ctx->client); g_assert (ctx->serving_system_indication_id); g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id); ctx->serving_system_indication_id = 0; g_task_return_error_if_cancelled (task); g_object_unref (task); } static gboolean register_in_network_timeout (GTask *task) { RegisterInNetworkContext *ctx; ctx = g_task_get_task_data (task); g_assert (ctx->timeout_id); ctx->timeout_id = 0; g_assert (ctx->client); g_assert (ctx->serving_system_indication_id); g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id); ctx->serving_system_indication_id = 0; if (ctx->cancellable && ctx->cancellable_id) { g_cancellable_disconnect (ctx->cancellable, ctx->cancellable_id); ctx->cancellable_id = 0; } /* the 3GPP interface will take care of checking if the registration is * the one we asked for */ g_task_return_boolean (task, TRUE); g_object_unref (task); return G_SOURCE_REMOVE; } static void register_in_network_ready (GTask *task, QmiIndicationNasServingSystemOutput *output) { RegisterInNetworkContext *ctx; QmiNasRegistrationState registration_state; /* ignore indication updates reporting "searching" */ qmi_indication_nas_serving_system_output_get_serving_system ( output, ®istration_state, NULL, /* cs_attach_state */ NULL, /* ps_attach_state */ NULL, /* selected_network */ NULL, /* radio_interfaces */ NULL); if (registration_state == QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) return; ctx = g_task_get_task_data (task); g_assert (ctx->client); g_assert (ctx->serving_system_indication_id); g_signal_handler_disconnect (ctx->client, ctx->serving_system_indication_id); ctx->serving_system_indication_id = 0; g_assert (ctx->timeout_id); g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; if (ctx->cancellable && ctx->cancellable_id) { g_cancellable_disconnect (ctx->cancellable, ctx->cancellable_id); ctx->cancellable_id = 0; } /* the 3GPP interface will take care of checking if the registration is * the one we asked for */ g_task_return_boolean (task, TRUE); g_object_unref (task); } static void initiate_network_register_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { GError *error = NULL; QmiMessageNasInitiateNetworkRegisterOutput *output; RegisterInNetworkContext *ctx; ctx = g_task_get_task_data (task); output = qmi_client_nas_initiate_network_register_finish (client, res, &error); if (!output || !qmi_message_nas_initiate_network_register_output_get_result (output, &error)) { /* No effect would mean we're already in the desired network */ if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { g_task_return_boolean (task, TRUE); g_error_free (error); } else { g_prefix_error (&error, "Couldn't initiate network register: "); g_task_return_error (task, error); } g_object_unref (task); goto out; } /* Registration attempt started, now we need to monitor "serving system" indications * to get notified when the registration changed. Note that we won't need to process * the indication, because we already have that logic setup (and it runs before this * new signal handler), we just need to get notified of when it happens. We will also * setup a maximum operation timeuot plus a cancellability point, as this operation * may be explicitly cancelled by the 3GPP interface if a new registration request * arrives while the current one is being processed. * * Task is shared among cancellable, indication and timeout. The first one triggered * will cancel the others. */ ctx->serving_system_indication_id = g_signal_connect_swapped (client, "serving-system", G_CALLBACK (register_in_network_ready), task); ctx->timeout_id = g_timeout_add_seconds (REGISTER_IN_NETWORK_TIMEOUT_SECS, (GSourceFunc) register_in_network_timeout, task); /* The cancellable may already be cancelled, and if so the given callback will be called * right away. So make sure this cancellable is always configured last, so that it clears the * timeout or signal handler upon early cancellation. */ if (ctx->cancellable) ctx->cancellable_id = g_cancellable_connect (ctx->cancellable, G_CALLBACK (register_in_network_cancelled), task, NULL); out: if (output) qmi_message_nas_initiate_network_register_output_unref (output); } static void register_in_network_inr (GTask *task, QmiClient *client, GCancellable *cancellable, guint16 mcc, guint16 mnc, gboolean mnc_pcs_digit) { QmiMessageNasInitiateNetworkRegisterInput *input; input = qmi_message_nas_initiate_network_register_input_new (); if (mcc) { /* If the user sent a specific network to use, lock it in. */ qmi_message_nas_initiate_network_register_input_set_action ( input, QMI_NAS_NETWORK_REGISTER_TYPE_MANUAL, NULL); qmi_message_nas_initiate_network_register_input_set_manual_registration_info_3gpp ( input, mcc, mnc, QMI_NAS_RADIO_INTERFACE_UNKNOWN, /* don't change radio interface */ NULL); if (mnc_pcs_digit && mnc < 100) qmi_message_nas_initiate_network_register_input_set_mnc_pcs_digit_include_status ( input, mnc_pcs_digit, NULL ); } else { /* Otherwise, automatic registration */ qmi_message_nas_initiate_network_register_input_set_action ( input, QMI_NAS_NETWORK_REGISTER_TYPE_AUTOMATIC, NULL); } qmi_client_nas_initiate_network_register ( QMI_CLIENT_NAS (client), input, 120, cancellable, (GAsyncReadyCallback)initiate_network_register_ready, task); qmi_message_nas_initiate_network_register_input_unref (input); } static void set_system_selection_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { GError *error = NULL; QmiMessageNasSetSystemSelectionPreferenceOutput *output; output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) { if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { g_prefix_error (&error, "Couldn't set network selection preference: "); g_task_return_error (task, error); goto out; } g_error_free (error); } g_task_return_boolean (task, TRUE); out: g_object_unref (task); if (output) qmi_message_nas_set_system_selection_preference_output_unref (output); } static void register_in_network_sssp (GTask *task, QmiClient *client, GCancellable *cancellable, guint16 mcc, guint16 mnc, gboolean mnc_pcs_digit) { QmiMessageNasSetSystemSelectionPreferenceInput *input; input = qmi_message_nas_set_system_selection_preference_input_new (); qmi_message_nas_set_system_selection_preference_input_set_network_selection_preference ( input, mcc ? QMI_NAS_NETWORK_SELECTION_PREFERENCE_MANUAL : QMI_NAS_NETWORK_SELECTION_PREFERENCE_AUTOMATIC, mcc, mnc, NULL); if (mnc_pcs_digit && mnc < 100) qmi_message_nas_set_system_selection_preference_input_set_mnc_pcs_digit_include_status ( input, mnc_pcs_digit, NULL ); qmi_client_nas_set_system_selection_preference ( QMI_CLIENT_NAS (client), input, 120, cancellable, (GAsyncReadyCallback)set_system_selection_preference_ready, task); qmi_message_nas_set_system_selection_preference_input_unref (input); } void mm_shared_qmi_3gpp_register_in_network (MMIfaceModem3gpp *self, const gchar *operator_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; RegisterInNetworkContext *ctx; guint16 mcc = 0; guint16 mnc = 0; gboolean mnc_pcs_digit = FALSE; QmiClient *client = NULL; GError *error = NULL; Private *priv = NULL; /* Get NAS client */ if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_NAS, &client, callback, user_data)) return; task = g_task_new (self, cancellable, callback, user_data); ctx = g_slice_new0 (RegisterInNetworkContext); ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL; g_task_set_task_data (task, ctx, (GDestroyNotify)register_in_network_context_free); /* Parse input MCC/MNC */ if (operator_id && !mm_3gpp_parse_operator_id (operator_id, &mcc, &mnc, &mnc_pcs_digit, &error)) { g_assert (error != NULL); g_task_return_error (task, error); g_object_unref (task); return; } priv = get_private (MM_SHARED_QMI (self)); if (priv->feature_nas_ssp == FEATURE_SUPPORTED) register_in_network_sssp (task, client, cancellable, mcc, mnc, mnc_pcs_digit); else register_in_network_inr (task, client, cancellable, mcc, mnc, mnc_pcs_digit); } /*****************************************************************************/ /* Current capabilities setting (Modem interface) */ typedef enum { SET_CURRENT_CAPABILITIES_STEP_FIRST, SET_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE, SET_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE, SET_CURRENT_CAPABILITIES_STEP_RESET, SET_CURRENT_CAPABILITIES_STEP_LAST, } SetCurrentCapabilitiesStep; typedef struct { QmiClientNas *client; MMModemCapability capabilities; gboolean capabilities_updated; SetCurrentCapabilitiesStep step; } SetCurrentCapabilitiesContext; static void set_current_capabilities_context_free (SetCurrentCapabilitiesContext *ctx) { g_object_unref (ctx->client); g_slice_free (SetCurrentCapabilitiesContext, ctx); } gboolean mm_shared_qmi_set_current_capabilities_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void set_current_capabilities_step (GTask *task); static void set_current_capabilities_reset_ready (MMIfaceModem *self, GAsyncResult *res, GTask *task) { SetCurrentCapabilitiesContext *ctx; GError *error = NULL; ctx = g_task_get_task_data (task); if (!mm_shared_qmi_reset_finish (self, res, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } ctx->step++; set_current_capabilities_step (task); } static void set_current_capabilities_set_technology_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { SetCurrentCapabilitiesContext *ctx; QmiMessageNasSetTechnologyPreferenceOutput *output = NULL; GError *error = NULL; ctx = g_task_get_task_data (task); output = qmi_client_nas_set_technology_preference_finish (client, res, &error); if (!output || !qmi_message_nas_set_technology_preference_output_get_result (output, &error)) { /* A no-effect error here is not a real error */ if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { g_task_return_error (task, error); g_object_unref (task); goto out; } /* no effect, just end operation without reset */ g_clear_error (&error); ctx->step = SET_CURRENT_CAPABILITIES_STEP_LAST; set_current_capabilities_step (task); goto out; } /* success! */ ctx->step = SET_CURRENT_CAPABILITIES_STEP_RESET; set_current_capabilities_step (task); out: if (output) qmi_message_nas_set_technology_preference_output_unref (output); } static void set_current_capabilities_technology_preference (GTask *task) { SetCurrentCapabilitiesContext *ctx; QmiMessageNasSetTechnologyPreferenceInput *input; QmiNasRadioTechnologyPreference pref; ctx = g_task_get_task_data (task); pref = mm_modem_capability_to_qmi_radio_technology_preference (ctx->capabilities); if (!pref) { gchar *str; str = mm_modem_capability_build_string_from_mask (ctx->capabilities); g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unhandled capabilities setting: '%s'", str); g_object_unref (task); g_free (str); return; } input = qmi_message_nas_set_technology_preference_input_new (); qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL); qmi_client_nas_set_technology_preference ( ctx->client, input, 5, NULL, (GAsyncReadyCallback)set_current_capabilities_set_technology_preference_ready, task); qmi_message_nas_set_technology_preference_input_unref (input); } static void set_current_capabilities_set_system_selection_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { SetCurrentCapabilitiesContext *ctx; QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; GError *error = NULL; ctx = g_task_get_task_data (task); output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) { g_task_return_error (task, error); g_object_unref (task); goto out; } /* success! */ ctx->step = SET_CURRENT_CAPABILITIES_STEP_RESET; set_current_capabilities_step (task); out: if (output) qmi_message_nas_set_system_selection_preference_output_unref (output); } static void set_current_capabilities_system_selection_preference (GTask *task) { SetCurrentCapabilitiesContext *ctx; QmiMessageNasSetSystemSelectionPreferenceInput *input; QmiNasRatModePreference pref; ctx = g_task_get_task_data (task); pref = mm_modem_capability_to_qmi_rat_mode_preference (ctx->capabilities); if (!pref) { gchar *str; str = mm_modem_capability_build_string_from_mask (ctx->capabilities); g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unhandled capabilities setting: '%s'", str); g_object_unref (task); g_free (str); return; } input = qmi_message_nas_set_system_selection_preference_input_new (); qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL); qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL); qmi_client_nas_set_system_selection_preference ( ctx->client, input, 5, NULL, (GAsyncReadyCallback)set_current_capabilities_set_system_selection_preference_ready, task); qmi_message_nas_set_system_selection_preference_input_unref (input); } static void set_current_capabilities_step (GTask *task) { MMSharedQmi *self; Private *priv; SetCurrentCapabilitiesContext *ctx; self = g_task_get_source_object (task); priv = get_private (MM_SHARED_QMI (self)); ctx = g_task_get_task_data (task); switch (ctx->step) { case SET_CURRENT_CAPABILITIES_STEP_FIRST: /* Error out early if both unsupported */ if ((priv->feature_nas_ssp != FEATURE_SUPPORTED) && (priv->feature_nas_tp != FEATURE_SUPPORTED)) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Setting capabilities is not supported by this device"); g_object_unref (task); return; } ctx->step++; /* fall-through */ case SET_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE: if (priv->feature_nas_ssp == FEATURE_SUPPORTED) { set_current_capabilities_system_selection_preference (task); return; } ctx->step++; /* fall-through */ case SET_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE: if (priv->feature_nas_tp == FEATURE_SUPPORTED) { set_current_capabilities_technology_preference (task); return; } ctx->step++; /* fall-through */ case SET_CURRENT_CAPABILITIES_STEP_RESET: mm_shared_qmi_reset (MM_IFACE_MODEM (self), (GAsyncReadyCallback)set_current_capabilities_reset_ready, task); return; case SET_CURRENT_CAPABILITIES_STEP_LAST: g_task_return_boolean (task, TRUE); g_object_unref (task); return; default: g_assert_not_reached (); } } void mm_shared_qmi_set_current_capabilities (MMIfaceModem *self, MMModemCapability capabilities, GAsyncReadyCallback callback, gpointer user_data) { Private *priv; SetCurrentCapabilitiesContext *ctx; GTask *task; QmiClient *client = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_NAS, &client, callback, user_data)) return; priv = get_private (MM_SHARED_QMI (self)); g_assert (priv->feature_nas_tp != FEATURE_UNKNOWN); g_assert (priv->feature_nas_ssp != FEATURE_UNKNOWN); ctx = g_slice_new0 (SetCurrentCapabilitiesContext); ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); ctx->capabilities = capabilities; ctx->step = SET_CURRENT_CAPABILITIES_STEP_FIRST; task = g_task_new (self, NULL, callback, user_data); g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_capabilities_context_free); set_current_capabilities_step (task); } /*****************************************************************************/ /* Current capabilities (Modem interface) */ typedef enum { LOAD_CURRENT_CAPABILITIES_STEP_FIRST, LOAD_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE, LOAD_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE, LOAD_CURRENT_CAPABILITIES_STEP_DMS_GET_CAPABILITIES, LOAD_CURRENT_CAPABILITIES_STEP_LAST, } LoadCurrentCapabilitiesStep; typedef struct { QmiClientNas *nas_client; QmiClientDms *dms_client; LoadCurrentCapabilitiesStep step; MMQmiCurrentCapabilitiesContext capabilities_context; } LoadCurrentCapabilitiesContext; MMModemCapability mm_shared_qmi_load_current_capabilities_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { GError *inner_error = NULL; gssize value; value = g_task_propagate_int (G_TASK (res), &inner_error); if (inner_error) { g_propagate_error (error, inner_error); return MM_MODEM_CAPABILITY_NONE; } return (MMModemCapability)value; } static void load_current_capabilities_context_free (LoadCurrentCapabilitiesContext *ctx) { g_object_unref (ctx->nas_client); g_object_unref (ctx->dms_client); g_slice_free (LoadCurrentCapabilitiesContext, ctx); } static void load_current_capabilities_step (GTask *task); static void load_current_capabilities_get_capabilities_ready (QmiClientDms *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; Private *priv; LoadCurrentCapabilitiesContext *ctx; QmiMessageDmsGetCapabilitiesOutput *output = NULL; GError *error = NULL; guint i; GArray *radio_interface_list; self = g_task_get_source_object (task); priv = get_private (self); ctx = g_task_get_task_data (task); output = qmi_client_dms_get_capabilities_finish (client, res, &error); if (!output) { g_prefix_error (&error, "QMI operation failed: "); goto out; } if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) { g_prefix_error (&error, "Couldn't get Capabilities: "); goto out; } qmi_message_dms_get_capabilities_output_get_info ( output, NULL, /* info_max_tx_channel_rate */ NULL, /* info_max_rx_channel_rate */ NULL, /* info_data_service_capability */ NULL, /* info_sim_capability */ &radio_interface_list, NULL); /* Cache supported radio interfaces */ g_assert (!priv->supported_radio_interfaces); priv->supported_radio_interfaces = g_array_ref (radio_interface_list); for (i = 0; i < radio_interface_list->len; i++) ctx->capabilities_context.dms_capabilities |= mm_modem_capability_from_qmi_radio_interface (g_array_index (radio_interface_list, QmiDmsRadioInterface, i), self); out: if (output) qmi_message_dms_get_capabilities_output_unref (output); /* Failure in DMS Get Capabilities is fatal */ if (error) { g_task_return_error (task, error); g_object_unref (task); return; } ctx->step++; load_current_capabilities_step (task); } static void load_current_capabilities_get_technology_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; Private *priv; LoadCurrentCapabilitiesContext *ctx; QmiMessageNasGetTechnologyPreferenceOutput *output = NULL; GError *error = NULL; self = g_task_get_source_object (task); priv = get_private (MM_SHARED_QMI (self)); ctx = g_task_get_task_data (task); output = qmi_client_nas_get_technology_preference_finish (client, res, &error); if (!output) { mm_obj_dbg (self, "QMI operation failed: %s", error->message); g_error_free (error); priv->feature_nas_tp = FEATURE_UNSUPPORTED; } else if (!qmi_message_nas_get_technology_preference_output_get_result (output, &error)) { mm_obj_dbg (self, "couldn't get technology preference: %s", error->message); g_error_free (error); priv->feature_nas_tp = FEATURE_UNSUPPORTED; } else { qmi_message_nas_get_technology_preference_output_get_active ( output, &ctx->capabilities_context.nas_tp_mask, NULL, /* duration */ NULL); priv->feature_nas_tp = FEATURE_SUPPORTED; } if (output) qmi_message_nas_get_technology_preference_output_unref (output); ctx->step++; load_current_capabilities_step (task); } static void load_current_capabilities_get_system_selection_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; Private *priv; LoadCurrentCapabilitiesContext *ctx; QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; GError *error = NULL; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); priv = get_private (MM_SHARED_QMI (self)); priv->feature_nas_ssp = FEATURE_UNSUPPORTED; priv->feature_nas_ssp_extended_lte_band_preference = FEATURE_UNSUPPORTED; priv->feature_nas_ssp_acquisition_order_preference = FEATURE_UNSUPPORTED; output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); if (!output) { mm_obj_dbg (self, "QMI operation failed: %s", error->message); g_error_free (error); } else if (!qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) { mm_obj_dbg (self, "couldn't get system selection preference: %s", error->message); g_error_free (error); } else { GArray *acquisition_order_preference_array = NULL; /* SSP is supported, perform feature checks */ priv->feature_nas_ssp = FEATURE_SUPPORTED; if (qmi_message_nas_get_system_selection_preference_output_get_extended_lte_band_preference (output, NULL, NULL, NULL, NULL, NULL)) priv->feature_nas_ssp_extended_lte_band_preference = FEATURE_SUPPORTED; if (qmi_message_nas_get_system_selection_preference_output_get_acquisition_order_preference (output, &acquisition_order_preference_array, NULL) && acquisition_order_preference_array && acquisition_order_preference_array->len) { priv->feature_nas_ssp_acquisition_order_preference = FEATURE_SUPPORTED; priv->feature_nas_ssp_acquisition_order_preference_array = g_array_ref (acquisition_order_preference_array); } qmi_message_nas_get_system_selection_preference_output_get_mode_preference ( output, &ctx->capabilities_context.nas_ssp_mode_preference_mask, NULL); } if (output) qmi_message_nas_get_system_selection_preference_output_unref (output); ctx->step++; load_current_capabilities_step (task); } static void load_current_capabilities_step (GTask *task) { MMSharedQmi *self; Private *priv; LoadCurrentCapabilitiesContext *ctx; self = g_task_get_source_object (task); priv = get_private (MM_SHARED_QMI (self)); ctx = g_task_get_task_data (task); switch (ctx->step) { case LOAD_CURRENT_CAPABILITIES_STEP_FIRST: ctx->step++; /* fall-through */ case LOAD_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE: qmi_client_nas_get_system_selection_preference ( ctx->nas_client, NULL, 5, NULL, (GAsyncReadyCallback)load_current_capabilities_get_system_selection_preference_ready, task); return; case LOAD_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE: qmi_client_nas_get_technology_preference ( ctx->nas_client, NULL, 5, NULL, (GAsyncReadyCallback)load_current_capabilities_get_technology_preference_ready, task); return; case LOAD_CURRENT_CAPABILITIES_STEP_DMS_GET_CAPABILITIES: qmi_client_dms_get_capabilities ( ctx->dms_client, NULL, 5, NULL, (GAsyncReadyCallback)load_current_capabilities_get_capabilities_ready, task); return; case LOAD_CURRENT_CAPABILITIES_STEP_LAST: g_assert (priv->feature_nas_tp != FEATURE_UNKNOWN); g_assert (priv->feature_nas_ssp != FEATURE_UNKNOWN); /* At this point we can already know if this is a multimode device or not */ if ((ctx->capabilities_context.dms_capabilities & MM_MODEM_CAPABILITY_MULTIMODE) == MM_MODEM_CAPABILITY_MULTIMODE) priv->multimode = ctx->capabilities_context.multimode = TRUE; priv->current_capabilities = mm_current_capability_from_qmi_current_capabilities_context (&ctx->capabilities_context, self); if (priv->current_capabilities == MM_MODEM_CAPABILITY_NONE) g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "modem has no current capabilities"); else g_task_return_int (task, priv->current_capabilities); g_object_unref (task); return; default: g_assert_not_reached (); } } void mm_shared_qmi_load_current_capabilities (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { LoadCurrentCapabilitiesContext *ctx; GTask *task; QmiClient *nas_client = NULL; QmiClient *dms_client = NULL; Private *priv; /* * We assume that DMS Get Capabilities reports always the same result, * that will include all capabilities supported by the device regardless * of which ones are configured at the moment. E.g. for the Load Supported * Capabilities we base the logic exclusively on this method's output. * * We then consider 3 different cases: * a) If the device supports NAS System Selection Preference, we use the * "mode preference" TLV to select currently enabled capabilities. * b) If the device supports NAS Technology Preference (older devices), * we use this method to select currently enabled capabilities. * c) If none of those messages is supported we don't allow 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; GArray *supported_combinations; guint i; MMQmiSupportedCapabilitiesContext ctx = { 0 }; task = g_task_new (self, NULL, callback, user_data); /* List of radio interfaces preloaded in current capabilities */ priv = get_private (MM_SHARED_QMI (self)); if (!priv->supported_radio_interfaces) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "cannot load current capabilities without radio interface information"); g_object_unref (task); return; } /* Build mask with all supported capabilities */ ctx.dms_capabilities = MM_MODEM_CAPABILITY_NONE; for (i = 0; i < priv->supported_radio_interfaces->len; i++) ctx.dms_capabilities |= mm_modem_capability_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self); ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED); ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED); ctx.multimode = priv->multimode; /* Build list of supported combinations */ supported_combinations = mm_supported_capabilities_from_qmi_supported_capabilities_context (&ctx, self); g_task_return_pointer (task, supported_combinations, (GDestroyNotify) g_array_unref); g_object_unref (task); } /*****************************************************************************/ /* Load model (Modem interface) */ gchar * mm_shared_qmi_load_model_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_pointer (G_TASK (res), error); } static void dms_get_model_ready (QmiClientDms *client, GAsyncResult *res, GTask *task) { QmiMessageDmsGetModelOutput *output = NULL; GError *error = NULL; output = qmi_client_dms_get_model_finish (client, res, &error); if (!output) { g_prefix_error (&error, "QMI operation failed: "); g_task_return_error (task, error); } else if (!qmi_message_dms_get_model_output_get_result (output, &error)) { g_prefix_error (&error, "Couldn't get Model: "); g_task_return_error (task, error); } else { const gchar *str; qmi_message_dms_get_model_output_get_model (output, &str, NULL); g_task_return_pointer (task, g_strdup (str), g_free); } if (output) qmi_message_dms_get_model_output_unref (output); g_object_unref (task); } void mm_shared_qmi_load_model (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { QmiClient *client = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_DMS, &client, callback, user_data)) return; mm_obj_dbg (self, "loading model..."); qmi_client_dms_get_model (QMI_CLIENT_DMS (client), NULL, 5, NULL, (GAsyncReadyCallback)dms_get_model_ready, g_task_new (self, NULL, callback, user_data)); } /*****************************************************************************/ /* Allowed modes setting (Modem interface) */ typedef struct { QmiClientNas *client; MMModemMode allowed; MMModemMode preferred; } SetCurrentModesContext; static void set_current_modes_context_free (SetCurrentModesContext *ctx) { g_object_unref (ctx->client); g_slice_free (SetCurrentModesContext, ctx); } gboolean mm_shared_qmi_set_current_modes_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void set_current_modes_technology_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { QmiMessageNasSetTechnologyPreferenceOutput *output = NULL; GError *error = NULL; output = qmi_client_nas_set_technology_preference_finish (client, res, &error); if (!output || (!qmi_message_nas_set_technology_preference_output_get_result (output, &error) && !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT))) { g_task_return_error (task, error); } else { g_clear_error (&error); g_task_return_boolean (task, TRUE); } g_object_unref (task); if (output) qmi_message_nas_set_technology_preference_output_unref (output); } static void set_current_modes_technology_preference (GTask *task) { MMIfaceModem *self; SetCurrentModesContext *ctx; QmiMessageNasSetTechnologyPreferenceInput *input; QmiNasRadioTechnologyPreference pref; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); if (ctx->preferred != MM_MODEM_MODE_NONE) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot set specific preferred mode"); g_object_unref (task); return; } pref = mm_modem_mode_to_qmi_radio_technology_preference (ctx->allowed, mm_iface_modem_is_cdma (self)); if (!pref) { gchar *str; str = mm_modem_mode_build_string_from_mask (ctx->allowed); g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unhandled allowed mode setting: '%s'", str); g_object_unref (task); g_free (str); return; } input = qmi_message_nas_set_technology_preference_input_new (); qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL); qmi_client_nas_set_technology_preference ( ctx->client, input, 5, NULL, /* cancellable */ (GAsyncReadyCallback)set_current_modes_technology_preference_ready, task); qmi_message_nas_set_technology_preference_input_unref (input); } static void set_current_modes_system_selection_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; GError *error = NULL; output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); if (output) qmi_message_nas_set_system_selection_preference_output_unref (output); } static void set_current_modes_system_selection_preference (GTask *task) { MMIfaceModem *self; Private *priv; SetCurrentModesContext *ctx; QmiMessageNasSetSystemSelectionPreferenceInput *input; QmiNasRatModePreference pref; self = g_task_get_source_object (task); priv = get_private (MM_SHARED_QMI (self)); ctx = g_task_get_task_data (task); input = qmi_message_nas_set_system_selection_preference_input_new (); qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL); /* Preferred modes */ if (ctx->preferred != MM_MODEM_MODE_NONE) { if (priv->feature_nas_ssp_acquisition_order_preference == FEATURE_SUPPORTED) { GArray *array; /* Acquisition order array */ array = mm_modem_mode_to_qmi_acquisition_order_preference (ctx->allowed, ctx->preferred, priv->feature_nas_ssp_acquisition_order_preference_array); g_assert (array); qmi_message_nas_set_system_selection_preference_input_set_acquisition_order_preference (input, array, NULL); g_array_unref (array); } /* Only set GSM/WCDMA acquisition order preference if both 2G and 3G given as allowed */ if (mm_iface_modem_is_3gpp (self) && ((ctx->allowed & (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G))) { QmiNasGsmWcdmaAcquisitionOrderPreference order; order = mm_modem_mode_to_qmi_gsm_wcdma_acquisition_order_preference (ctx->preferred, self); qmi_message_nas_set_system_selection_preference_input_set_gsm_wcdma_acquisition_order_preference (input, order, NULL); } } /* Allowed modes */ pref = mm_modem_mode_to_qmi_rat_mode_preference (ctx->allowed, mm_iface_modem_is_cdma (self), mm_iface_modem_is_3gpp (self)); qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL); qmi_client_nas_set_system_selection_preference ( ctx->client, input, 5, NULL, /* cancellable */ (GAsyncReadyCallback)set_current_modes_system_selection_preference_ready, task); qmi_message_nas_set_system_selection_preference_input_unref (input); } void mm_shared_qmi_set_current_modes (MMIfaceModem *self, MMModemMode allowed, MMModemMode preferred, GAsyncReadyCallback callback, gpointer user_data) { SetCurrentModesContext *ctx; GTask *task; QmiClient *client = NULL; Private *priv; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_NAS, &client, callback, user_data)) return; ctx = g_slice_new0 (SetCurrentModesContext); ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); if (allowed == MM_MODEM_MODE_ANY && ctx->preferred == MM_MODEM_MODE_NONE) { ctx->allowed = MM_MODEM_MODE_NONE; if (mm_iface_modem_is_2g (self)) ctx->allowed |= MM_MODEM_MODE_2G; if (mm_iface_modem_is_3g (self)) ctx->allowed |= MM_MODEM_MODE_3G; if (mm_iface_modem_is_4g (self)) ctx->allowed |= MM_MODEM_MODE_4G; if (mm_iface_modem_is_5g (self)) ctx->allowed |= MM_MODEM_MODE_5G; ctx->preferred = MM_MODEM_MODE_NONE; } else { ctx->allowed = allowed; ctx->preferred = preferred; } task = g_task_new (self, NULL, callback, user_data); g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_modes_context_free); priv = get_private (MM_SHARED_QMI (self)); if (priv->feature_nas_ssp == FEATURE_SUPPORTED) { set_current_modes_system_selection_preference (task); return; } if (priv->feature_nas_tp == FEATURE_SUPPORTED) { set_current_modes_technology_preference (task); return; } g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Setting allowed modes is not supported by this device"); g_object_unref (task); } /*****************************************************************************/ /* Load current modes (Modem interface) */ typedef struct { QmiClientNas *client; } LoadCurrentModesContext; typedef struct { MMModemMode allowed; MMModemMode preferred; } LoadCurrentModesResult; static void load_current_modes_context_free (LoadCurrentModesContext *ctx) { g_object_unref (ctx->client); g_free (ctx); } gboolean mm_shared_qmi_load_current_modes_finish (MMIfaceModem *self, GAsyncResult *res, MMModemMode *allowed, MMModemMode *preferred, GError **error) { LoadCurrentModesResult *result; result = g_task_propagate_pointer (G_TASK (res), error); if (!result) return FALSE; *allowed = result->allowed; *preferred = result->preferred; g_free (result); return TRUE; } static MMModemMode filter_modes_by_supported_radio_interfaces (MMSharedQmi *self, GArray *supported_radio_interfaces, MMModemMode allowed_modes) { MMModemMode modem_modes; guint i; modem_modes = MM_MODEM_MODE_NONE; for (i = 0; i < supported_radio_interfaces->len; i++) modem_modes |= mm_modem_mode_from_qmi_radio_interface (g_array_index (supported_radio_interfaces, QmiDmsRadioInterface, i), self); return allowed_modes & modem_modes; } static void get_technology_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; Private *priv; LoadCurrentModesResult *result = NULL; QmiMessageNasGetTechnologyPreferenceOutput *output = NULL; GError *error = NULL; MMModemMode allowed; QmiNasRadioTechnologyPreference preference_mask; self = g_task_get_source_object (task); priv = get_private (self); output = qmi_client_nas_get_technology_preference_finish (client, res, &error); if (!output || !qmi_message_nas_get_technology_preference_output_get_result (output, &error)) { g_task_return_error (task, error); goto out; } qmi_message_nas_get_technology_preference_output_get_active ( output, &preference_mask, NULL, /* duration */ NULL); allowed = mm_modem_mode_from_qmi_radio_technology_preference (preference_mask); if (allowed == MM_MODEM_MODE_NONE) { gchar *str; str = qmi_nas_radio_technology_preference_build_string_from_mask (preference_mask); g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported modes reported: '%s'", str); g_free (str); goto out; } g_assert (priv->supported_radio_interfaces); allowed = filter_modes_by_supported_radio_interfaces (self, priv->supported_radio_interfaces, allowed); /* We got a valid value from here */ result = g_new (LoadCurrentModesResult, 1); result->allowed = allowed; result->preferred = MM_MODEM_MODE_NONE; g_task_return_pointer (task, result, g_free); out: if (output) qmi_message_nas_get_technology_preference_output_unref (output); g_object_unref (task); } static void load_current_modes_technology_preference (GTask *task) { LoadCurrentModesContext *ctx; ctx = g_task_get_task_data (task); qmi_client_nas_get_technology_preference ( ctx->client, NULL, /* no input */ 5, NULL, /* cancellable */ (GAsyncReadyCallback)get_technology_preference_ready, task); } static void load_current_modes_system_selection_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; Private *priv; LoadCurrentModesResult *result = NULL; QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; GError *error = NULL; QmiNasRatModePreference mode_preference_mask = 0; MMModemMode allowed; self = g_task_get_source_object (task); priv = get_private (self); output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) { g_task_return_error (task, error); goto out; } if (!qmi_message_nas_get_system_selection_preference_output_get_mode_preference ( output, &mode_preference_mask, NULL)) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Mode preference not reported in system selection preference"); goto out; } allowed = mm_modem_mode_from_qmi_rat_mode_preference (mode_preference_mask); if (allowed == MM_MODEM_MODE_NONE) { gchar *str; str = qmi_nas_rat_mode_preference_build_string_from_mask (mode_preference_mask); g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported modes reported: '%s'", str); g_free (str); goto out; } g_assert (priv->supported_radio_interfaces); allowed = filter_modes_by_supported_radio_interfaces (self, priv->supported_radio_interfaces, allowed); /* We got a valid value from here */ result = g_new (LoadCurrentModesResult, 1); result->allowed = allowed; result->preferred = MM_MODEM_MODE_NONE; /* If acquisition order preference is available, always use that first */ if (priv->feature_nas_ssp_acquisition_order_preference == FEATURE_SUPPORTED) { GArray *array; if (qmi_message_nas_get_system_selection_preference_output_get_acquisition_order_preference (output, &array, NULL) && array->len > 0) { guint i; /* The array of preference contains the preference of the full list of supported * access technologies, regardless of whether they're enabled or not. So, look for * the first one that is flagged as enabled, not just the first one in the array. */ for (i = 0; i < array->len; i++) { MMModemMode mode; mode = mm_modem_mode_from_qmi_nas_radio_interface (g_array_index (array, QmiNasRadioInterface, i)); if (allowed == mode) break; if (allowed & mode) { result->preferred = mode; break; } } } } /* For 2G+3G only rely on the GSM/WCDMA acquisition order preference TLV */ else if (mode_preference_mask == (QMI_NAS_RAT_MODE_PREFERENCE_GSM | QMI_NAS_RAT_MODE_PREFERENCE_UMTS)) { QmiNasGsmWcdmaAcquisitionOrderPreference gsm_or_wcdma; if (qmi_message_nas_get_system_selection_preference_output_get_gsm_wcdma_acquisition_order_preference ( output, &gsm_or_wcdma, NULL)) result->preferred = mm_modem_mode_from_qmi_gsm_wcdma_acquisition_order_preference (gsm_or_wcdma, self); } g_task_return_pointer (task, result, g_free); out: if (output) qmi_message_nas_get_system_selection_preference_output_unref (output); g_object_unref (task); } static void load_current_modes_system_selection_preference (GTask *task) { LoadCurrentModesContext *ctx; ctx = g_task_get_task_data (task); qmi_client_nas_get_system_selection_preference ( ctx->client, NULL, /* no input */ 5, NULL, /* cancellable */ (GAsyncReadyCallback)load_current_modes_system_selection_preference_ready, task); } void mm_shared_qmi_load_current_modes (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { Private *priv; LoadCurrentModesContext *ctx; GTask *task; QmiClient *client = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_NAS, &client, callback, user_data)) return; ctx = g_new0 (LoadCurrentModesContext, 1); ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); task = g_task_new (self, NULL, callback, user_data); g_task_set_task_data (task, ctx, (GDestroyNotify)load_current_modes_context_free); priv = get_private (MM_SHARED_QMI (self)); if (priv->feature_nas_ssp != FEATURE_UNSUPPORTED) { load_current_modes_system_selection_preference (task); return; } if (priv->feature_nas_tp != FEATURE_UNSUPPORTED) { load_current_modes_technology_preference (task); return; } /* Default to supported */ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "Loading current modes is not supported by this device"); g_object_unref (task); } /*****************************************************************************/ /* Supported modes (Modem interface) */ GArray * mm_shared_qmi_load_supported_modes_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_pointer (G_TASK (res), error); } void mm_shared_qmi_load_supported_modes (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; Private *priv; MMQmiSupportedModesContext ctx = { 0 }; guint i; GArray *combinations; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_QMI (self)); g_assert (priv->supported_radio_interfaces); g_assert (priv->current_capabilities != MM_MODEM_CAPABILITY_NONE); /* Build all, based on the supported radio interfaces */ ctx.all = MM_MODEM_MODE_NONE; for (i = 0; i < priv->supported_radio_interfaces->len; i++) ctx.all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self); /* Filter out those unsupported by the current capabilities */ if (!(priv->current_capabilities & (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO))) ctx.all &= ~(MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_LTE)) ctx.all &= ~MM_MODEM_MODE_4G; else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_5GNR)) ctx.all &= ~MM_MODEM_MODE_5G; ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED); ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED); ctx.current_capabilities = priv->current_capabilities; ctx.multimode = priv->multimode; combinations = mm_supported_modes_from_qmi_supported_modes_context (&ctx, self); g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref); g_object_unref (task); } /*****************************************************************************/ /* Load supported bands (Modem interface) */ GArray * mm_shared_qmi_load_supported_bands_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return (GArray *) g_task_propagate_pointer (G_TASK (res), error); } static void dms_get_band_capabilities_ready (QmiClientDms *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; Private *priv; QmiMessageDmsGetBandCapabilitiesOutput *output; GError *error = NULL; GArray *mm_bands = NULL; QmiDmsBandCapability qmi_bands = 0; QmiDmsLteBandCapability qmi_lte_bands = 0; GArray *extended_qmi_lte_bands = NULL; GArray *qmi_nr5g_bands = NULL; self = g_task_get_source_object (task); priv = get_private (self); output = qmi_client_dms_get_band_capabilities_finish (client, res, &error); if (!output || !qmi_message_dms_get_band_capabilities_output_get_result (output, &error)) { g_prefix_error (&error, "Couldn't get band capabilities: "); goto out; } qmi_message_dms_get_band_capabilities_output_get_band_capability ( output, &qmi_bands, NULL); qmi_message_dms_get_band_capabilities_output_get_lte_band_capability ( output, &qmi_lte_bands, NULL); qmi_message_dms_get_band_capabilities_output_get_extended_lte_band_capability ( output, &extended_qmi_lte_bands, NULL); qmi_message_dms_get_band_capabilities_output_get_nr5g_band_capability ( output, &qmi_nr5g_bands, NULL); mm_bands = mm_modem_bands_from_qmi_band_capabilities (qmi_bands, qmi_lte_bands, extended_qmi_lte_bands, qmi_nr5g_bands, self); if (mm_bands->len == 0) { g_clear_pointer (&mm_bands, g_array_unref); error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse the list of supported bands"); goto out; } /* Cache the result */ g_clear_pointer (&priv->supported_bands, g_array_unref); priv->supported_bands = g_array_ref (mm_bands); out: if (output) qmi_message_dms_get_band_capabilities_output_unref (output); if (error) g_task_return_error (task, error); else if (mm_bands) g_task_return_pointer (task, mm_bands, (GDestroyNotify)g_array_unref); else g_assert_not_reached (); g_object_unref (task); } void mm_shared_qmi_load_supported_bands (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; QmiClient *client = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_DMS, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); qmi_client_dms_get_band_capabilities (QMI_CLIENT_DMS (client), NULL, 5, NULL, (GAsyncReadyCallback)dms_get_band_capabilities_ready, task); } /*****************************************************************************/ /* Load current bands (Modem interface) */ GArray * mm_shared_qmi_load_current_bands_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return (GArray *) g_task_propagate_pointer (G_TASK (res), error); } static void load_bands_get_system_selection_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; Private *priv; QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; GError *error = NULL; GArray *mm_bands = NULL; QmiNasBandPreference band_preference_mask = 0; QmiNasLteBandPreference lte_band_preference_mask = 0; guint64 extended_lte_band_preference[4] = { 0 }; guint extended_lte_band_preference_size = 0; guint64 nr5g_sa_band_preference[8] = { 0 }; guint64 nr5g_nsa_band_preference[8] = { 0 }; guint64 nr5g_band_preference[8] = { 0 }; guint nr5g_band_preference_size = 0; self = g_task_get_source_object (task); priv = get_private (self); output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) { g_prefix_error (&error, "Couldn't get system selection preference: "); goto out; } qmi_message_nas_get_system_selection_preference_output_get_band_preference ( output, &band_preference_mask, NULL); qmi_message_nas_get_system_selection_preference_output_get_lte_band_preference ( output, <e_band_preference_mask, NULL); if ((priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED) && qmi_message_nas_get_system_selection_preference_output_get_extended_lte_band_preference ( output, &extended_lte_band_preference[0], &extended_lte_band_preference[1], &extended_lte_band_preference[2], &extended_lte_band_preference[3], NULL)) extended_lte_band_preference_size = G_N_ELEMENTS (extended_lte_band_preference); if (qmi_message_nas_get_system_selection_preference_output_get_nr5g_sa_band_preference ( output, &nr5g_sa_band_preference[0], &nr5g_sa_band_preference[1], &nr5g_sa_band_preference[2], &nr5g_sa_band_preference[3], &nr5g_sa_band_preference[4], &nr5g_sa_band_preference[5], &nr5g_sa_band_preference[6], &nr5g_sa_band_preference[7], NULL) || qmi_message_nas_get_system_selection_preference_output_get_nr5g_nsa_band_preference ( output, &nr5g_nsa_band_preference[0], &nr5g_nsa_band_preference[1], &nr5g_nsa_band_preference[2], &nr5g_nsa_band_preference[3], &nr5g_nsa_band_preference[4], &nr5g_nsa_band_preference[5], &nr5g_nsa_band_preference[6], &nr5g_nsa_band_preference[7], NULL)) { guint i; nr5g_band_preference_size = G_N_ELEMENTS (nr5g_band_preference); for (i = 0; i < nr5g_band_preference_size; i++) nr5g_band_preference[i] = nr5g_sa_band_preference[i] | nr5g_nsa_band_preference[i]; } mm_bands = mm_modem_bands_from_qmi_band_preference (band_preference_mask, lte_band_preference_mask, extended_lte_band_preference_size ? extended_lte_band_preference : NULL, extended_lte_band_preference_size, nr5g_band_preference_size ? nr5g_band_preference : NULL, nr5g_band_preference_size, self); if (mm_bands->len == 0) { g_clear_pointer (&mm_bands, g_array_unref); error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse the list of current bands"); } out: if (output) qmi_message_nas_get_system_selection_preference_output_unref (output); if (error) g_task_return_error (task, error); else if (mm_bands) g_task_return_pointer (task, mm_bands, (GDestroyNotify)g_array_unref); else g_assert_not_reached (); g_object_unref (task); } void mm_shared_qmi_load_current_bands (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; QmiClient *client = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_NAS, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); qmi_client_nas_get_system_selection_preference ( QMI_CLIENT_NAS (client), NULL, /* no input */ 5, NULL, /* cancellable */ (GAsyncReadyCallback)load_bands_get_system_selection_preference_ready, task); } /*****************************************************************************/ /* Set current bands (Modem interface) */ gboolean mm_shared_qmi_set_current_bands_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void bands_set_system_selection_preference_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; GError *error = NULL; output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) { g_prefix_error (&error, "Couldn't set system selection preference: "); g_task_return_error (task, error); } else g_task_return_boolean (task, TRUE); if (output) qmi_message_nas_set_system_selection_preference_output_unref (output); g_object_unref (task); } void mm_shared_qmi_set_current_bands (MMIfaceModem *self, GArray *bands_array, GAsyncReadyCallback callback, gpointer user_data) { QmiMessageNasSetSystemSelectionPreferenceInput *input; Private *priv; GTask *task; QmiClient *client = NULL; QmiNasBandPreference qmi_bands = 0; QmiNasLteBandPreference qmi_lte_bands = 0; guint64 extended_qmi_lte_bands[4] = { 0 }; guint64 qmi_nr5g_bands[8] = { 0 }; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_NAS, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_QMI (self)); /* Handle ANY separately */ if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { if (!priv->supported_bands) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot handle 'ANY' if supported bands are unknown"); g_object_unref (task); return; } bands_array = priv->supported_bands; } mm_modem_bands_to_qmi_band_preference (bands_array, &qmi_bands, &qmi_lte_bands, priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED ? extended_qmi_lte_bands : NULL, G_N_ELEMENTS (extended_qmi_lte_bands), qmi_nr5g_bands, G_N_ELEMENTS (qmi_nr5g_bands), self); input = qmi_message_nas_set_system_selection_preference_input_new (); qmi_message_nas_set_system_selection_preference_input_set_band_preference (input, qmi_bands, NULL); if (mm_iface_modem_is_3gpp_lte (self)) { if (priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED) qmi_message_nas_set_system_selection_preference_input_set_extended_lte_band_preference ( input, extended_qmi_lte_bands[0], extended_qmi_lte_bands[1], extended_qmi_lte_bands[2], extended_qmi_lte_bands[3], NULL); else qmi_message_nas_set_system_selection_preference_input_set_lte_band_preference (input, qmi_lte_bands, NULL); } qmi_message_nas_set_system_selection_preference_input_set_nr5g_sa_band_preference ( input, qmi_nr5g_bands[0], qmi_nr5g_bands[1], qmi_nr5g_bands[2], qmi_nr5g_bands[3], qmi_nr5g_bands[4], qmi_nr5g_bands[5], qmi_nr5g_bands[6], qmi_nr5g_bands[7], NULL); qmi_message_nas_set_system_selection_preference_input_set_nr5g_nsa_band_preference ( input, qmi_nr5g_bands[0], qmi_nr5g_bands[1], qmi_nr5g_bands[2], qmi_nr5g_bands[3], qmi_nr5g_bands[4], qmi_nr5g_bands[5], qmi_nr5g_bands[6], qmi_nr5g_bands[7], NULL); qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL); qmi_client_nas_set_system_selection_preference ( QMI_CLIENT_NAS (client), input, 5, NULL, /* cancellable */ (GAsyncReadyCallback)bands_set_system_selection_preference_ready, task); qmi_message_nas_set_system_selection_preference_input_unref (input); } /*****************************************************************************/ /* Reset (Modem interface) */ gboolean mm_shared_qmi_reset_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void reset_set_operating_mode_reset_ready (QmiClientDms *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; QmiMessageDmsSetOperatingModeOutput *output; GError *error = NULL; self = g_task_get_source_object (task); output = qmi_client_dms_set_operating_mode_finish (client, res, &error); if (!output || !qmi_message_dms_set_operating_mode_output_get_result (output, &error)) { g_task_return_error (task, error); } else { mm_obj_msg (self, "rebooting now"); g_task_return_boolean (task, TRUE); } if (output) qmi_message_dms_set_operating_mode_output_unref (output); g_object_unref (task); } static void reset_set_operating_mode_offline_ready (QmiClientDms *client, GAsyncResult *res, GTask *task) { QmiMessageDmsSetOperatingModeInput *input; QmiMessageDmsSetOperatingModeOutput *output; GError *error = NULL; output = qmi_client_dms_set_operating_mode_finish (client, res, &error); if (!output) { g_task_return_error (task, error); g_object_unref (task); return; } if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) { g_task_return_error (task, error); g_object_unref (task); qmi_message_dms_set_operating_mode_output_unref (output); return; } qmi_message_dms_set_operating_mode_output_unref (output); /* Now, go into reset mode. This will fully reboot the modem, and the current * modem object should get disposed. */ input = qmi_message_dms_set_operating_mode_input_new (); qmi_message_dms_set_operating_mode_input_set_mode (input, QMI_DMS_OPERATING_MODE_RESET, NULL); qmi_client_dms_set_operating_mode (client, input, 20, NULL, (GAsyncReadyCallback)reset_set_operating_mode_reset_ready, task); qmi_message_dms_set_operating_mode_input_unref (input); } void mm_shared_qmi_reset (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { QmiMessageDmsSetOperatingModeInput *input; GTask *task; QmiClient *client; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_DMS, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); /* Now, go into offline mode */ input = qmi_message_dms_set_operating_mode_input_new (); qmi_message_dms_set_operating_mode_input_set_mode (input, QMI_DMS_OPERATING_MODE_OFFLINE, NULL); qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (client), input, 20, NULL, (GAsyncReadyCallback)reset_set_operating_mode_offline_ready, task); qmi_message_dms_set_operating_mode_input_unref (input); } /*****************************************************************************/ /* Factory reset (Modem interface) */ gboolean mm_shared_qmi_factory_reset_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void dms_restore_factory_defaults_ready (QmiClientDms *client, GAsyncResult *res, GTask *task) { QmiMessageDmsRestoreFactoryDefaultsOutput *output = NULL; GError *error = NULL; output = qmi_client_dms_restore_factory_defaults_finish (client, res, &error); if (!output) { g_prefix_error (&error, "QMI operation failed: "); g_task_return_error (task, error); } else if (!qmi_message_dms_restore_factory_defaults_output_get_result (output, &error)) { g_prefix_error (&error, "Couldn't restore factory defaults: "); g_task_return_error (task, error); } else g_task_return_boolean (task, TRUE); if (output) qmi_message_dms_restore_factory_defaults_output_unref (output); g_object_unref (task); } void mm_shared_qmi_factory_reset (MMIfaceModem *self, const gchar *code, GAsyncReadyCallback callback, gpointer user_data) { QmiMessageDmsRestoreFactoryDefaultsInput *input; GTask *task; QmiClient *client = NULL; GError *error = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_DMS, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); input = qmi_message_dms_restore_factory_defaults_input_new (); if (!qmi_message_dms_restore_factory_defaults_input_set_service_programming_code ( input, code, &error)) { qmi_message_dms_restore_factory_defaults_input_unref (input); g_task_return_error (task, error); g_object_unref (task); return; } mm_obj_dbg (self, "performing a factory reset..."); qmi_client_dms_restore_factory_defaults (QMI_CLIENT_DMS (client), input, 10, NULL, (GAsyncReadyCallback)dms_restore_factory_defaults_ready, task); } /*****************************************************************************/ /* Setup carrier config (Modem interface) */ #define SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS 10 #define GENERIC_CONFIG_FALLBACK "generic" typedef enum { SETUP_CARRIER_CONFIG_STEP_FIRST, SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED, SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED, SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT, SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT, SETUP_CARRIER_CONFIG_STEP_LAST, } SetupCarrierConfigStep; typedef struct { SetupCarrierConfigStep step; QmiClientPdc *client; GKeyFile *keyfile; gchar *imsi; gint config_requested_i; gchar *config_requested; guint token; guint timeout_id; gulong set_selected_config_indication_id; gulong activate_config_indication_id; } SetupCarrierConfigContext; /* Allow to cleanup action setup right away, without being tied * to the lifecycle of the GTask */ static void setup_carrier_config_context_cleanup_action (SetupCarrierConfigContext *ctx) { if (ctx->activate_config_indication_id) { g_signal_handler_disconnect (ctx->client, ctx->activate_config_indication_id); ctx->activate_config_indication_id = 0; } if (ctx->set_selected_config_indication_id) { g_signal_handler_disconnect (ctx->client, ctx->set_selected_config_indication_id); ctx->set_selected_config_indication_id = 0; } if (ctx->timeout_id) { g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; } } static void setup_carrier_config_context_free (SetupCarrierConfigContext *ctx) { setup_carrier_config_context_cleanup_action (ctx); g_free (ctx->config_requested); g_free (ctx->imsi); g_key_file_unref (ctx->keyfile); g_clear_object (&ctx->client); g_slice_free (SetupCarrierConfigContext, ctx); } gboolean mm_shared_qmi_setup_carrier_config_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void setup_carrier_config_step (GTask *task); static void setup_carrier_config_abort (GTask *task, GError *error) { SetupCarrierConfigContext *ctx; ctx = g_task_get_task_data (task); setup_carrier_config_context_cleanup_action (ctx); g_task_return_error (task, error); g_object_unref (task); } static gboolean setup_carrier_config_timeout_no_error (GTask *task) { SetupCarrierConfigContext *ctx; ctx = g_task_get_task_data (task); g_assert (ctx->timeout_id); ctx->timeout_id = 0; setup_carrier_config_context_cleanup_action (ctx); ctx->step++; setup_carrier_config_step (task); return G_SOURCE_REMOVE; } static gboolean setup_carrier_config_timeout (GTask *task) { SetupCarrierConfigContext *ctx; ctx = g_task_get_task_data (task); g_assert (ctx->timeout_id); ctx->timeout_id = 0; setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, "Operation timed out")); return G_SOURCE_REMOVE; } static void activate_config_indication (QmiClientPdc *client, QmiIndicationPdcActivateConfigOutput *output, GTask *task) { SetupCarrierConfigContext *ctx; GError *error = NULL; guint16 error_code = 0; ctx = g_task_get_task_data (task); if (!qmi_indication_pdc_activate_config_output_get_indication_result (output, &error_code, &error)) { setup_carrier_config_abort (task, error); return; } if (error_code != 0) { setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't activate config: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code))); return; } /* Go on */ setup_carrier_config_context_cleanup_action (ctx); ctx->step++; setup_carrier_config_step (task); } static void activate_config_ready (QmiClientPdc *client, GAsyncResult *res, GTask *task) { QmiMessagePdcActivateConfigOutput *output; SetupCarrierConfigContext *ctx; GError *error = NULL; ctx = g_task_get_task_data (task); output = qmi_client_pdc_activate_config_finish (client, res, &error); if (!output || !qmi_message_pdc_activate_config_output_get_result (output, &error)) { setup_carrier_config_abort (task, error); goto out; } /* When we activate the config, if the operation is successful, we'll just * see the modem going away completely. So, do not consider an error the timeout * waiting for the Activate Config indication, as that is actually a good * thing. */ ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS, (GSourceFunc) setup_carrier_config_timeout_no_error, task); ctx->activate_config_indication_id = g_signal_connect (ctx->client, "activate-config", G_CALLBACK (activate_config_indication), task); out: if (output) qmi_message_pdc_activate_config_output_unref (output); } static void set_selected_config_indication (QmiClientPdc *client, QmiIndicationPdcSetSelectedConfigOutput *output, GTask *task) { SetupCarrierConfigContext *ctx; GError *error = NULL; guint16 error_code = 0; ctx = g_task_get_task_data (task); if (!qmi_indication_pdc_set_selected_config_output_get_indication_result (output, &error_code, &error)) { setup_carrier_config_abort (task, error); return; } if (error_code != 0) { setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't set selected config: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code))); return; } /* Go on */ setup_carrier_config_context_cleanup_action (ctx); ctx->step++; setup_carrier_config_step (task); } static void set_selected_config_ready (QmiClientPdc *client, GAsyncResult *res, GTask *task) { QmiMessagePdcSetSelectedConfigOutput *output; SetupCarrierConfigContext *ctx; GError *error = NULL; ctx = g_task_get_task_data (task); output = qmi_client_pdc_set_selected_config_finish (client, res, &error); if (!output || !qmi_message_pdc_set_selected_config_output_get_result (output, &error)) { setup_carrier_config_abort (task, error); goto out; } ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS, (GSourceFunc) setup_carrier_config_timeout, task); ctx->set_selected_config_indication_id = g_signal_connect (ctx->client, "set-selected-config", G_CALLBACK (set_selected_config_indication), task); out: if (output) qmi_message_pdc_set_selected_config_output_unref (output); } static gint select_newest_carrier_config (MMSharedQmi *self, gint config_a_i, gint config_b_i) { Private *priv; ConfigInfo *config_a; ConfigInfo *config_b; priv = get_private (self); config_a = &g_array_index (priv->config_list, ConfigInfo, config_a_i); config_b = &g_array_index (priv->config_list, ConfigInfo, config_b_i); g_assert (!g_strcmp0 (config_a->description, config_b->description)); if (config_a->version > config_b->version) return config_a_i; if (config_b->version > config_a->version) return config_b_i; /* if both are equal, return the first one found always */ return config_a_i; } static void find_requested_carrier_config (GTask *task) { SetupCarrierConfigContext *ctx; MMSharedQmi *self; Private *priv; gchar mccmnc[7]; gchar *group; gint config_fallback_i = -1; gchar *config_fallback = NULL; ctx = g_task_get_task_data (task); self = MM_SHARED_QMI (g_task_get_source_object (task)); priv = get_private (self); /* Only one group expected per file, so get the start one */ group = g_key_file_get_start_group (ctx->keyfile); /* Match generic configuration */ config_fallback = g_key_file_get_string (ctx->keyfile, group, GENERIC_CONFIG_FALLBACK, NULL); mm_obj_dbg (self, "fallback carrier configuration %sfound in group '%s'", config_fallback ? "" : "not ", group); /* First, try to match 6 MCCMNC digits (3-digit MNCs) */ strncpy (mccmnc, ctx->imsi, 6); mccmnc[6] = '\0'; ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL); if (!ctx->config_requested) { /* If not found, try to match 5 MCCMNC digits (2-digit MNCs) */ mccmnc[5] = '\0'; ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL); } mm_obj_dbg (self, "requested carrier configuration %sfound for '%s' in group '%s': %s", ctx->config_requested ? "" : "not ", mccmnc, group, ctx->config_requested ? ctx->config_requested : "n/a"); if (!ctx->config_requested && !config_fallback) { setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, "no valid configuration found in group '%s'", group)); goto out; } /* Now, look for the configurations among the ones available in the device */ if (priv->config_list) { guint i; for (i = 0; i < priv->config_list->len; i++) { ConfigInfo *config; config = &g_array_index (priv->config_list, ConfigInfo, i); if (ctx->config_requested && !g_strcmp0 (ctx->config_requested, config->description)) { mm_obj_dbg (self, "requested carrier configuration '%s' is available (version 0x%08x, size %u bytes)", config->description, config->version, config->total_size); if (ctx->config_requested_i < 0) ctx->config_requested_i = i; else ctx->config_requested_i = select_newest_carrier_config (self, ctx->config_requested_i, i); } if (config_fallback && !g_strcmp0 (config_fallback, config->description)) { mm_obj_dbg (self, "fallback carrier configuration '%s' is available (version 0x%08x, size %u bytes)", config->description, config->version, config->total_size); if (config_fallback_i < 0) config_fallback_i = i; else config_fallback_i = select_newest_carrier_config (self, config_fallback_i, i); } } } /* Fail operation if we didn't find the one we want */ if ((ctx->config_requested_i < 0) && (config_fallback_i < 0)) { setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "carrier configurations (requested '%s', fallback '%s') are not available", ctx->config_requested, config_fallback)); goto out; } /* If the mapping expects a given config, but the config isn't installed, * we fallback to generic */ if (ctx->config_requested_i < 0) { ConfigInfo *config; g_assert (config_fallback_i >= 0); config = &g_array_index (priv->config_list, ConfigInfo, config_fallback_i); mm_obj_dbg (self, "using fallback carrier configuration '%s' (version 0x%08x, size %u bytes)", config->description, config->version, config->total_size); g_free (ctx->config_requested); ctx->config_requested = config_fallback; ctx->config_requested_i = config_fallback_i; config_fallback = NULL; } else { ConfigInfo *config; config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); mm_obj_dbg (self, "using requested carrier configuration '%s' (version 0x%08x, size %u bytes)", config->description, config->version, config->total_size); } ctx->step++; setup_carrier_config_step (task); out: g_free (config_fallback); g_free (group); } static void setup_carrier_config_step (GTask *task) { MMSharedQmi *self; SetupCarrierConfigContext *ctx; Private *priv; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); priv = get_private (self); switch (ctx->step) { case SETUP_CARRIER_CONFIG_STEP_FIRST: ctx->step++; /* fall-through */ case SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED: find_requested_carrier_config (task); return; case SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED: g_assert (ctx->config_requested_i >= 0); g_assert (priv->config_active_i >= 0 || priv->config_active_default); if (ctx->config_requested_i == priv->config_active_i) { mm_obj_msg (self, "carrier config switching not needed: already using '%s'", ctx->config_requested); ctx->step = SETUP_CARRIER_CONFIG_STEP_LAST; setup_carrier_config_step (task); return; } ctx->step++; /* fall-through */ case SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT: { g_autoptr(QmiMessagePdcSetSelectedConfigInput) input = NULL; ConfigInfo *requested_config; ConfigInfo *active_config; requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); active_config = (priv->config_active_default ? NULL : &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i)); mm_obj_warn (self, "carrier config switching needed: '%s' -> '%s'", active_config ? active_config->description : DEFAULT_CONFIG_DESCRIPTION, requested_config->description); input = qmi_message_pdc_set_selected_config_input_new (); qmi_message_pdc_set_selected_config_input_set_type_with_id_v2 (input, requested_config->config_type, requested_config->id, NULL); qmi_message_pdc_set_selected_config_input_set_token (input, ctx->token++, NULL); qmi_client_pdc_set_selected_config (ctx->client, input, 10, NULL, (GAsyncReadyCallback)set_selected_config_ready, task); return; } case SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT: { g_autoptr(QmiMessagePdcActivateConfigInput) input = NULL; ConfigInfo *requested_config; requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); input = qmi_message_pdc_activate_config_input_new (); qmi_message_pdc_activate_config_input_set_config_type (input, requested_config->config_type, NULL); qmi_message_pdc_activate_config_input_set_token (input, ctx->token++, NULL); qmi_client_pdc_activate_config (ctx->client, input, 10, NULL, (GAsyncReadyCallback) activate_config_ready, task); return; } case SETUP_CARRIER_CONFIG_STEP_LAST: g_task_return_boolean (task, TRUE); g_object_unref (task); break; default: g_assert_not_reached (); } } void mm_shared_qmi_setup_carrier_config (MMIfaceModem *self, const gchar *imsi, const gchar *carrier_config_mapping, GAsyncReadyCallback callback, gpointer user_data) { SetupCarrierConfigContext *ctx; GTask *task; QmiClient *client = NULL; GError *error = NULL; g_assert (imsi); g_assert (carrier_config_mapping); task = g_task_new (self, NULL, callback, user_data); ctx = g_slice_new0 (SetupCarrierConfigContext); ctx->step = SETUP_CARRIER_CONFIG_STEP_FIRST; ctx->imsi = g_strdup (imsi); ctx->keyfile = g_key_file_new (); ctx->config_requested_i = -1; g_task_set_task_data (task, ctx, (GDestroyNotify)setup_carrier_config_context_free); /* Load mapping keyfile */ if (!g_key_file_load_from_file (ctx->keyfile, carrier_config_mapping, G_KEY_FILE_NONE, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } /* Load PDC client */ client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDC, MM_PORT_QMI_FLAG_DEFAULT, NULL); if (!client) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "QMI PDC not supported"); g_object_unref (task); return; } ctx->client = QMI_CLIENT_PDC (g_object_ref (client)); setup_carrier_config_step (task); } /*****************************************************************************/ /* Load carrier config (Modem interface) */ #define LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS 5 typedef enum { LOAD_CARRIER_CONFIG_STEP_FIRST, LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS, LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT, LOAD_CARRIER_CONFIG_STEP_LAST, } LoadCarrierConfigStep; typedef struct { LoadCarrierConfigStep step; QmiClientPdc *client; GArray *config_list; guint configs_loaded; gboolean config_active_default; gint config_active_i; guint token; guint timeout_id; gulong list_configs_indication_id; gulong get_selected_config_indication_id; gulong get_config_info_indication_id; } LoadCarrierConfigContext; /* Allow to cleanup action load right away, without being tied * to the lifecycle of the GTask */ static void load_carrier_config_context_cleanup_action (LoadCarrierConfigContext *ctx) { if (ctx->get_selected_config_indication_id) { g_signal_handler_disconnect (ctx->client, ctx->get_selected_config_indication_id); ctx->get_selected_config_indication_id = 0; } if (ctx->get_config_info_indication_id) { g_signal_handler_disconnect (ctx->client, ctx->get_config_info_indication_id); ctx->get_config_info_indication_id = 0; } if (ctx->list_configs_indication_id) { g_signal_handler_disconnect (ctx->client, ctx->list_configs_indication_id); ctx->list_configs_indication_id = 0; } if (ctx->timeout_id) { g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; } } static void load_carrier_config_context_free (LoadCarrierConfigContext *ctx) { load_carrier_config_context_cleanup_action (ctx); if (ctx->config_list) g_array_unref (ctx->config_list); g_clear_object (&ctx->client); g_slice_free (LoadCarrierConfigContext, ctx); } gboolean mm_shared_qmi_load_carrier_config_finish (MMIfaceModem *self, GAsyncResult *res, gchar **carrier_config_name, gchar **carrier_config_revision, GError **error) { Private *priv; if (!g_task_propagate_boolean (G_TASK (res), error)) return FALSE; priv = get_private (MM_SHARED_QMI (self)); g_assert (priv->config_active_i >= 0 || priv->config_active_default); if (priv->config_active_i >= 0) { ConfigInfo *config; config = &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i); *carrier_config_name = g_strdup (config->description); *carrier_config_revision = g_strdup_printf ("%08X", config->version); } else if (priv->config_active_default) { *carrier_config_name = g_strdup (DEFAULT_CONFIG_DESCRIPTION); *carrier_config_revision = NULL; } else g_assert_not_reached (); return TRUE; } static void load_carrier_config_step (GTask *task); static void load_carrier_config_abort (GTask *task, GError *error) { LoadCarrierConfigContext *ctx; ctx = g_task_get_task_data (task); load_carrier_config_context_cleanup_action (ctx); g_task_return_error (task, error); g_object_unref (task); } static gboolean load_carrier_config_timeout (GTask *task) { LoadCarrierConfigContext *ctx; ctx = g_task_get_task_data (task); g_assert (ctx->timeout_id); ctx->timeout_id = 0; load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, "Operation timed out")); return G_SOURCE_REMOVE; } static void get_selected_config_indication (QmiClientPdc *client, QmiIndicationPdcGetSelectedConfigOutput *output, GTask *task) { MMSharedQmi *self; LoadCarrierConfigContext *ctx; GArray *active_id = NULL; GError *error = NULL; guint16 error_code = 0; guint i; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); if (!qmi_indication_pdc_get_selected_config_output_get_indication_result (output, &error_code, &error)) { load_carrier_config_abort (task, error); return; } if (error_code != 0 && error_code != QMI_PROTOCOL_ERROR_NOT_PROVISIONED) { /* No configs active */ load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't get selected config: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code))); return; } qmi_indication_pdc_get_selected_config_output_get_active_id (output, &active_id, NULL); if (!active_id) { mm_obj_dbg (self, "no carrier config currently selected (default in use)"); ctx->config_active_default = TRUE; goto next; } g_assert (ctx->config_list); g_assert (ctx->config_list->len); for (i = 0; i < ctx->config_list->len; i++) { ConfigInfo *config; config = &g_array_index (ctx->config_list, ConfigInfo, i); if ((config->id->len == active_id->len) && !memcmp (config->id->data, active_id->data, active_id->len)) { ctx->config_active_i = i; break; } } if (i == ctx->config_list->len) { load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't find currently selected config")); return; } next: /* Go on */ load_carrier_config_context_cleanup_action (ctx); ctx->step++; load_carrier_config_step (task); } static void get_selected_config_ready (QmiClientPdc *client, GAsyncResult *res, GTask *task) { QmiMessagePdcGetSelectedConfigOutput *output; LoadCarrierConfigContext *ctx; GError *error = NULL; ctx = g_task_get_task_data (task); output = qmi_client_pdc_get_selected_config_finish (client, res, &error); if (!output || !qmi_message_pdc_get_selected_config_output_get_result (output, &error)) { load_carrier_config_abort (task, error); goto out; } ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS, (GSourceFunc) load_carrier_config_timeout, task); ctx->get_selected_config_indication_id = g_signal_connect (ctx->client, "get-selected-config", G_CALLBACK (get_selected_config_indication), task); out: if (output) qmi_message_pdc_get_selected_config_output_unref (output); } static void get_config_info_indication (QmiClientPdc *client, QmiIndicationPdcGetConfigInfoOutput *output, GTask *task) { LoadCarrierConfigContext *ctx; GError *error = NULL; ConfigInfo *current_config = NULL; guint32 token; const gchar *description; guint i; guint16 error_code = 0; ctx = g_task_get_task_data (task); if (!qmi_indication_pdc_get_config_info_output_get_indication_result (output, &error_code, &error)) { load_carrier_config_abort (task, error); return; } if (error_code != 0) { load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't get config info: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code))); return; } if (!qmi_indication_pdc_get_config_info_output_get_token (output, &token, &error)) { load_carrier_config_abort (task, error); return; } /* Look for the current config in the list, match by token */ for (i = 0; i < ctx->config_list->len; i++) { current_config = &g_array_index (ctx->config_list, ConfigInfo, i); if (current_config->token == token) break; } /* Ignore if not found in the list */ if (i == ctx->config_list->len) return; /* Ignore if already set */ if (current_config->description) return; /* Store total size, version and description of the current config */ if (!qmi_indication_pdc_get_config_info_output_get_total_size (output, ¤t_config->total_size, &error) || !qmi_indication_pdc_get_config_info_output_get_version (output, ¤t_config->version, &error) || !qmi_indication_pdc_get_config_info_output_get_description (output, &description, &error)) { load_carrier_config_abort (task, error); return; } current_config->description = g_strdup (description); ctx->configs_loaded++; /* If not all loaded, wait for more */ if (ctx->configs_loaded < ctx->config_list->len) return; /* Go on */ load_carrier_config_context_cleanup_action (ctx); ctx->step++; load_carrier_config_step (task); } static void list_configs_indication (QmiClientPdc *client, QmiIndicationPdcListConfigsOutput *output, GTask *task) { MMSharedQmi *self; LoadCarrierConfigContext *ctx; GError *error = NULL; GArray *configs = NULL; guint i; guint16 error_code = 0; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); if (!qmi_indication_pdc_list_configs_output_get_indication_result (output, &error_code, &error)) { load_carrier_config_abort (task, error); return; } if (error_code != 0) { load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't list configs: %s", qmi_protocol_error_get_string ((QmiProtocolError) error_code))); return; } if (!qmi_indication_pdc_list_configs_output_get_configs (output, &configs, &error)) { load_carrier_config_abort (task, error); return; } /* If no configs are installed, the module is running with the default one */ if (!configs || !configs->len) { ctx->config_active_default = TRUE; ctx->step = LOAD_CARRIER_CONFIG_STEP_LAST; load_carrier_config_step (task); return; } /* Preallocate config list and request details for each */ mm_obj_dbg (self, "found %u carrier configurations...", configs->len); ctx->config_list = g_array_sized_new (FALSE, TRUE, sizeof (ConfigInfo), configs->len); g_array_set_size (ctx->config_list, configs->len); g_array_set_clear_func (ctx->config_list, (GDestroyNotify) config_info_clear); ctx->get_config_info_indication_id = g_signal_connect (ctx->client, "get-config-info", G_CALLBACK (get_config_info_indication), task); for (i = 0; i < configs->len; i++) { ConfigInfo *current_info; QmiIndicationPdcListConfigsOutputConfigsElement *element; g_autoptr(QmiMessagePdcGetConfigInfoInput) input = NULL; element = &g_array_index (configs, QmiIndicationPdcListConfigsOutputConfigsElement, i); current_info = &g_array_index (ctx->config_list, ConfigInfo, i); current_info->token = ctx->token++; current_info->id = g_array_ref (element->id); current_info->config_type = element->config_type; input = qmi_message_pdc_get_config_info_input_new (); qmi_message_pdc_get_config_info_input_set_type_with_id_v2 (input, element->config_type, current_info->id, NULL); qmi_message_pdc_get_config_info_input_set_token (input, current_info->token, NULL); qmi_client_pdc_get_config_info (ctx->client, input, 10, NULL, NULL, NULL); /* ignore response! */ } } static void list_configs_ready (QmiClientPdc *client, GAsyncResult *res, GTask *task) { QmiMessagePdcListConfigsOutput *output; LoadCarrierConfigContext *ctx; GError *error = NULL; ctx = g_task_get_task_data (task); output = qmi_client_pdc_list_configs_finish (client, res, &error); if (!output || !qmi_message_pdc_list_configs_output_get_result (output, &error)) { load_carrier_config_abort (task, error); goto out; } ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS, (GSourceFunc) load_carrier_config_timeout, task); ctx->list_configs_indication_id = g_signal_connect (ctx->client, "list-configs", G_CALLBACK (list_configs_indication), task); out: if (output) qmi_message_pdc_list_configs_output_unref (output); } static void load_carrier_config_step (GTask *task) { LoadCarrierConfigContext *ctx; Private *priv; ctx = g_task_get_task_data (task); priv = get_private (g_task_get_source_object (task)); switch (ctx->step) { case LOAD_CARRIER_CONFIG_STEP_FIRST: ctx->step++; /* fall-through */ case LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS: { QmiMessagePdcListConfigsInput *input; input = qmi_message_pdc_list_configs_input_new (); qmi_message_pdc_list_configs_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); qmi_message_pdc_list_configs_input_set_token (input, ctx->token++, NULL); qmi_client_pdc_list_configs (ctx->client, input, 5, NULL, (GAsyncReadyCallback)list_configs_ready, task); qmi_message_pdc_list_configs_input_unref (input); return; } case LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT: { QmiMessagePdcGetSelectedConfigInput *input; input = qmi_message_pdc_get_selected_config_input_new (); qmi_message_pdc_get_selected_config_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); qmi_message_pdc_get_selected_config_input_set_token (input, ctx->token++, NULL); qmi_client_pdc_get_selected_config (ctx->client, input, 5, NULL, (GAsyncReadyCallback)get_selected_config_ready, task); qmi_message_pdc_get_selected_config_input_unref (input); return; } case LOAD_CARRIER_CONFIG_STEP_LAST: /* We will now store the loaded information so that we can later on use it * if needed during the automatic carrier config switching operation */ g_assert (priv->config_active_i < 0 && !priv->config_active_default); g_assert (ctx->config_active_i >= 0 || ctx->config_active_default); priv->config_list = ctx->config_list ? g_array_ref (ctx->config_list) : NULL; priv->config_active_i = ctx->config_active_i; priv->config_active_default = ctx->config_active_default; g_task_return_boolean (task, TRUE); g_object_unref (task); break; default: g_assert_not_reached (); } } void mm_shared_qmi_load_carrier_config (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { LoadCarrierConfigContext *ctx; GTask *task; QmiClient *client = NULL; task = g_task_new (self, NULL, callback, user_data); ctx = g_slice_new0 (LoadCarrierConfigContext); ctx->step = LOAD_CARRIER_CONFIG_STEP_FIRST; ctx->config_active_i = -1; g_task_set_task_data (task, ctx, (GDestroyNotify)load_carrier_config_context_free); /* Load PDC client */ client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), QMI_SERVICE_PDC, MM_PORT_QMI_FLAG_DEFAULT, NULL); if (!client) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "QMI PDC not supported"); g_object_unref (task); return; } ctx->client = QMI_CLIENT_PDC (g_object_ref (client)); load_carrier_config_step (task); } /*****************************************************************************/ /* Load SIM slots (modem interface) */ typedef struct { QmiClientUim *client_uim; GPtrArray *sim_slots; guint active_slot_number; guint active_logical_id; } LoadSimSlotsContext; static void load_sim_slots_context_free (LoadSimSlotsContext *ctx) { g_clear_pointer (&ctx->sim_slots, g_ptr_array_unref); g_clear_object (&ctx->client_uim); g_slice_free (LoadSimSlotsContext, ctx); } static void sim_slot_free (MMBaseSim *sim) { if (sim) g_object_unref (sim); } gboolean mm_shared_qmi_load_sim_slots_finish (MMIfaceModem *self, GAsyncResult *res, GPtrArray **sim_slots, guint *primary_sim_slot, GError **error) { LoadSimSlotsContext *ctx; if (!g_task_propagate_boolean (G_TASK (res), error)) return FALSE; ctx = g_task_get_task_data (G_TASK (res)); if (sim_slots) *sim_slots = g_steal_pointer (&ctx->sim_slots); if (primary_sim_slot) *primary_sim_slot = ctx->active_slot_number; return TRUE; } static void uim_get_slot_status_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) { g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; LoadSimSlotsContext *ctx; MMIfaceModem *self; Private *priv; g_autoptr(GError) error = NULL; GArray *physical_slots = NULL; GArray *ext_information = NULL; GArray *slot_eids = NULL; guint i; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); priv = get_private (MM_SHARED_QMI (self)); output = qmi_client_uim_get_slot_status_finish (client, res, &error); if (!output || !qmi_message_uim_get_slot_status_output_get_result (output, &error) || !qmi_message_uim_get_slot_status_output_get_physical_slot_status (output, &physical_slots, &error)) { if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_DEVICE_UNSUPPORTED) || g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND) || g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED)) g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "QMI SIM slot switch operation not supported"); else g_task_return_error (task, g_steal_pointer (&error)); g_object_unref (task); return; } /* Store the slot status before loading all sim slots. * We will use this to check if a hotswap requires a reprobe. */ if (priv->slots_status) g_array_unref (priv->slots_status); priv->slots_status = g_array_ref (physical_slots); /* It's fine if we don't have EID information, but it should be well-formed if present. If it's malformed, * there is probably a modem firmware bug. */ if (qmi_message_uim_get_slot_status_output_get_physical_slot_information (output, &ext_information, NULL) && qmi_message_uim_get_slot_status_output_get_slot_eid (output, &slot_eids, NULL) && (ext_information->len != physical_slots->len || slot_eids->len != physical_slots->len)) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "UIM Get Slot Status returned malformed response"); g_object_unref (task); return; } ctx->sim_slots = g_ptr_array_new_full (physical_slots->len, (GDestroyNotify) sim_slot_free); for (i = 0; i < physical_slots->len; i++) { QmiPhysicalSlotStatusSlot *slot_status; QmiPhysicalSlotInformationSlot *slot_info; MMBaseSim *sim; g_autofree gchar *raw_iccid = NULL; g_autofree gchar *iccid = NULL; g_autofree gchar *eid = NULL; g_autoptr(GError) inner_error = NULL; gboolean sim_active = FALSE; /* Store active slot info */ slot_status = &g_array_index (physical_slots, QmiPhysicalSlotStatusSlot, i); if (slot_status->physical_slot_status == QMI_UIM_SLOT_STATE_ACTIVE) { sim_active = TRUE; ctx->active_logical_id = slot_status->logical_slot; ctx->active_slot_number = i + 1; } if (!slot_status->iccid->len) { mm_obj_dbg (self, "not creating SIM object: no SIM in slot %u", i + 1); g_ptr_array_add (ctx->sim_slots, NULL); continue; } /* This is supposed to be BCD, but some carriers have non-spec-compliant ICCIDs that use * A-F characters as part of the operator-specific part of the ICCID. Parse it as hex and * let mm_3gpp_parse_iccid take care of handling the A-F characters properly. */ raw_iccid = mm_utils_bin2hexstr ((const guint8 *)slot_status->iccid->data, slot_status->iccid->len); if (!raw_iccid) { mm_obj_warn (self, "not creating SIM object: failed to convert ICCID from BCD"); g_ptr_array_add (ctx->sim_slots, NULL); continue; } iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error); if (!iccid) { mm_obj_warn (self, "not creating SIM object: couldn't parse SIM iccid: %s", inner_error->message); g_ptr_array_add (ctx->sim_slots, NULL); continue; } if (ext_information && slot_eids) { slot_info = &g_array_index (ext_information, QmiPhysicalSlotInformationSlot, i); if (slot_info->is_euicc) { QmiSlotEidElement *slot_eid_element; slot_eid_element = &g_array_index (slot_eids, QmiSlotEidElement, i); if (slot_eid_element->eid->len) eid = mm_decode_eid (slot_eid_element->eid->data, slot_eid_element->eid->len); if (!eid) mm_obj_dbg (self, "SIM in slot %d is marked as eUICC, but has malformed EID", i + 1); } } sim = mm_sim_qmi_new_initialized (MM_BASE_MODEM (self), TRUE, /* consider DMS UIM deprecated if we're creating SIM slots */ i + 1, /* slot number is the array index starting at 1 */ sim_active, iccid, NULL, /* imsi unknown */ eid, /* may be NULL, which is fine */ NULL, /* operator id unknown */ NULL, /* operator name unknown */ NULL); /* emergency numbers unknown */ g_ptr_array_add (ctx->sim_slots, sim); } g_assert_cmpuint (ctx->sim_slots->len, ==, physical_slots->len); g_task_return_boolean (task, TRUE); g_object_unref (task); } void mm_shared_qmi_load_sim_slots (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { LoadSimSlotsContext *ctx; GTask *task; QmiClient *client = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_UIM, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); ctx = g_slice_new0 (LoadSimSlotsContext); ctx->client_uim = QMI_CLIENT_UIM (g_object_ref (client)); g_task_set_task_data (task, ctx, (GDestroyNotify) load_sim_slots_context_free); qmi_client_uim_get_slot_status (ctx->client_uim, NULL, 10, NULL, (GAsyncReadyCallback) uim_get_slot_status_ready, task); } /*****************************************************************************/ /* Set Primary SIM slot (modem interface) */ gboolean mm_shared_qmi_set_primary_sim_slot_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void uim_switch_slot_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) { g_autoptr(QmiMessageUimSwitchSlotOutput) output = NULL; g_autoptr(GError) error = NULL; MMIfaceModem *self; self = g_task_get_source_object (task); output = qmi_client_uim_switch_slot_finish (client, res, &error); if (!output || !qmi_message_uim_switch_slot_output_get_result (output, &error)) { if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_EXISTS, "SIM slot switch operation not needed"); else g_task_return_error (task, g_steal_pointer (&error)); } else { mm_obj_msg (self, "SIM slot switch operation request successful"); g_task_return_boolean (task, TRUE); } g_object_unref (task); } static void uim_switch_get_slot_status_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) { g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; g_autoptr(QmiMessageUimSwitchSlotInput) input = NULL; MMIfaceModem *self; g_autoptr(GError) error = NULL; GArray *physical_slots = NULL; guint i; guint active_logical_id = 0; guint active_slot_number; guint slot_number; self = g_task_get_source_object (task); slot_number = GPOINTER_TO_UINT (g_task_get_task_data (task)); output = qmi_client_uim_get_slot_status_finish (client, res, &error); if (!output || !qmi_message_uim_get_slot_status_output_get_result (output, &error) || !qmi_message_uim_get_slot_status_output_get_physical_slot_status (output, &physical_slots, &error)) { if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_DEVICE_UNSUPPORTED) || g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND) || g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED)) g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "QMI SIM slot switch operation not supported"); else g_task_return_error (task, g_steal_pointer (&error)); g_object_unref (task); return; } for (i = 0; i < physical_slots->len; i++) { QmiPhysicalSlotStatusSlot *slot_status; /* We look for the currently ACTIVE SIM card only! */ slot_status = &g_array_index (physical_slots, QmiPhysicalSlotStatusSlot, i); if (slot_status->physical_slot_status != QMI_UIM_SLOT_STATE_ACTIVE) continue; active_logical_id = slot_status->logical_slot; active_slot_number = i + 1; } if (!active_logical_id) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't find active slot logical ID"); g_object_unref (task); return; } if (active_slot_number == slot_number) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_EXISTS, "SIM slot switch operation not needed"); g_object_unref (task); return; } mm_obj_dbg (self, "requesting active logical id %d switch to SIM slot %u", active_logical_id, slot_number); input = qmi_message_uim_switch_slot_input_new (); qmi_message_uim_switch_slot_input_set_logical_slot (input, (guint8) active_logical_id, NULL); qmi_message_uim_switch_slot_input_set_physical_slot (input, slot_number, NULL); qmi_client_uim_switch_slot (client, input, 10, NULL, (GAsyncReadyCallback) uim_switch_slot_ready, task); } void mm_shared_qmi_set_primary_sim_slot (MMIfaceModem *self, guint sim_slot, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; QmiClient *client = NULL; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_UIM, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); g_task_set_task_data (task, GUINT_TO_POINTER (sim_slot), NULL); qmi_client_uim_get_slot_status (QMI_CLIENT_UIM (client), NULL, 10, NULL, (GAsyncReadyCallback) uim_switch_get_slot_status_ready, task); } /*****************************************************************************/ /* UIM refresh indication handling */ #define REFRESH_START_TIMEOUT_SECS 3 static void uim_refresh_complete (QmiClientUim *client, QmiUimSessionType session_type) { g_autoptr(QmiMessageUimRefreshCompleteInput) refresh_complete_input = NULL; GArray *placeholder_aid; placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); refresh_complete_input = qmi_message_uim_refresh_complete_input_new (); qmi_message_uim_refresh_complete_input_set_session ( refresh_complete_input, session_type, placeholder_aid, /* ignored */ NULL); qmi_message_uim_refresh_complete_input_set_info ( refresh_complete_input, TRUE, NULL); qmi_client_uim_refresh_complete ( client, refresh_complete_input, 10, NULL, NULL, NULL); g_array_unref (placeholder_aid); } static gboolean uim_start_refresh_timeout (MMSharedQmi *self) { Private *priv; priv = get_private (self); priv->uim_refresh_start_timeout_id = 0; mm_obj_dbg (self, "refresh start timed out; trigger SIM change check"); mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), 0, NULL, 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 UICC Reset type refresh, which can be used * in profile switch scenarios, and Init Full FCN type refresh for * SIM IMSI switch scenarios. In other cases we just trigger 'refresh * complete' during start phase. Signal to notify about potential SIM * profile switch is triggered when the refresh is ending. If it were * triggered in start phase, reading SIM files seems to fail with * an internal error. * * It's possible that 'end-with-success' stage never appears. For that, * we start a timer at 'start' stage and if it expires, the SIM change * check is triggered anyway. */ if (stage == QMI_UIM_REFRESH_STAGE_START) { if (mode == QMI_UIM_REFRESH_MODE_RESET || mode == QMI_UIM_REFRESH_MODE_INIT_FULL_FCN) { if (!priv->uim_refresh_start_timeout_id) priv->uim_refresh_start_timeout_id = g_timeout_add_seconds (REFRESH_START_TIMEOUT_SECS, (GSourceFunc)uim_start_refresh_timeout, self); } else uim_refresh_complete (client, session_type); } else if (stage == QMI_UIM_REFRESH_STAGE_END_WITH_SUCCESS) { if (mode == QMI_UIM_REFRESH_MODE_RESET || mode == QMI_UIM_REFRESH_MODE_INIT_FULL_FCN) { if (priv->uim_refresh_start_timeout_id) { g_source_remove (priv->uim_refresh_start_timeout_id); priv->uim_refresh_start_timeout_id = 0; } mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), 0, NULL, NULL, NULL, NULL); } } } /*****************************************************************************/ /* UIM slot status indication handling */ /* Modifies the sim at slot == index+1, based on the content of slot_status. * Primarily used when a hotswap occurs on the inactive slot */ static void update_sim_from_slot_status (MMSharedQmi *self, QmiPhysicalSlotStatusSlot *slot_status, guint slot_index) { g_autoptr(MMBaseSim) sim = NULL; g_autofree gchar *raw_iccid = NULL; g_autofree gchar *iccid = NULL; g_autoptr(GError) inner_error = NULL; mm_obj_dbg (self, "Updating sim at slot %d", slot_index + 1); /* If sim is not present, ask mm_iface_modem to clear the sim object */ if (slot_status->physical_card_status != QMI_UIM_PHYSICAL_CARD_STATE_PRESENT) { mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, NULL); return; } raw_iccid = mm_utils_bin2hexstr ((const guint8 *)slot_status->iccid->data, slot_status->iccid->len); if (!raw_iccid) { mm_obj_warn (self, "not creating SIM object: failed to convert ICCID from BCD"); return; } iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error); if (!iccid) { mm_obj_warn (self, "not creating SIM object: couldn't parse SIM iccid: %s", inner_error->message); return; } sim = mm_sim_qmi_new_initialized (MM_BASE_MODEM (self), TRUE, /* consider DMS UIM deprecated if we're creating SIM slots */ slot_index + 1, /* slot number is the array index starting at 1 */ slot_status->physical_slot_status, /* is_active */ iccid, NULL, /* imsi unknown */ NULL, /* eid unknown */ NULL, /* operator id unknown */ NULL, /* operator name unknown */ NULL); /* emergency numbers unknown */ mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, sim); } /* Checks for equality of two slots. */ static gboolean slot_status_equal (QmiPhysicalSlotStatusSlot *slot_a, QmiPhysicalSlotStatusSlot *slot_b) { guint j; if (slot_a->physical_slot_status != slot_b->physical_slot_status) return FALSE; if (slot_a->physical_card_status != slot_b->physical_card_status) return FALSE; if (slot_a->iccid->len != slot_b->iccid->len) return FALSE; for (j = 0; j < slot_a->iccid->len; j++) { if (g_array_index (slot_a->iccid, guint8, j) != g_array_index (slot_b->iccid, guint8, j)) return FALSE; } return TRUE; } /* Checks for equality of QmiPhysicalSlotStatusSlot arrays. * The number of elements in each array is expected to be the number of sim slots in the modem.*/ static gboolean slot_array_status_equal (GArray *slots_status1, GArray *slots_status2, gboolean check_active_slots_only) { guint i; if (!slots_status1 && !slots_status2) return TRUE; if (!slots_status1 || !slots_status2 || slots_status1->len != slots_status2->len) return FALSE; for (i = 0; i < slots_status1->len; i++) { /* Compare slot at index i from slots_status1 and slots_status2 */ QmiPhysicalSlotStatusSlot *slot_a; QmiPhysicalSlotStatusSlot *slot_b; slot_a = &g_array_index (slots_status1, QmiPhysicalSlotStatusSlot, i); slot_b = &g_array_index (slots_status2, QmiPhysicalSlotStatusSlot, i); /* Check that slot_a and slot_b have the same slot status (i.e. active or inactive) */ if (slot_a->physical_slot_status != slot_b->physical_slot_status) return FALSE; /* Once slot_a and slot_b are confirmed to have the same physical slot status, * we will ignore inactive slots if check_active_slots_only is set. */ if (check_active_slots_only && slot_a->physical_slot_status != QMI_UIM_SLOT_STATE_ACTIVE) { g_assert (slot_a->physical_slot_status == slot_b->physical_slot_status); continue; } if (!slot_status_equal (slot_a, slot_b)) return FALSE; } return TRUE; } static void uim_slot_status_indication_cb (QmiClientUim *client, QmiIndicationUimSlotStatusOutput *output, MMSharedQmi *self) { GArray *new_slots_status = NULL; Private *priv; guint i; g_autoptr(GError) error = NULL; priv = get_private (self); mm_obj_dbg (self, "received slot status indication"); if (!priv->slots_status) { mm_obj_dbg (self, "initial slot status is not loaded yet"); return; } if (!qmi_indication_uim_slot_status_output_get_physical_slot_status (output, &new_slots_status, &error)) { mm_obj_warn (self, "could not process slot status indication: %s", error->message); return; } /* A slot status indication means that * 1) The physical slot to logical slot mapping has changed as a * result of switching the slot. or, * 2) A card has been removed from, or inserted to, the physical slot. or, * 3) A physical slot is powered up or down. */ /* Reprobe if the active slot changed or the information in an * active slot's status changed */ if (!slot_array_status_equal (priv->slots_status, new_slots_status, TRUE)) { mm_obj_dbg (self, "An active slot had a status change, will reprobe the modem"); mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self)); return; } /* An inactive slot changed, we won't be reprobing the modem. * Instead, we will ask mm_iface_modem to update the sim object. * Iterate over each slot to identify the slots that changed state.*/ for (i = 0; i < priv->slots_status->len; i++) { QmiPhysicalSlotStatusSlot *old_slot; QmiPhysicalSlotStatusSlot *new_slot; old_slot = &g_array_index (priv->slots_status, QmiPhysicalSlotStatusSlot, i); new_slot = &g_array_index (new_slots_status, QmiPhysicalSlotStatusSlot, i); if (!slot_status_equal (old_slot, new_slot)) { mm_obj_dbg (self, "Slot %d (inactive) had a status change. Will update sims, but not reprobe", i + 1); update_sim_from_slot_status (self, new_slot, i); } } g_clear_pointer (&priv->slots_status, g_array_unref); priv->slots_status = g_array_ref (new_slots_status); } /*****************************************************************************/ /* SIM hot swap setup */ typedef enum { SETUP_SIM_HOT_SWAP_STEP_FIRST, SETUP_SIM_HOT_SWAP_STEP_UIM_REGISTER_SLOT_STATUS, SETUP_SIM_HOT_SWAP_STEP_UIM_CHECK_SLOT_STATUS, SETUP_SIM_HOT_SWAP_STEP_UIM_SLOT_STATUS_INDICATION, SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ALL, SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ICCID, SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_IMSI, SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_INDICATION, SETUP_SIM_HOT_SWAP_STEP_LAST, } SetupSimHotSwapStep; typedef struct { SetupSimHotSwapStep step; gboolean register_slot_status_supported; gboolean get_slot_status_supported; gboolean refresh_all_supported; gboolean refresh_file_supported; QmiClient *uim_client; gulong uim_slot_status_indication_id; gulong uim_refresh_indication_id; } SetupSimHotSwapContext; static void setup_sim_hot_swap_step (GTask *task); static void setup_sim_hot_swap_context_free (SetupSimHotSwapContext *ctx) { if (ctx->uim_client && ctx->uim_slot_status_indication_id) g_signal_handler_disconnect (ctx->uim_client, ctx->uim_slot_status_indication_id); if (ctx->uim_client && ctx->uim_refresh_indication_id) g_signal_handler_disconnect (ctx->uim_client, ctx->uim_refresh_indication_id); g_clear_object (&ctx->uim_client); g_slice_free (SetupSimHotSwapContext, ctx); } gboolean mm_shared_qmi_setup_sim_hot_swap_finish (MMIfaceModem *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void uim_refresh_register_file_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) { MMSharedQmi *self; g_autoptr(QmiMessageUimRefreshRegisterOutput) output = NULL; g_autoptr(GError) error = NULL; SetupSimHotSwapContext *ctx; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); output = qmi_client_uim_refresh_register_finish (client, res, &error); if (!output || !qmi_message_uim_refresh_register_output_get_result (output, &error)) mm_obj_dbg (self, "file refresh registration using 'refresh register' failed: %s", error->message); else { mm_obj_dbg (self, "file refresh registered using 'refresh register'"); ctx->refresh_file_supported = TRUE; } /* Go on to next step */ ctx->step++; setup_sim_hot_swap_step (task); } static void uim_refresh_register_file (GTask *task, guint16 file_id, const guint16 *file_path, gsize file_path_len) { MMSharedQmi *self; SetupSimHotSwapContext *ctx; QmiMessageUimRefreshRegisterInputInfoFilesElement file_element; guint8 val; gsize i; g_autoptr(QmiMessageUimRefreshRegisterInput) refresh_register_input = NULL; g_autoptr(GArray) placeholder_aid = NULL; g_autoptr(GArray) file = NULL; g_autoptr(GArray) file_element_path = NULL; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); /* This is the last resort if 'refresh register all' does not work. It works * on some older modems. Those modems may not also support QMI_UIM_SESSION_TYPE_CARD_SLOT_1 * so we'll use QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING */ mm_obj_dbg (self, "register for refresh file indication"); placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); file = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageUimRefreshRegisterInputInfoFilesElement), 1); file_element_path = g_array_sized_new (FALSE, FALSE, sizeof (guint8), file_path_len * 2); for (i = 0; i < file_path_len; ++i) { val = file_path[i] & 0xFF; g_array_append_val (file_element_path, val); val = (file_path[i] >> 8) & 0xFF; g_array_append_val (file_element_path, val); } memset (&file_element, 0, sizeof (file_element)); file_element.file_id = file_id; file_element.path = file_element_path; g_array_append_val (file, file_element); refresh_register_input = qmi_message_uim_refresh_register_input_new (); qmi_message_uim_refresh_register_input_set_info (refresh_register_input, TRUE, FALSE, file, NULL); qmi_message_uim_refresh_register_input_set_session (refresh_register_input, QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING, placeholder_aid, NULL); qmi_client_uim_refresh_register (QMI_CLIENT_UIM (ctx->uim_client), refresh_register_input, 10, NULL, (GAsyncReadyCallback) uim_refresh_register_file_ready, task); } /* Refresh registration and event handling. * This is used for detecting ICCID and IMSI change. */ static void uim_refresh_register_all_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) { g_autoptr(QmiMessageUimRefreshRegisterAllOutput) output = NULL; g_autoptr(GError) error = NULL; MMIfaceModem *self; SetupSimHotSwapContext *ctx; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); output = qmi_client_uim_refresh_register_all_finish (client, res, &error); if (!output || !qmi_message_uim_refresh_register_all_output_get_result (output, &error)) { mm_obj_dbg (self, "refresh register all operation failed: %s", error->message); } else { /* Jump to setup refresh indication signal */ mm_obj_dbg (self, "registered for all SIM refresh events"); ctx->refresh_all_supported = TRUE; } ctx->step++; setup_sim_hot_swap_step (task); } static void uim_check_get_slot_status_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) { g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; g_autoptr(GError) error = NULL; MMIfaceModem *self; SetupSimHotSwapContext *ctx; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); output = qmi_client_uim_get_slot_status_finish (client, res, &error); if (!output || !qmi_message_uim_get_slot_status_output_get_result (output, &error)) { mm_obj_dbg (self, "slot status retrieval failed: %s", error->message); } else { /* Go on to next step */ mm_obj_dbg (self, "slot status retrieval succeeded"); ctx->get_slot_status_supported = TRUE; } ctx->step++; setup_sim_hot_swap_step (task); } static void uim_register_events_ready (QmiClientUim *client, GAsyncResult *res, GTask *task) { g_autoptr(QmiMessageUimRegisterEventsOutput) output = NULL; g_autoptr(GError) error = NULL; MMIfaceModem *self; SetupSimHotSwapContext *ctx; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); /* If event registration fails, go on with initialization. In that case * we cannot use slot status indications to detect eUICC profile switches. */ output = qmi_client_uim_register_events_finish (client, res, &error); if (!output || !qmi_message_uim_register_events_output_get_result (output, &error)) { mm_obj_dbg (self, "not registered for slot status indications: %s", error->message); } else { mm_obj_dbg (self, "registered for slot status indications"); ctx->register_slot_status_supported = TRUE; } /* Go on to next step */ ctx->step++; setup_sim_hot_swap_step (task); } static void setup_sim_hot_swap_step (GTask *task) { MMSharedQmi *self; Private *priv; SetupSimHotSwapContext *ctx; self = g_task_get_source_object (task); priv = get_private (MM_SHARED_QMI (self)); ctx = g_task_get_task_data (task); switch (ctx->step) { case SETUP_SIM_HOT_SWAP_STEP_FIRST: ctx->step++; /* fall-through */ case SETUP_SIM_HOT_SWAP_STEP_UIM_REGISTER_SLOT_STATUS: { g_autoptr(QmiMessageUimRegisterEventsInput) register_events_input = NULL; mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for slot status indications...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); register_events_input = qmi_message_uim_register_events_input_new (); qmi_message_uim_register_events_input_set_event_registration_mask (register_events_input, QMI_UIM_EVENT_REGISTRATION_FLAG_PHYSICAL_SLOT_STATUS, NULL); qmi_client_uim_register_events (QMI_CLIENT_UIM (ctx->uim_client), register_events_input, 10, NULL, (GAsyncReadyCallback) uim_register_events_ready, task); return; } case SETUP_SIM_HOT_SWAP_STEP_UIM_CHECK_SLOT_STATUS: if (ctx->register_slot_status_supported) { mm_obj_dbg (self, "setup SIM hot swap (%u/%u): checking slot status support...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); /* Successful registration does not mean that the modem actually sends * physical slot status indications; invoke Get Slot Status to find out if * the modem really supports slot status. */ qmi_client_uim_get_slot_status (QMI_CLIENT_UIM (ctx->uim_client), NULL, 10, NULL, (GAsyncReadyCallback) uim_check_get_slot_status_ready, task); return; } mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to check for slot status support...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); ctx->step++; /* fall-through */ case SETUP_SIM_HOT_SWAP_STEP_UIM_SLOT_STATUS_INDICATION: if (ctx->register_slot_status_supported && ctx->get_slot_status_supported) { mm_obj_dbg (self, "setup SIM hot swap (%u/%u): monitoring slot status indications...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); ctx->uim_slot_status_indication_id = g_signal_connect (ctx->uim_client, "slot-status", G_CALLBACK (uim_slot_status_indication_cb), self); } else mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to monitor for slot status indications...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); ctx->step++; /* fall-through */ case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ALL: { g_autoptr(QmiMessageUimRefreshRegisterAllInput) refresh_register_all_input = NULL; g_autoptr(GArray) placeholder_aid = NULL; mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for all refresh events...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); refresh_register_all_input = qmi_message_uim_refresh_register_all_input_new (); qmi_message_uim_refresh_register_all_input_set_info (refresh_register_all_input, TRUE, NULL); qmi_message_uim_refresh_register_all_input_set_session (refresh_register_all_input, QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING, placeholder_aid, NULL); qmi_client_uim_refresh_register_all (QMI_CLIENT_UIM (ctx->uim_client), refresh_register_all_input, 10, NULL, (GAsyncReadyCallback) uim_refresh_register_all_ready, task); return; } case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ICCID: /* If refresh all not supported, register for a single file refresh notification */ if (!ctx->refresh_all_supported) { const guint16 file_path[] = { 0x3F00 }; mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for SIM ICCID refresh events...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); uim_refresh_register_file (task, 0x2FE2, file_path, G_N_ELEMENTS (file_path)); return; } mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to register for SIM ICCID refresh events", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); ctx->step++; /* fall-through */ case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_IMSI: /* If refresh all not supported, register for a single file refresh notification */ if (!ctx->refresh_all_supported) { const guint16 file_path[] = { 0x3F00, 0x7FFF }; mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for SIM IMSI refresh events...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); uim_refresh_register_file (task, 0x6F07, file_path, G_N_ELEMENTS (file_path)); return; } mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to register for SIM IMSI refresh events...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); ctx->step++; /* fall-through */ case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_INDICATION: if (ctx->refresh_all_supported || ctx->refresh_file_supported) { mm_obj_dbg (self, "setup SIM hot swap (%u/%u): monitoring refresh indications...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); ctx->uim_refresh_indication_id = g_signal_connect (ctx->uim_client, "refresh", G_CALLBACK (uim_refresh_indication_cb), self); } else mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to monitor for refresh indications...", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); ctx->step++; /* fall-through */ case SETUP_SIM_HOT_SWAP_STEP_LAST: if (ctx->refresh_all_supported || ctx->refresh_file_supported || (ctx->register_slot_status_supported && ctx->get_slot_status_supported)) { /* at least one method was supported, transfer state to the private * info and return success */ g_assert (!priv->uim_client); priv->uim_client = g_steal_pointer (&ctx->uim_client); if (ctx->uim_slot_status_indication_id) { g_assert (!priv->uim_slot_status_indication_id); priv->uim_slot_status_indication_id = ctx->uim_slot_status_indication_id; ctx->uim_slot_status_indication_id = 0; } if (ctx->uim_refresh_indication_id) { g_assert (!priv->uim_refresh_indication_id); priv->uim_refresh_indication_id = ctx->uim_refresh_indication_id; ctx->uim_refresh_indication_id = 0; } mm_obj_dbg (self, "setup SIM hot swap (%u/%u): successfully finished", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); g_task_return_boolean (task, TRUE); } else { mm_obj_dbg (self, "setup SIM hot swap (%u/%u): failed", ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't setup SIM hot swap"); } g_object_unref (task); return; default: g_assert_not_reached (); } } void mm_shared_qmi_setup_sim_hot_swap (MMIfaceModem *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; QmiClient *client = NULL; SetupSimHotSwapContext *ctx; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_UIM, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); ctx = g_slice_new0 (SetupSimHotSwapContext); ctx->step = SETUP_SIM_HOT_SWAP_STEP_FIRST; ctx->uim_client = g_object_ref (client); g_task_set_task_data (task, ctx, (GDestroyNotify)setup_sim_hot_swap_context_free); setup_sim_hot_swap_step (task); } /*****************************************************************************/ /* Set packet service state (3GPP interface) */ typedef struct { QmiClientNas *client; MMModem3gppPacketServiceState packet_service_state; } SetPacketServiceStateContext; static void set_packet_service_state_context_free (SetPacketServiceStateContext *ctx) { g_object_unref (ctx->client); g_slice_free (SetPacketServiceStateContext, ctx); } gboolean mm_shared_qmi_set_packet_service_state_finish (MMIfaceModem3gpp *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void set_packet_service_state_ia_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessageNasAttachDetachOutput) output = NULL; output = qmi_client_nas_attach_detach_finish (client, res, &error); if ((!output || !qmi_message_nas_attach_detach_output_get_result (output, &error)) && !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { g_prefix_error (&error, "Couldn't set packet service state: "); g_task_return_error (task, g_steal_pointer (&error)); } else g_task_return_boolean (task, TRUE); g_object_unref (task); } static void set_packet_service_state_ia (GTask *task) { g_autoptr(QmiMessageNasAttachDetachInput) input = NULL; SetPacketServiceStateContext *ctx; input = qmi_message_nas_attach_detach_input_new (); ctx = g_task_get_task_data (task); if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) qmi_message_nas_attach_detach_input_set_action (input, QMI_NAS_PS_ATTACH_ACTION_ATTACH, NULL); else if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED) qmi_message_nas_attach_detach_input_set_action (input, QMI_NAS_PS_ATTACH_ACTION_DETACH, NULL); else g_assert_not_reached (); qmi_client_nas_attach_detach ( ctx->client, input, 5, NULL, (GAsyncReadyCallback)set_packet_service_state_ia_ready, task); } static void set_packet_service_state_sssp_ready (QmiClientNas *client, GAsyncResult *res, GTask *task) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessageNasSetSystemSelectionPreferenceOutput) output = NULL; output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); if ((!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) && !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { g_prefix_error (&error, "Couldn't set packet service state: "); g_task_return_error (task, g_steal_pointer (&error)); } else g_task_return_boolean (task, TRUE); g_object_unref (task); } static void set_packet_service_state_sssp (GTask *task) { g_autoptr(QmiMessageNasSetSystemSelectionPreferenceInput) input = NULL; SetPacketServiceStateContext *ctx; input = qmi_message_nas_set_system_selection_preference_input_new (); ctx = g_task_get_task_data (task); if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) qmi_message_nas_set_system_selection_preference_input_set_service_domain_preference ( input, QMI_NAS_SERVICE_DOMAIN_PREFERENCE_PS_ATTACH, NULL); else if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED) qmi_message_nas_set_system_selection_preference_input_set_service_domain_preference ( input, QMI_NAS_SERVICE_DOMAIN_PREFERENCE_PS_DETACH, NULL); else g_assert_not_reached (); qmi_client_nas_set_system_selection_preference ( ctx->client, input, 5, NULL, (GAsyncReadyCallback)set_packet_service_state_sssp_ready, task); } void mm_shared_qmi_set_packet_service_state (MMIfaceModem3gpp *self, MMModem3gppPacketServiceState packet_service_state, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; SetPacketServiceStateContext *ctx; QmiClient *client = NULL; Private *priv = NULL; g_assert ((packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) || (packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED)); /* Get NAS client */ if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_NAS, &client, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); ctx = g_slice_new0 (SetPacketServiceStateContext); ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); ctx->packet_service_state = packet_service_state; g_task_set_task_data (task, ctx, (GDestroyNotify)set_packet_service_state_context_free); priv = get_private (MM_SHARED_QMI (self)); if (priv->feature_nas_ssp == FEATURE_SUPPORTED) set_packet_service_state_sssp (task); else set_packet_service_state_ia (task); } /*****************************************************************************/ /* Location: Set SUPL server */ typedef struct { QmiClient *client; gchar *supl; glong indication_id; guint timeout_id; } SetSuplServerContext; static void set_supl_server_context_free (SetSuplServerContext *ctx) { if (ctx->client) { if (ctx->timeout_id) g_source_remove (ctx->timeout_id); if (ctx->indication_id) g_signal_handler_disconnect (ctx->client, ctx->indication_id); g_object_unref (ctx->client); } g_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_dbg (self, "injecting xtra data: %" G_GSIZE_FORMAT " bytes (%u/%u)", count, (guint) ctx->n_part, (guint) ctx->total_parts); qmi_client_loc_inject_xtra_data (ctx->client, input, 10, NULL, (GAsyncReadyCallback) inject_xtra_data_ready, task); qmi_message_loc_inject_xtra_data_input_unref (input); } static void inject_xtra_data (GTask *task) { InjectAssistanceDataContext *ctx; ctx = g_task_get_task_data (task); g_assert (ctx->timeout_id == 0); g_assert (ctx->indication_id == 0); ctx->n_part = 0; ctx->i = 0; inject_xtra_data_next (task); } static void inject_assistance_data_next (GTask *task); static void loc_location_inject_predicted_orbits_data_indication_cb (QmiClientLoc *client, QmiIndicationLocInjectPredictedOrbitsDataOutput *output, GTask *task) { InjectAssistanceDataContext *ctx; QmiLocIndicationStatus status; GError *error = NULL; if (!qmi_indication_loc_inject_predicted_orbits_data_output_get_indication_status (output, &status, &error)) { g_prefix_error (&error, "QMI operation failed: "); goto out; } mm_error_from_qmi_loc_indication_status (status, &error); out: if (error) { g_task_return_error (task, error); g_object_unref (task); return; } ctx = g_task_get_task_data (task); g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; g_signal_handler_disconnect (ctx->client, ctx->indication_id); ctx->indication_id = 0; inject_assistance_data_next (task); } static void inject_predicted_orbits_data_ready (QmiClientLoc *client, GAsyncResult *res, GTask *task) { QmiMessageLocInjectPredictedOrbitsDataOutput *output; InjectAssistanceDataContext *ctx; GError *error = NULL; ctx = g_task_get_task_data (task); output = qmi_client_loc_inject_predicted_orbits_data_finish (client, res, &error); if (!output || !qmi_message_loc_inject_predicted_orbits_data_output_get_result (output, &error)) { /* Try with InjectXtra if InjectPredictedOrbits is unsupported */ if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED)) { g_error_free (error); inject_xtra_data (task); goto out; } g_prefix_error (&error, "QMI operation failed: "); g_task_return_error (task, error); g_object_unref (task); goto out; } /* The task ownership is shared between signal and timeout; the one which is * scheduled first will cancel the other. */ ctx->indication_id = g_signal_connect (ctx->client, "inject-predicted-orbits-data", G_CALLBACK (loc_location_inject_predicted_orbits_data_indication_cb), task); ctx->timeout_id = g_timeout_add_seconds (10, (GSourceFunc)loc_location_inject_data_indication_timed_out, task); out: if (output) qmi_message_loc_inject_predicted_orbits_data_output_unref (output); } static void inject_assistance_data_next (GTask *task) { MMSharedQmi *self; QmiMessageLocInjectPredictedOrbitsDataInput *input; InjectAssistanceDataContext *ctx; goffset total_bytes_left; gsize count; GArray *data; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); g_assert (ctx->data_size >= ctx->i); total_bytes_left = ctx->data_size - ctx->i; if (total_bytes_left == 0) { g_task_return_boolean (task, TRUE); g_object_unref (task); return; } ctx->n_part++; count = (total_bytes_left >= ctx->part_size) ? ctx->part_size : total_bytes_left; input = qmi_message_loc_inject_predicted_orbits_data_input_new (); qmi_message_loc_inject_predicted_orbits_data_input_set_format_type ( input, QMI_LOC_PREDICTED_ORBITS_DATA_FORMAT_XTRA, NULL); qmi_message_loc_inject_predicted_orbits_data_input_set_total_size ( input, (guint32)ctx->data_size, NULL); qmi_message_loc_inject_predicted_orbits_data_input_set_total_parts ( input, (guint16)ctx->total_parts, NULL); qmi_message_loc_inject_predicted_orbits_data_input_set_part_number ( input, (guint16)ctx->n_part, NULL); data = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), count), &(ctx->data[ctx->i]), count); qmi_message_loc_inject_predicted_orbits_data_input_set_part_data ( input, data, NULL); g_array_unref (data); ctx->i += count; mm_obj_dbg (self, "injecting predicted orbits data: %" G_GSIZE_FORMAT " bytes (%u/%u)", count, (guint) ctx->n_part, (guint) ctx->total_parts); qmi_client_loc_inject_predicted_orbits_data (ctx->client, input, 10, NULL, (GAsyncReadyCallback) inject_predicted_orbits_data_ready, task); qmi_message_loc_inject_predicted_orbits_data_input_unref (input); } void mm_shared_qmi_location_inject_assistance_data (MMIfaceModemLocation *self, const guint8 *data, gsize data_size, GAsyncReadyCallback callback, gpointer user_data) { InjectAssistanceDataContext *ctx; QmiClient *client; GTask *task; Private *priv; if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), QMI_SERVICE_LOC, &client, callback, user_data)) return; priv = get_private (MM_SHARED_QMI (self)); task = g_task_new (self, NULL, callback, user_data); ctx = g_slice_new0 (InjectAssistanceDataContext); ctx->client = QMI_CLIENT_LOC (g_object_ref (client)); ctx->data = g_memdup (data, data_size); ctx->data_size = data_size; ctx->part_size = ((priv->loc_assistance_data_max_part_size > 0) ? priv->loc_assistance_data_max_part_size : MAX_BYTES_PER_REQUEST); g_task_set_task_data (task, ctx, (GDestroyNotify) inject_assistance_data_context_free); if ((ctx->data_size > (G_MAXUINT16 * ctx->part_size)) || ((priv->loc_assistance_data_max_file_size > 0) && (ctx->data_size > priv->loc_assistance_data_max_file_size))) { g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_TOO_MANY, "Assistance data file is too big"); g_object_unref (task); return; } ctx->total_parts = (ctx->data_size / ctx->part_size); if (ctx->data_size % ctx->part_size) ctx->total_parts++; g_assert (ctx->total_parts <= G_MAXUINT16); mm_obj_dbg (self, "injecting gpsOneXTRA data (%" G_GOFFSET_FORMAT " bytes)...", ctx->data_size); inject_assistance_data_next (task); } /*****************************************************************************/ QmiClient * mm_shared_qmi_peek_client (MMSharedQmi *self, QmiService service, MMPortQmiFlag flag, GError **error) { g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_client); return MM_SHARED_QMI_GET_INTERFACE (self)->peek_client (self, service, flag, error); } gboolean mm_shared_qmi_ensure_client (MMSharedQmi *self, QmiService service, QmiClient **o_client, GAsyncReadyCallback callback, gpointer user_data) { GError *error = NULL; QmiClient *client; client = mm_shared_qmi_peek_client (self, service, MM_PORT_QMI_FLAG_DEFAULT, &error); if (!client) { g_task_report_error (self, callback, user_data, mm_shared_qmi_ensure_client, error); return FALSE; } *o_client = client; return TRUE; } static void shared_qmi_init (gpointer g_iface) { } GType mm_shared_qmi_get_type (void) { static GType shared_qmi_type = 0; if (!G_UNLIKELY (shared_qmi_type)) { static const GTypeInfo info = { sizeof (MMSharedQmi), /* class_size */ shared_qmi_init, /* base_init */ NULL, /* base_finalize */ }; shared_qmi_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedQmi", &info, 0); g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM); g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM_3GPP); g_type_interface_add_prerequisite (shared_qmi_type, MM_TYPE_IFACE_MODEM_LOCATION); } return shared_qmi_type; }