| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details: |
| * |
| * Copyright (C) 2018 Aleksander Morgado <aleksander@aleksander.es> |
| * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. |
| */ |
| |
| #include <config.h> |
| #include <string.h> |
| #include <arpa/inet.h> |
| |
| #include <glib-object.h> |
| #include <gio/gio.h> |
| |
| #define _LIBMM_INSIDE_MM |
| #include <libmm-glib.h> |
| |
| #include <libqmi-glib.h> |
| |
| #include "mm-log-object.h" |
| #include "mm-iface-modem.h" |
| #include "mm-iface-modem-3gpp.h" |
| #include "mm-iface-modem-location.h" |
| #include "mm-sim-qmi.h" |
| #include "mm-shared-qmi.h" |
| #include "mm-modem-helpers-qmi.h" |
| |
| /* Default session id to use in LOC operations */ |
| #define DEFAULT_LOC_SESSION_ID 0x10 |
| |
| /* Default description for the default configuration of the firmware */ |
| #define DEFAULT_CONFIG_DESCRIPTION "default" |
| |
| /*****************************************************************************/ |
| /* Private data context */ |
| |
| #define PRIVATE_TAG "shared-qmi-private-tag" |
| static GQuark private_quark; |
| |
| typedef enum { |
| FEATURE_UNKNOWN, |
| FEATURE_UNSUPPORTED, |
| FEATURE_SUPPORTED, |
| } Feature; |
| |
| typedef struct { |
| GArray *id; |
| QmiPdcConfigurationType config_type; |
| guint32 token; |
| guint32 version; |
| gchar *description; |
| guint32 total_size; |
| } ConfigInfo; |
| |
| static void |
| config_info_clear (ConfigInfo *config_info) |
| { |
| g_array_unref (config_info->id); |
| g_free (config_info->description); |
| } |
| |
| typedef struct { |
| /* Capabilities & modes helpers */ |
| gboolean multimode; |
| MMModemCapability current_capabilities; |
| GArray *supported_radio_interfaces; |
| Feature feature_nas_tp; |
| Feature feature_nas_ssp; |
| Feature feature_nas_ssp_extended_lte_band_preference; |
| Feature feature_nas_ssp_acquisition_order_preference; |
| GArray *feature_nas_ssp_acquisition_order_preference_array; |
| GArray *supported_bands; |
| |
| /* Location helpers */ |
| MMIfaceModemLocationInterface *iface_modem_location_parent; |
| MMModemLocationSource enabled_sources; |
| QmiClient *pds_client; |
| gulong pds_location_event_report_indication_id; |
| QmiClient *loc_client; |
| gulong loc_location_nmea_indication_id; |
| gchar **loc_assistance_data_servers; |
| guint32 loc_assistance_data_max_file_size; |
| guint32 loc_assistance_data_max_part_size; |
| |
| /* 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 switching |
| * capabilities. |
| */ |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_NAS, &nas_client, |
| callback, user_data)) |
| return; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_DMS, &dms_client, |
| callback, user_data)) |
| return; |
| |
| /* Current capabilities is the first thing run, and will only be run once per modem, |
| * so we should here check support for the optional features. */ |
| priv = get_private (MM_SHARED_QMI (self)); |
| g_assert (priv->feature_nas_tp == FEATURE_UNKNOWN); |
| g_assert (priv->feature_nas_ssp == FEATURE_UNKNOWN); |
| |
| ctx = g_slice_new0 (LoadCurrentCapabilitiesContext); |
| ctx->nas_client = QMI_CLIENT_NAS (g_object_ref (nas_client)); |
| ctx->dms_client = QMI_CLIENT_DMS (g_object_ref (dms_client)); |
| ctx->step = LOAD_CURRENT_CAPABILITIES_STEP_FIRST; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)load_current_capabilities_context_free); |
| |
| load_current_capabilities_step (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Supported capabilities (Modem interface) */ |
| |
| GArray * |
| mm_shared_qmi_load_supported_capabilities_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| void |
| mm_shared_qmi_load_supported_capabilities (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| Private *priv; |
| GArray *supported_combinations; |
| guint i; |
| MMQmiSupportedCapabilitiesContext ctx = { 0 }; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* List of radio interfaces preloaded in current capabilities */ |
| priv = get_private (MM_SHARED_QMI (self)); |
| if (!priv->supported_radio_interfaces) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "cannot load current capabilities without radio interface information"); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Build mask with all supported capabilities */ |
| ctx.dms_capabilities = MM_MODEM_CAPABILITY_NONE; |
| for (i = 0; i < priv->supported_radio_interfaces->len; i++) |
| ctx.dms_capabilities |= mm_modem_capability_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self); |
| |
| ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED); |
| ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED); |
| ctx.multimode = priv->multimode; |
| |
| /* Build list of supported combinations */ |
| supported_combinations = mm_supported_capabilities_from_qmi_supported_capabilities_context (&ctx, self); |
| g_task_return_pointer (task, supported_combinations, (GDestroyNotify) g_array_unref); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load model (Modem interface) */ |
| |
| gchar * |
| mm_shared_qmi_load_model_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| dms_get_model_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsGetModelOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_get_model_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_get_model_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't get Model: "); |
| g_task_return_error (task, error); |
| } else { |
| const gchar *str; |
| |
| qmi_message_dms_get_model_output_get_model (output, &str, NULL); |
| g_task_return_pointer (task, g_strdup (str), g_free); |
| } |
| |
| if (output) |
| qmi_message_dms_get_model_output_unref (output); |
| |
| g_object_unref (task); |
| } |
| |
| void |
| mm_shared_qmi_load_model (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| QmiClient *client = NULL; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_DMS, &client, |
| callback, user_data)) |
| return; |
| |
| mm_obj_dbg (self, "loading model..."); |
| qmi_client_dms_get_model (QMI_CLIENT_DMS (client), |
| NULL, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)dms_get_model_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| /*****************************************************************************/ |
| /* Allowed modes setting (Modem interface) */ |
| |
| typedef struct { |
| QmiClientNas *client; |
| MMModemMode allowed; |
| MMModemMode preferred; |
| } SetCurrentModesContext; |
| |
| static void |
| set_current_modes_context_free (SetCurrentModesContext *ctx) |
| { |
| g_object_unref (ctx->client); |
| g_slice_free (SetCurrentModesContext, ctx); |
| } |
| |
| gboolean |
| mm_shared_qmi_set_current_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| set_current_modes_technology_preference_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageNasSetTechnologyPreferenceOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_nas_set_technology_preference_finish (client, res, &error); |
| if (!output || |
| (!qmi_message_nas_set_technology_preference_output_get_result (output, &error) && |
| !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT))) { |
| g_task_return_error (task, error); |
| } else { |
| g_clear_error (&error); |
| g_task_return_boolean (task, TRUE); |
| } |
| g_object_unref (task); |
| |
| if (output) |
| qmi_message_nas_set_technology_preference_output_unref (output); |
| } |
| |
| static void |
| set_current_modes_technology_preference (GTask *task) |
| { |
| MMIfaceModem *self; |
| SetCurrentModesContext *ctx; |
| QmiMessageNasSetTechnologyPreferenceInput *input; |
| QmiNasRadioTechnologyPreference pref; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| if (ctx->preferred != MM_MODEM_MODE_NONE) { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Cannot set specific preferred mode"); |
| g_object_unref (task); |
| return; |
| } |
| |
| pref = mm_modem_mode_to_qmi_radio_technology_preference (ctx->allowed, mm_iface_modem_is_cdma (self)); |
| if (!pref) { |
| gchar *str; |
| |
| str = mm_modem_mode_build_string_from_mask (ctx->allowed); |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Unhandled allowed mode setting: '%s'", |
| str); |
| g_object_unref (task); |
| g_free (str); |
| return; |
| } |
| |
| input = qmi_message_nas_set_technology_preference_input_new (); |
| qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL); |
| |
| qmi_client_nas_set_technology_preference ( |
| ctx->client, |
| input, |
| 5, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)set_current_modes_technology_preference_ready, |
| task); |
| qmi_message_nas_set_technology_preference_input_unref (input); |
| } |
| |
| static void |
| set_current_modes_system_selection_preference_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); |
| if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| |
| if (output) |
| qmi_message_nas_set_system_selection_preference_output_unref (output); |
| } |
| |
| static void |
| set_current_modes_system_selection_preference (GTask *task) |
| { |
| MMIfaceModem *self; |
| Private *priv; |
| SetCurrentModesContext *ctx; |
| QmiMessageNasSetSystemSelectionPreferenceInput *input; |
| QmiNasRatModePreference pref; |
| |
| self = g_task_get_source_object (task); |
| priv = get_private (MM_SHARED_QMI (self)); |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_nas_set_system_selection_preference_input_new (); |
| qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL); |
| |
| /* Preferred modes */ |
| |
| if (ctx->preferred != MM_MODEM_MODE_NONE) { |
| if (priv->feature_nas_ssp_acquisition_order_preference == FEATURE_SUPPORTED) { |
| GArray *array; |
| |
| /* Acquisition order array */ |
| array = mm_modem_mode_to_qmi_acquisition_order_preference (ctx->allowed, |
| ctx->preferred, |
| priv->feature_nas_ssp_acquisition_order_preference_array); |
| g_assert (array); |
| qmi_message_nas_set_system_selection_preference_input_set_acquisition_order_preference (input, array, NULL); |
| g_array_unref (array); |
| } |
| |
| /* Only set GSM/WCDMA acquisition order preference if both 2G and 3G given as allowed */ |
| if (mm_iface_modem_is_3gpp (self) && ((ctx->allowed & (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G))) { |
| QmiNasGsmWcdmaAcquisitionOrderPreference order; |
| |
| order = mm_modem_mode_to_qmi_gsm_wcdma_acquisition_order_preference (ctx->preferred, self); |
| qmi_message_nas_set_system_selection_preference_input_set_gsm_wcdma_acquisition_order_preference (input, order, NULL); |
| } |
| } |
| |
| /* Allowed modes */ |
| pref = mm_modem_mode_to_qmi_rat_mode_preference (ctx->allowed, |
| mm_iface_modem_is_cdma (self), |
| mm_iface_modem_is_3gpp (self)); |
| qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL); |
| |
| qmi_client_nas_set_system_selection_preference ( |
| ctx->client, |
| input, |
| 5, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)set_current_modes_system_selection_preference_ready, |
| task); |
| qmi_message_nas_set_system_selection_preference_input_unref (input); |
| } |
| |
| void |
| mm_shared_qmi_set_current_modes (MMIfaceModem *self, |
| MMModemMode allowed, |
| MMModemMode preferred, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| SetCurrentModesContext *ctx; |
| GTask *task; |
| QmiClient *client = NULL; |
| Private *priv; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_NAS, &client, |
| callback, user_data)) |
| return; |
| |
| ctx = g_slice_new0 (SetCurrentModesContext); |
| ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); |
| |
| if (allowed == MM_MODEM_MODE_ANY && ctx->preferred == MM_MODEM_MODE_NONE) { |
| ctx->allowed = MM_MODEM_MODE_NONE; |
| if (mm_iface_modem_is_2g (self)) |
| ctx->allowed |= MM_MODEM_MODE_2G; |
| if (mm_iface_modem_is_3g (self)) |
| ctx->allowed |= MM_MODEM_MODE_3G; |
| if (mm_iface_modem_is_4g (self)) |
| ctx->allowed |= MM_MODEM_MODE_4G; |
| if (mm_iface_modem_is_5g (self)) |
| ctx->allowed |= MM_MODEM_MODE_5G; |
| ctx->preferred = MM_MODEM_MODE_NONE; |
| } else { |
| ctx->allowed = allowed; |
| ctx->preferred = preferred; |
| } |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_modes_context_free); |
| |
| priv = get_private (MM_SHARED_QMI (self)); |
| |
| if (priv->feature_nas_ssp == FEATURE_SUPPORTED) { |
| set_current_modes_system_selection_preference (task); |
| return; |
| } |
| |
| if (priv->feature_nas_tp == FEATURE_SUPPORTED) { |
| set_current_modes_technology_preference (task); |
| return; |
| } |
| |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Setting allowed modes is not supported by this device"); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load current modes (Modem interface) */ |
| |
| typedef struct { |
| QmiClientNas *client; |
| } LoadCurrentModesContext; |
| |
| typedef struct { |
| MMModemMode allowed; |
| MMModemMode preferred; |
| } LoadCurrentModesResult; |
| |
| static void |
| load_current_modes_context_free (LoadCurrentModesContext *ctx) |
| { |
| g_object_unref (ctx->client); |
| g_free (ctx); |
| } |
| |
| gboolean |
| mm_shared_qmi_load_current_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| MMModemMode *allowed, |
| MMModemMode *preferred, |
| GError **error) |
| { |
| LoadCurrentModesResult *result; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| if (!result) |
| return FALSE; |
| |
| *allowed = result->allowed; |
| *preferred = result->preferred; |
| g_free (result); |
| return TRUE; |
| } |
| |
| static MMModemMode |
| filter_modes_by_supported_radio_interfaces (MMSharedQmi *self, |
| GArray *supported_radio_interfaces, |
| MMModemMode allowed_modes) |
| { |
| MMModemMode modem_modes; |
| guint i; |
| |
| modem_modes = MM_MODEM_MODE_NONE; |
| |
| for (i = 0; i < supported_radio_interfaces->len; i++) |
| modem_modes |= mm_modem_mode_from_qmi_radio_interface (g_array_index (supported_radio_interfaces, QmiDmsRadioInterface, i), self); |
| |
| return allowed_modes & modem_modes; |
| } |
| |
| static void |
| get_technology_preference_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| Private *priv; |
| LoadCurrentModesResult *result = NULL; |
| QmiMessageNasGetTechnologyPreferenceOutput *output = NULL; |
| GError *error = NULL; |
| MMModemMode allowed; |
| QmiNasRadioTechnologyPreference preference_mask; |
| |
| self = g_task_get_source_object (task); |
| priv = get_private (self); |
| |
| output = qmi_client_nas_get_technology_preference_finish (client, res, &error); |
| if (!output || !qmi_message_nas_get_technology_preference_output_get_result (output, &error)) { |
| g_task_return_error (task, error); |
| goto out; |
| } |
| |
| qmi_message_nas_get_technology_preference_output_get_active ( |
| output, |
| &preference_mask, |
| NULL, /* duration */ |
| NULL); |
| allowed = mm_modem_mode_from_qmi_radio_technology_preference (preference_mask); |
| if (allowed == MM_MODEM_MODE_NONE) { |
| gchar *str; |
| |
| str = qmi_nas_radio_technology_preference_build_string_from_mask (preference_mask); |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Unsupported modes reported: '%s'", str); |
| g_free (str); |
| goto out; |
| } |
| |
| g_assert (priv->supported_radio_interfaces); |
| allowed = filter_modes_by_supported_radio_interfaces (self, priv->supported_radio_interfaces, allowed); |
| |
| /* We got a valid value from here */ |
| result = g_new (LoadCurrentModesResult, 1); |
| result->allowed = allowed; |
| result->preferred = MM_MODEM_MODE_NONE; |
| g_task_return_pointer (task, result, g_free); |
| |
| out: |
| if (output) |
| qmi_message_nas_get_technology_preference_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| load_current_modes_technology_preference (GTask *task) |
| { |
| LoadCurrentModesContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| |
| qmi_client_nas_get_technology_preference ( |
| ctx->client, |
| NULL, /* no input */ |
| 5, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)get_technology_preference_ready, |
| task); |
| } |
| |
| static void |
| load_current_modes_system_selection_preference_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| Private *priv; |
| LoadCurrentModesResult *result = NULL; |
| QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; |
| GError *error = NULL; |
| QmiNasRatModePreference mode_preference_mask = 0; |
| MMModemMode allowed; |
| |
| self = g_task_get_source_object (task); |
| priv = get_private (self); |
| |
| output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); |
| if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) { |
| g_task_return_error (task, error); |
| goto out; |
| } |
| |
| if (!qmi_message_nas_get_system_selection_preference_output_get_mode_preference ( |
| output, |
| &mode_preference_mask, |
| NULL)) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Mode preference not reported in system selection preference"); |
| goto out; |
| } |
| |
| allowed = mm_modem_mode_from_qmi_rat_mode_preference (mode_preference_mask); |
| if (allowed == MM_MODEM_MODE_NONE) { |
| gchar *str; |
| |
| str = qmi_nas_rat_mode_preference_build_string_from_mask (mode_preference_mask); |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Unsupported modes reported: '%s'", str); |
| g_free (str); |
| goto out; |
| } |
| |
| g_assert (priv->supported_radio_interfaces); |
| allowed = filter_modes_by_supported_radio_interfaces (self, priv->supported_radio_interfaces, allowed); |
| |
| /* We got a valid value from here */ |
| result = g_new (LoadCurrentModesResult, 1); |
| result->allowed = allowed; |
| result->preferred = MM_MODEM_MODE_NONE; |
| |
| /* If acquisition order preference is available, always use that first */ |
| if (priv->feature_nas_ssp_acquisition_order_preference == FEATURE_SUPPORTED) { |
| GArray *array; |
| |
| if (qmi_message_nas_get_system_selection_preference_output_get_acquisition_order_preference (output, &array, NULL) && |
| array->len > 0) { |
| guint i; |
| |
| /* The array of preference contains the preference of the full list of supported |
| * access technologies, regardless of whether they're enabled or not. So, look for |
| * the first one that is flagged as enabled, not just the first one in the array. |
| */ |
| for (i = 0; i < array->len; i++) { |
| MMModemMode mode; |
| |
| mode = mm_modem_mode_from_qmi_nas_radio_interface (g_array_index (array, QmiNasRadioInterface, i)); |
| if (allowed == mode) |
| break; |
| if (allowed & mode) { |
| result->preferred = mode; |
| break; |
| } |
| } |
| } |
| } |
| /* For 2G+3G only rely on the GSM/WCDMA acquisition order preference TLV */ |
| else if (mode_preference_mask == (QMI_NAS_RAT_MODE_PREFERENCE_GSM | QMI_NAS_RAT_MODE_PREFERENCE_UMTS)) { |
| QmiNasGsmWcdmaAcquisitionOrderPreference gsm_or_wcdma; |
| |
| if (qmi_message_nas_get_system_selection_preference_output_get_gsm_wcdma_acquisition_order_preference ( |
| output, |
| &gsm_or_wcdma, |
| NULL)) |
| result->preferred = mm_modem_mode_from_qmi_gsm_wcdma_acquisition_order_preference (gsm_or_wcdma, self); |
| } |
| |
| g_task_return_pointer (task, result, g_free); |
| |
| out: |
| if (output) |
| qmi_message_nas_get_system_selection_preference_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| load_current_modes_system_selection_preference (GTask *task) |
| { |
| LoadCurrentModesContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| qmi_client_nas_get_system_selection_preference ( |
| ctx->client, |
| NULL, /* no input */ |
| 5, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)load_current_modes_system_selection_preference_ready, |
| task); |
| } |
| |
| void |
| mm_shared_qmi_load_current_modes (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| Private *priv; |
| LoadCurrentModesContext *ctx; |
| GTask *task; |
| QmiClient *client = NULL; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_NAS, &client, |
| callback, user_data)) |
| return; |
| |
| ctx = g_new0 (LoadCurrentModesContext, 1); |
| ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)load_current_modes_context_free); |
| |
| priv = get_private (MM_SHARED_QMI (self)); |
| |
| if (priv->feature_nas_ssp != FEATURE_UNSUPPORTED) { |
| load_current_modes_system_selection_preference (task); |
| return; |
| } |
| |
| if (priv->feature_nas_tp != FEATURE_UNSUPPORTED) { |
| load_current_modes_technology_preference (task); |
| return; |
| } |
| |
| /* Default to supported */ |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Loading current modes is not supported by this device"); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Supported modes (Modem interface) */ |
| |
| GArray * |
| mm_shared_qmi_load_supported_modes_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| void |
| mm_shared_qmi_load_supported_modes (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| Private *priv; |
| MMQmiSupportedModesContext ctx = { 0 }; |
| guint i; |
| GArray *combinations; |
| GError *error = NULL; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| priv = get_private (MM_SHARED_QMI (self)); |
| g_assert (priv->supported_radio_interfaces); |
| g_assert (priv->current_capabilities != MM_MODEM_CAPABILITY_NONE); |
| |
| /* Build all, based on the supported radio interfaces */ |
| ctx.all = MM_MODEM_MODE_NONE; |
| for (i = 0; i < priv->supported_radio_interfaces->len; i++) |
| ctx.all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self); |
| |
| /* Filter out those unsupported by the current capabilities */ |
| if (!(priv->current_capabilities & (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO))) |
| ctx.all &= ~(MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); |
| else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_LTE)) |
| ctx.all &= ~MM_MODEM_MODE_4G; |
| else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_5GNR)) |
| ctx.all &= ~MM_MODEM_MODE_5G; |
| |
| ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED); |
| ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED); |
| ctx.current_capabilities = priv->current_capabilities; |
| ctx.multimode = priv->multimode; |
| |
| combinations = mm_supported_modes_from_qmi_supported_modes_context (&ctx, self, &error); |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load supported bands (Modem interface) */ |
| |
| GArray * |
| mm_shared_qmi_load_supported_bands_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return (GArray *) g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| dms_get_band_capabilities_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| Private *priv; |
| QmiMessageDmsGetBandCapabilitiesOutput *output; |
| GError *error = NULL; |
| GArray *mm_bands = NULL; |
| QmiDmsBandCapability qmi_bands = 0; |
| QmiDmsLteBandCapability qmi_lte_bands = 0; |
| GArray *extended_qmi_lte_bands = NULL; |
| GArray *qmi_nr5g_bands = NULL; |
| |
| self = g_task_get_source_object (task); |
| priv = get_private (self); |
| |
| output = qmi_client_dms_get_band_capabilities_finish (client, res, &error); |
| if (!output || !qmi_message_dms_get_band_capabilities_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't get band capabilities: "); |
| goto out; |
| } |
| |
| qmi_message_dms_get_band_capabilities_output_get_band_capability ( |
| output, |
| &qmi_bands, |
| NULL); |
| qmi_message_dms_get_band_capabilities_output_get_lte_band_capability ( |
| output, |
| &qmi_lte_bands, |
| NULL); |
| qmi_message_dms_get_band_capabilities_output_get_extended_lte_band_capability ( |
| output, |
| &extended_qmi_lte_bands, |
| NULL); |
| qmi_message_dms_get_band_capabilities_output_get_nr5g_band_capability ( |
| output, |
| &qmi_nr5g_bands, |
| NULL); |
| |
| mm_bands = mm_modem_bands_from_qmi_band_capabilities (qmi_bands, qmi_lte_bands, extended_qmi_lte_bands, qmi_nr5g_bands, self); |
| if (mm_bands->len == 0) { |
| g_clear_pointer (&mm_bands, g_array_unref); |
| error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Couldn't parse the list of supported bands"); |
| goto out; |
| } |
| |
| /* Cache the result */ |
| g_clear_pointer (&priv->supported_bands, g_array_unref); |
| priv->supported_bands = g_array_ref (mm_bands); |
| |
| out: |
| if (output) |
| qmi_message_dms_get_band_capabilities_output_unref (output); |
| |
| if (error) |
| g_task_return_error (task, error); |
| else if (mm_bands) |
| g_task_return_pointer (task, mm_bands, (GDestroyNotify)g_array_unref); |
| else |
| g_assert_not_reached (); |
| g_object_unref (task); |
| } |
| |
| void |
| mm_shared_qmi_load_supported_bands (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| QmiClient *client = NULL; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_DMS, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| qmi_client_dms_get_band_capabilities (QMI_CLIENT_DMS (client), |
| NULL, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)dms_get_band_capabilities_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load current bands (Modem interface) */ |
| |
| GArray * |
| mm_shared_qmi_load_current_bands_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return (GArray *) g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| load_bands_get_system_selection_preference_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| Private *priv; |
| QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; |
| GError *error = NULL; |
| GArray *mm_bands = NULL; |
| QmiNasBandPreference band_preference_mask = 0; |
| QmiNasLteBandPreference lte_band_preference_mask = 0; |
| guint64 extended_lte_band_preference[4] = { 0 }; |
| guint extended_lte_band_preference_size = 0; |
| guint64 nr5g_sa_band_preference[8] = { 0 }; |
| guint64 nr5g_nsa_band_preference[8] = { 0 }; |
| guint64 nr5g_band_preference[8] = { 0 }; |
| guint nr5g_band_preference_size = 0; |
| |
| self = g_task_get_source_object (task); |
| priv = get_private (self); |
| |
| output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); |
| if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't get system selection preference: "); |
| goto out; |
| } |
| |
| qmi_message_nas_get_system_selection_preference_output_get_band_preference ( |
| output, |
| &band_preference_mask, |
| NULL); |
| |
| qmi_message_nas_get_system_selection_preference_output_get_lte_band_preference ( |
| output, |
| <e_band_preference_mask, |
| NULL); |
| |
| if ((priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED) && |
| qmi_message_nas_get_system_selection_preference_output_get_extended_lte_band_preference ( |
| output, |
| &extended_lte_band_preference[0], |
| &extended_lte_band_preference[1], |
| &extended_lte_band_preference[2], |
| &extended_lte_band_preference[3], |
| NULL)) |
| extended_lte_band_preference_size = G_N_ELEMENTS (extended_lte_band_preference); |
| |
| if (qmi_message_nas_get_system_selection_preference_output_get_nr5g_sa_band_preference ( |
| output, |
| &nr5g_sa_band_preference[0], |
| &nr5g_sa_band_preference[1], |
| &nr5g_sa_band_preference[2], |
| &nr5g_sa_band_preference[3], |
| &nr5g_sa_band_preference[4], |
| &nr5g_sa_band_preference[5], |
| &nr5g_sa_band_preference[6], |
| &nr5g_sa_band_preference[7], |
| NULL) || qmi_message_nas_get_system_selection_preference_output_get_nr5g_nsa_band_preference ( |
| output, |
| &nr5g_nsa_band_preference[0], |
| &nr5g_nsa_band_preference[1], |
| &nr5g_nsa_band_preference[2], |
| &nr5g_nsa_band_preference[3], |
| &nr5g_nsa_band_preference[4], |
| &nr5g_nsa_band_preference[5], |
| &nr5g_nsa_band_preference[6], |
| &nr5g_nsa_band_preference[7], |
| NULL)) { |
| guint i; |
| |
| nr5g_band_preference_size = G_N_ELEMENTS (nr5g_band_preference); |
| for (i = 0; i < nr5g_band_preference_size; i++) |
| nr5g_band_preference[i] = nr5g_sa_band_preference[i] | nr5g_nsa_band_preference[i]; |
| } |
| |
| mm_bands = mm_modem_bands_from_qmi_band_preference (band_preference_mask, |
| lte_band_preference_mask, |
| extended_lte_band_preference_size ? extended_lte_band_preference : NULL, |
| extended_lte_band_preference_size, |
| nr5g_band_preference_size ? nr5g_band_preference : NULL, |
| nr5g_band_preference_size, |
| self); |
| |
| if (mm_bands->len == 0) { |
| g_clear_pointer (&mm_bands, g_array_unref); |
| error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Couldn't parse the list of current bands"); |
| } |
| |
| out: |
| |
| if (output) |
| qmi_message_nas_get_system_selection_preference_output_unref (output); |
| |
| if (error) |
| g_task_return_error (task, error); |
| else if (mm_bands) |
| g_task_return_pointer (task, mm_bands, (GDestroyNotify)g_array_unref); |
| else |
| g_assert_not_reached (); |
| g_object_unref (task); |
| } |
| |
| void |
| mm_shared_qmi_load_current_bands (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| QmiClient *client = NULL; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_NAS, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| qmi_client_nas_get_system_selection_preference ( |
| QMI_CLIENT_NAS (client), |
| NULL, /* no input */ |
| 5, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)load_bands_get_system_selection_preference_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Set current bands (Modem interface) */ |
| |
| gboolean |
| mm_shared_qmi_set_current_bands_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| bands_set_system_selection_preference_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); |
| if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't set system selection preference: "); |
| g_task_return_error (task, error); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_nas_set_system_selection_preference_output_unref (output); |
| |
| g_object_unref (task); |
| } |
| |
| void |
| mm_shared_qmi_set_current_bands (MMIfaceModem *self, |
| GArray *bands_array, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| QmiMessageNasSetSystemSelectionPreferenceInput *input; |
| Private *priv; |
| GTask *task; |
| QmiClient *client = NULL; |
| QmiNasBandPreference qmi_bands = 0; |
| QmiNasLteBandPreference qmi_lte_bands = 0; |
| guint64 extended_qmi_lte_bands[4] = { 0 }; |
| guint64 qmi_nr5g_bands[8] = { 0 }; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_NAS, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| priv = get_private (MM_SHARED_QMI (self)); |
| |
| /* Handle ANY separately */ |
| if (bands_array->len == 1 && g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { |
| if (!priv->supported_bands) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Cannot handle 'ANY' if supported bands are unknown"); |
| g_object_unref (task); |
| return; |
| } |
| bands_array = priv->supported_bands; |
| } |
| |
| mm_modem_bands_to_qmi_band_preference (bands_array, |
| &qmi_bands, |
| &qmi_lte_bands, |
| priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED ? extended_qmi_lte_bands : NULL, |
| G_N_ELEMENTS (extended_qmi_lte_bands), |
| qmi_nr5g_bands, |
| G_N_ELEMENTS (qmi_nr5g_bands), |
| self); |
| |
| input = qmi_message_nas_set_system_selection_preference_input_new (); |
| qmi_message_nas_set_system_selection_preference_input_set_band_preference (input, qmi_bands, NULL); |
| if (mm_iface_modem_is_3gpp_lte (self)) { |
| if (priv->feature_nas_ssp_extended_lte_band_preference == FEATURE_SUPPORTED) |
| qmi_message_nas_set_system_selection_preference_input_set_extended_lte_band_preference ( |
| input, |
| extended_qmi_lte_bands[0], |
| extended_qmi_lte_bands[1], |
| extended_qmi_lte_bands[2], |
| extended_qmi_lte_bands[3], |
| NULL); |
| else |
| qmi_message_nas_set_system_selection_preference_input_set_lte_band_preference (input, qmi_lte_bands, NULL); |
| } |
| qmi_message_nas_set_system_selection_preference_input_set_nr5g_sa_band_preference ( |
| input, |
| qmi_nr5g_bands[0], |
| qmi_nr5g_bands[1], |
| qmi_nr5g_bands[2], |
| qmi_nr5g_bands[3], |
| qmi_nr5g_bands[4], |
| qmi_nr5g_bands[5], |
| qmi_nr5g_bands[6], |
| qmi_nr5g_bands[7], |
| NULL); |
| qmi_message_nas_set_system_selection_preference_input_set_nr5g_nsa_band_preference ( |
| input, |
| qmi_nr5g_bands[0], |
| qmi_nr5g_bands[1], |
| qmi_nr5g_bands[2], |
| qmi_nr5g_bands[3], |
| qmi_nr5g_bands[4], |
| qmi_nr5g_bands[5], |
| qmi_nr5g_bands[6], |
| qmi_nr5g_bands[7], |
| NULL); |
| qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL); |
| |
| qmi_client_nas_set_system_selection_preference ( |
| QMI_CLIENT_NAS (client), |
| input, |
| 5, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)bands_set_system_selection_preference_ready, |
| task); |
| qmi_message_nas_set_system_selection_preference_input_unref (input); |
| } |
| |
| /*****************************************************************************/ |
| /* Reset (Modem interface) */ |
| |
| gboolean |
| mm_shared_qmi_reset_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| reset_set_operating_mode_reset_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| QmiMessageDmsSetOperatingModeOutput *output; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| output = qmi_client_dms_set_operating_mode_finish (client, res, &error); |
| if (!output || !qmi_message_dms_set_operating_mode_output_get_result (output, &error)) { |
| g_task_return_error (task, error); |
| } else { |
| mm_obj_msg (self, "rebooting now"); |
| g_task_return_boolean (task, TRUE); |
| } |
| |
| if (output) |
| qmi_message_dms_set_operating_mode_output_unref (output); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| reset_set_operating_mode_offline_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsSetOperatingModeInput *input; |
| QmiMessageDmsSetOperatingModeOutput *output; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_set_operating_mode_finish (client, res, &error); |
| if (!output) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| qmi_message_dms_set_operating_mode_output_unref (output); |
| return; |
| } |
| |
| qmi_message_dms_set_operating_mode_output_unref (output); |
| |
| /* Now, go into reset mode. This will fully reboot the modem, and the current |
| * modem object should get disposed. */ |
| input = qmi_message_dms_set_operating_mode_input_new (); |
| qmi_message_dms_set_operating_mode_input_set_mode (input, QMI_DMS_OPERATING_MODE_RESET, NULL); |
| qmi_client_dms_set_operating_mode (client, |
| input, |
| 20, |
| NULL, |
| (GAsyncReadyCallback)reset_set_operating_mode_reset_ready, |
| task); |
| qmi_message_dms_set_operating_mode_input_unref (input); |
| } |
| |
| void |
| mm_shared_qmi_reset (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| QmiMessageDmsSetOperatingModeInput *input; |
| GTask *task; |
| QmiClient *client; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_DMS, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* Now, go into offline mode */ |
| input = qmi_message_dms_set_operating_mode_input_new (); |
| qmi_message_dms_set_operating_mode_input_set_mode (input, QMI_DMS_OPERATING_MODE_OFFLINE, NULL); |
| qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (client), |
| input, |
| 20, |
| NULL, |
| (GAsyncReadyCallback)reset_set_operating_mode_offline_ready, |
| task); |
| qmi_message_dms_set_operating_mode_input_unref (input); |
| } |
| |
| /*****************************************************************************/ |
| /* Factory reset (Modem interface) */ |
| |
| gboolean |
| mm_shared_qmi_factory_reset_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| dms_restore_factory_defaults_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsRestoreFactoryDefaultsOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_restore_factory_defaults_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_restore_factory_defaults_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't restore factory defaults: "); |
| g_task_return_error (task, error); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_dms_restore_factory_defaults_output_unref (output); |
| |
| g_object_unref (task); |
| } |
| |
| void |
| mm_shared_qmi_factory_reset (MMIfaceModem *self, |
| const gchar *code, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| QmiMessageDmsRestoreFactoryDefaultsInput *input; |
| GTask *task; |
| QmiClient *client = NULL; |
| GError *error = NULL; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_DMS, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| input = qmi_message_dms_restore_factory_defaults_input_new (); |
| if (!qmi_message_dms_restore_factory_defaults_input_set_service_programming_code ( |
| input, |
| code, |
| &error)) { |
| qmi_message_dms_restore_factory_defaults_input_unref (input); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "performing a factory reset..."); |
| qmi_client_dms_restore_factory_defaults (QMI_CLIENT_DMS (client), |
| input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback)dms_restore_factory_defaults_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Setup carrier config (Modem interface) */ |
| |
| #define SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS 10 |
| #define GENERIC_CONFIG_FALLBACK "generic" |
| |
| typedef enum { |
| SETUP_CARRIER_CONFIG_STEP_FIRST, |
| SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED, |
| SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED, |
| SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT, |
| SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT, |
| SETUP_CARRIER_CONFIG_STEP_LAST, |
| } SetupCarrierConfigStep; |
| |
| |
| typedef struct { |
| SetupCarrierConfigStep step; |
| QmiClientPdc *client; |
| GKeyFile *keyfile; |
| gchar *imsi; |
| |
| gint config_requested_i; |
| gchar *config_requested; |
| |
| guint token; |
| guint timeout_id; |
| gulong set_selected_config_indication_id; |
| gulong activate_config_indication_id; |
| } SetupCarrierConfigContext; |
| |
| /* Allow to cleanup action setup right away, without being tied |
| * to the lifecycle of the GTask */ |
| static void |
| setup_carrier_config_context_cleanup_action (SetupCarrierConfigContext *ctx) |
| { |
| if (ctx->activate_config_indication_id) { |
| g_signal_handler_disconnect (ctx->client, ctx->activate_config_indication_id); |
| ctx->activate_config_indication_id = 0; |
| } |
| if (ctx->set_selected_config_indication_id) { |
| g_signal_handler_disconnect (ctx->client, ctx->set_selected_config_indication_id); |
| ctx->set_selected_config_indication_id = 0; |
| } |
| if (ctx->timeout_id) { |
| g_source_remove (ctx->timeout_id); |
| ctx->timeout_id = 0; |
| } |
| } |
| |
| static void |
| setup_carrier_config_context_free (SetupCarrierConfigContext *ctx) |
| { |
| setup_carrier_config_context_cleanup_action (ctx); |
| |
| g_free (ctx->config_requested); |
| g_free (ctx->imsi); |
| g_key_file_unref (ctx->keyfile); |
| g_clear_object (&ctx->client); |
| g_slice_free (SetupCarrierConfigContext, ctx); |
| } |
| |
| gboolean |
| mm_shared_qmi_setup_carrier_config_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void setup_carrier_config_step (GTask *task); |
| |
| static void |
| setup_carrier_config_abort (GTask *task, |
| GError *error) |
| { |
| SetupCarrierConfigContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| setup_carrier_config_context_cleanup_action (ctx); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| |
| static gboolean |
| setup_carrier_config_timeout_no_error (GTask *task) |
| { |
| SetupCarrierConfigContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| g_assert (ctx->timeout_id); |
| ctx->timeout_id = 0; |
| |
| setup_carrier_config_context_cleanup_action (ctx); |
| ctx->step++; |
| setup_carrier_config_step (task); |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| static gboolean |
| setup_carrier_config_timeout (GTask *task) |
| { |
| SetupCarrierConfigContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| g_assert (ctx->timeout_id); |
| ctx->timeout_id = 0; |
| |
| setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, |
| "Operation timed out")); |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| activate_config_indication (QmiClientPdc *client, |
| QmiIndicationPdcActivateConfigOutput *output, |
| GTask *task) |
| { |
| SetupCarrierConfigContext *ctx; |
| GError *error = NULL; |
| guint16 error_code = 0; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!qmi_indication_pdc_activate_config_output_get_indication_result (output, &error_code, &error)) { |
| setup_carrier_config_abort (task, error); |
| return; |
| } |
| |
| if (error_code != 0) { |
| setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "couldn't activate config: %s", |
| qmi_protocol_error_get_string ((QmiProtocolError) error_code))); |
| return; |
| } |
| |
| /* Go on */ |
| setup_carrier_config_context_cleanup_action (ctx); |
| ctx->step++; |
| setup_carrier_config_step (task); |
| } |
| |
| static void |
| activate_config_ready (QmiClientPdc *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessagePdcActivateConfigOutput *output; |
| SetupCarrierConfigContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_pdc_activate_config_finish (client, res, &error); |
| if (!output || !qmi_message_pdc_activate_config_output_get_result (output, &error)) { |
| setup_carrier_config_abort (task, error); |
| goto out; |
| } |
| |
| /* When we activate the config, if the operation is successful, we'll just |
| * see the modem going away completely. So, do not consider an error the timeout |
| * waiting for the Activate Config indication, as that is actually a good |
| * thing. |
| */ |
| ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS, |
| (GSourceFunc) setup_carrier_config_timeout_no_error, |
| task); |
| ctx->activate_config_indication_id = g_signal_connect (ctx->client, |
| "activate-config", |
| G_CALLBACK (activate_config_indication), |
| task); |
| out: |
| if (output) |
| qmi_message_pdc_activate_config_output_unref (output); |
| } |
| |
| static void |
| set_selected_config_indication (QmiClientPdc *client, |
| QmiIndicationPdcSetSelectedConfigOutput *output, |
| GTask *task) |
| { |
| SetupCarrierConfigContext *ctx; |
| GError *error = NULL; |
| guint16 error_code = 0; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!qmi_indication_pdc_set_selected_config_output_get_indication_result (output, &error_code, &error)) { |
| setup_carrier_config_abort (task, error); |
| return; |
| } |
| |
| if (error_code != 0) { |
| setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "couldn't set selected config: %s", |
| qmi_protocol_error_get_string ((QmiProtocolError) error_code))); |
| return; |
| } |
| |
| /* Go on */ |
| setup_carrier_config_context_cleanup_action (ctx); |
| ctx->step++; |
| setup_carrier_config_step (task); |
| } |
| |
| static void |
| set_selected_config_ready (QmiClientPdc *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessagePdcSetSelectedConfigOutput *output; |
| SetupCarrierConfigContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_pdc_set_selected_config_finish (client, res, &error); |
| if (!output || !qmi_message_pdc_set_selected_config_output_get_result (output, &error)) { |
| setup_carrier_config_abort (task, error); |
| goto out; |
| } |
| |
| ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS, |
| (GSourceFunc) setup_carrier_config_timeout, |
| task); |
| ctx->set_selected_config_indication_id = g_signal_connect (ctx->client, |
| "set-selected-config", |
| G_CALLBACK (set_selected_config_indication), |
| task); |
| out: |
| if (output) |
| qmi_message_pdc_set_selected_config_output_unref (output); |
| } |
| |
| static gint |
| select_newest_carrier_config (MMSharedQmi *self, |
| gint config_a_i, |
| gint config_b_i) |
| { |
| Private *priv; |
| ConfigInfo *config_a; |
| ConfigInfo *config_b; |
| |
| priv = get_private (self); |
| config_a = &g_array_index (priv->config_list, ConfigInfo, config_a_i); |
| config_b = &g_array_index (priv->config_list, ConfigInfo, config_b_i); |
| |
| g_assert (!g_strcmp0 (config_a->description, config_b->description)); |
| |
| if (config_a->version > config_b->version) |
| return config_a_i; |
| if (config_b->version > config_a->version) |
| return config_b_i; |
| /* if both are equal, return the first one found always */ |
| return config_a_i; |
| } |
| |
| static void |
| find_requested_carrier_config (GTask *task) |
| { |
| SetupCarrierConfigContext *ctx; |
| MMSharedQmi *self; |
| Private *priv; |
| gchar mccmnc[7]; |
| gchar *group; |
| gint config_fallback_i = -1; |
| gchar *config_fallback = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| self = MM_SHARED_QMI (g_task_get_source_object (task)); |
| priv = get_private (self); |
| |
| /* Only one group expected per file, so get the start one */ |
| group = g_key_file_get_start_group (ctx->keyfile); |
| |
| /* Match generic configuration */ |
| config_fallback = g_key_file_get_string (ctx->keyfile, group, GENERIC_CONFIG_FALLBACK, NULL); |
| mm_obj_dbg (self, "fallback carrier configuration %sfound in group '%s'", config_fallback ? "" : "not ", group); |
| |
| /* First, try to match 6 MCCMNC digits (3-digit MNCs) */ |
| strncpy (mccmnc, ctx->imsi, 6); |
| mccmnc[6] = '\0'; |
| ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL); |
| if (!ctx->config_requested) { |
| /* If not found, try to match 5 MCCMNC digits (2-digit MNCs) */ |
| mccmnc[5] = '\0'; |
| ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL); |
| } |
| mm_obj_dbg (self, "requested carrier configuration %sfound for '%s' in group '%s': %s", |
| ctx->config_requested ? "" : "not ", mccmnc, group, ctx->config_requested ? ctx->config_requested : "n/a"); |
| |
| if (!ctx->config_requested && !config_fallback) { |
| setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, |
| "no valid configuration found in group '%s'", group)); |
| goto out; |
| } |
| |
| /* Now, look for the configurations among the ones available in the device */ |
| if (priv->config_list) { |
| guint i; |
| |
| for (i = 0; i < priv->config_list->len; i++) { |
| ConfigInfo *config; |
| |
| config = &g_array_index (priv->config_list, ConfigInfo, i); |
| if (ctx->config_requested && !g_strcmp0 (ctx->config_requested, config->description)) { |
| mm_obj_dbg (self, "requested carrier configuration '%s' is available (version 0x%08x, size %u bytes)", |
| config->description, config->version, config->total_size); |
| if (ctx->config_requested_i < 0) |
| ctx->config_requested_i = i; |
| else |
| ctx->config_requested_i = select_newest_carrier_config (self, ctx->config_requested_i, i); |
| } |
| if (config_fallback && !g_strcmp0 (config_fallback, config->description)) { |
| mm_obj_dbg (self, "fallback carrier configuration '%s' is available (version 0x%08x, size %u bytes)", |
| config->description, config->version, config->total_size); |
| if (config_fallback_i < 0) |
| config_fallback_i = i; |
| else |
| config_fallback_i = select_newest_carrier_config (self, config_fallback_i, i); |
| } |
| } |
| } |
| |
| /* Fail operation if we didn't find the one we want */ |
| if ((ctx->config_requested_i < 0) && (config_fallback_i < 0)) { |
| setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "carrier configurations (requested '%s', fallback '%s') are not available", |
| ctx->config_requested, config_fallback)); |
| goto out; |
| } |
| |
| /* If the mapping expects a given config, but the config isn't installed, |
| * we fallback to generic */ |
| if (ctx->config_requested_i < 0) { |
| ConfigInfo *config; |
| |
| g_assert (config_fallback_i >= 0); |
| |
| config = &g_array_index (priv->config_list, ConfigInfo, config_fallback_i); |
| mm_obj_dbg (self, "using fallback carrier configuration '%s' (version 0x%08x, size %u bytes)", |
| config->description, config->version, config->total_size); |
| |
| g_free (ctx->config_requested); |
| ctx->config_requested = config_fallback; |
| ctx->config_requested_i = config_fallback_i; |
| config_fallback = NULL; |
| } else { |
| ConfigInfo *config; |
| |
| config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); |
| mm_obj_dbg (self, "using requested carrier configuration '%s' (version 0x%08x, size %u bytes)", |
| config->description, config->version, config->total_size); |
| } |
| |
| ctx->step++; |
| setup_carrier_config_step (task); |
| |
| out: |
| g_free (config_fallback); |
| g_free (group); |
| } |
| |
| static void |
| setup_carrier_config_step (GTask *task) |
| { |
| MMSharedQmi *self; |
| SetupCarrierConfigContext *ctx; |
| Private *priv; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| priv = get_private (self); |
| |
| switch (ctx->step) { |
| case SETUP_CARRIER_CONFIG_STEP_FIRST: |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED: |
| find_requested_carrier_config (task); |
| return; |
| |
| case SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED: |
| g_assert (ctx->config_requested_i >= 0); |
| g_assert (priv->config_active_i >= 0 || priv->config_active_default); |
| if (ctx->config_requested_i == priv->config_active_i) { |
| mm_obj_msg (self, "carrier config switching not needed: already using '%s'", ctx->config_requested); |
| ctx->step = SETUP_CARRIER_CONFIG_STEP_LAST; |
| setup_carrier_config_step (task); |
| return; |
| } |
| |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT: { |
| g_autoptr(QmiMessagePdcSetSelectedConfigInput) input = NULL; |
| ConfigInfo *requested_config; |
| ConfigInfo *active_config; |
| |
| requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); |
| active_config = (priv->config_active_default ? NULL : &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i)); |
| mm_obj_warn (self, "carrier config switching needed: '%s' -> '%s'", |
| active_config ? active_config->description : DEFAULT_CONFIG_DESCRIPTION, requested_config->description); |
| |
| input = qmi_message_pdc_set_selected_config_input_new (); |
| qmi_message_pdc_set_selected_config_input_set_type_with_id_v2 (input, requested_config->config_type, requested_config->id, NULL); |
| qmi_message_pdc_set_selected_config_input_set_token (input, ctx->token++, NULL); |
| qmi_client_pdc_set_selected_config (ctx->client, |
| input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback)set_selected_config_ready, |
| task); |
| return; |
| } |
| |
| case SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT: { |
| g_autoptr(QmiMessagePdcActivateConfigInput) input = NULL; |
| ConfigInfo *requested_config; |
| |
| requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); |
| |
| input = qmi_message_pdc_activate_config_input_new (); |
| qmi_message_pdc_activate_config_input_set_config_type (input, requested_config->config_type, NULL); |
| qmi_message_pdc_activate_config_input_set_token (input, ctx->token++, NULL); |
| qmi_client_pdc_activate_config (ctx->client, |
| input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) activate_config_ready, |
| task); |
| return; |
| } |
| |
| case SETUP_CARRIER_CONFIG_STEP_LAST: |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| break; |
| |
| default: |
| g_assert_not_reached (); |
| } |
| } |
| |
| void |
| mm_shared_qmi_setup_carrier_config (MMIfaceModem *self, |
| const gchar *imsi, |
| const gchar *carrier_config_mapping, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| SetupCarrierConfigContext *ctx; |
| GTask *task; |
| QmiClient *client = NULL; |
| GError *error = NULL; |
| |
| g_assert (imsi); |
| g_assert (carrier_config_mapping); |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| ctx = g_slice_new0 (SetupCarrierConfigContext); |
| ctx->step = SETUP_CARRIER_CONFIG_STEP_FIRST; |
| ctx->imsi = g_strdup (imsi); |
| ctx->keyfile = g_key_file_new (); |
| ctx->config_requested_i = -1; |
| g_task_set_task_data (task, ctx, (GDestroyNotify)setup_carrier_config_context_free); |
| |
| /* Load mapping keyfile */ |
| if (!g_key_file_load_from_file (ctx->keyfile, |
| carrier_config_mapping, |
| G_KEY_FILE_NONE, |
| &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Load PDC client */ |
| client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_PDC, |
| MM_PORT_QMI_FLAG_DEFAULT, |
| NULL); |
| if (!client) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "QMI PDC not supported"); |
| g_object_unref (task); |
| return; |
| } |
| ctx->client = QMI_CLIENT_PDC (g_object_ref (client)); |
| |
| setup_carrier_config_step (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load carrier config (Modem interface) */ |
| |
| #define LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS 5 |
| |
| typedef enum { |
| LOAD_CARRIER_CONFIG_STEP_FIRST, |
| LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS, |
| LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT, |
| LOAD_CARRIER_CONFIG_STEP_LAST, |
| } LoadCarrierConfigStep; |
| |
| typedef struct { |
| LoadCarrierConfigStep step; |
| |
| QmiClientPdc *client; |
| |
| GArray *config_list; |
| guint configs_loaded; |
| gboolean config_active_default; |
| gint config_active_i; |
| |
| guint token; |
| guint timeout_id; |
| gulong list_configs_indication_id; |
| gulong get_selected_config_indication_id; |
| gulong get_config_info_indication_id; |
| } LoadCarrierConfigContext; |
| |
| /* Allow to cleanup action load right away, without being tied |
| * to the lifecycle of the GTask */ |
| static void |
| load_carrier_config_context_cleanup_action (LoadCarrierConfigContext *ctx) |
| { |
| if (ctx->get_selected_config_indication_id) { |
| g_signal_handler_disconnect (ctx->client, ctx->get_selected_config_indication_id); |
| ctx->get_selected_config_indication_id = 0; |
| } |
| if (ctx->get_config_info_indication_id) { |
| g_signal_handler_disconnect (ctx->client, ctx->get_config_info_indication_id); |
| ctx->get_config_info_indication_id = 0; |
| } |
| if (ctx->list_configs_indication_id) { |
| g_signal_handler_disconnect (ctx->client, ctx->list_configs_indication_id); |
| ctx->list_configs_indication_id = 0; |
| } |
| if (ctx->timeout_id) { |
| g_source_remove (ctx->timeout_id); |
| ctx->timeout_id = 0; |
| } |
| } |
| |
| static void |
| load_carrier_config_context_free (LoadCarrierConfigContext *ctx) |
| { |
| load_carrier_config_context_cleanup_action (ctx); |
| |
| if (ctx->config_list) |
| g_array_unref (ctx->config_list); |
| g_clear_object (&ctx->client); |
| g_slice_free (LoadCarrierConfigContext, ctx); |
| } |
| |
| gboolean |
| mm_shared_qmi_load_carrier_config_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| gchar **carrier_config_name, |
| gchar **carrier_config_revision, |
| GError **error) |
| { |
| Private *priv; |
| |
| if (!g_task_propagate_boolean (G_TASK (res), error)) |
| return FALSE; |
| |
| priv = get_private (MM_SHARED_QMI (self)); |
| g_assert (priv->config_active_i >= 0 || priv->config_active_default); |
| |
| if (priv->config_active_i >= 0) { |
| ConfigInfo *config; |
| |
| config = &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i); |
| *carrier_config_name = g_strdup (config->description); |
| *carrier_config_revision = g_strdup_printf ("%08X", config->version); |
| } else if (priv->config_active_default) { |
| *carrier_config_name = g_strdup (DEFAULT_CONFIG_DESCRIPTION); |
| *carrier_config_revision = NULL; |
| } else |
| g_assert_not_reached (); |
| |
| return TRUE; |
| } |
| |
| static void load_carrier_config_step (GTask *task); |
| |
| static void |
| load_carrier_config_abort (GTask *task, |
| GError *error) |
| { |
| LoadCarrierConfigContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| load_carrier_config_context_cleanup_action (ctx); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| |
| static gboolean |
| load_carrier_config_timeout (GTask *task) |
| { |
| LoadCarrierConfigContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| g_assert (ctx->timeout_id); |
| ctx->timeout_id = 0; |
| |
| load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, |
| "Operation timed out")); |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| get_selected_config_indication (QmiClientPdc *client, |
| QmiIndicationPdcGetSelectedConfigOutput *output, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| LoadCarrierConfigContext *ctx; |
| GArray *active_id = NULL; |
| GError *error = NULL; |
| guint16 error_code = 0; |
| guint i; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| if (!qmi_indication_pdc_get_selected_config_output_get_indication_result (output, &error_code, &error)) { |
| load_carrier_config_abort (task, error); |
| return; |
| } |
| |
| if (error_code != 0 && |
| error_code != QMI_PROTOCOL_ERROR_NOT_PROVISIONED) { /* No configs active */ |
| load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "couldn't get selected config: %s", |
| qmi_protocol_error_get_string ((QmiProtocolError) error_code))); |
| return; |
| } |
| |
| qmi_indication_pdc_get_selected_config_output_get_active_id (output, &active_id, NULL); |
| if (!active_id) { |
| mm_obj_dbg (self, "no carrier config currently selected (default in use)"); |
| ctx->config_active_default = TRUE; |
| goto next; |
| } |
| |
| g_assert (ctx->config_list); |
| g_assert (ctx->config_list->len); |
| |
| for (i = 0; i < ctx->config_list->len; i++) { |
| ConfigInfo *config; |
| |
| config = &g_array_index (ctx->config_list, ConfigInfo, i); |
| if ((config->id->len == active_id->len) && |
| !memcmp (config->id->data, active_id->data, active_id->len)) { |
| ctx->config_active_i = i; |
| break; |
| } |
| } |
| |
| if (i == ctx->config_list->len) { |
| load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "couldn't find currently selected config")); |
| return; |
| } |
| |
| next: |
| |
| /* Go on */ |
| load_carrier_config_context_cleanup_action (ctx); |
| ctx->step++; |
| load_carrier_config_step (task); |
| } |
| |
| static void |
| get_selected_config_ready (QmiClientPdc *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessagePdcGetSelectedConfigOutput *output; |
| LoadCarrierConfigContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_pdc_get_selected_config_finish (client, res, &error); |
| if (!output || !qmi_message_pdc_get_selected_config_output_get_result (output, &error)) { |
| load_carrier_config_abort (task, error); |
| goto out; |
| } |
| |
| ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS, |
| (GSourceFunc) load_carrier_config_timeout, |
| task); |
| ctx->get_selected_config_indication_id = g_signal_connect (ctx->client, |
| "get-selected-config", |
| G_CALLBACK (get_selected_config_indication), |
| task); |
| |
| out: |
| if (output) |
| qmi_message_pdc_get_selected_config_output_unref (output); |
| } |
| |
| static void |
| get_config_info_indication (QmiClientPdc *client, |
| QmiIndicationPdcGetConfigInfoOutput *output, |
| GTask *task) |
| { |
| LoadCarrierConfigContext *ctx; |
| GError *error = NULL; |
| ConfigInfo *current_config = NULL; |
| guint32 token; |
| const gchar *description; |
| guint i; |
| guint16 error_code = 0; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!qmi_indication_pdc_get_config_info_output_get_indication_result (output, &error_code, &error)) { |
| load_carrier_config_abort (task, error); |
| return; |
| } |
| |
| if (error_code != 0) { |
| load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "couldn't get config info: %s", |
| qmi_protocol_error_get_string ((QmiProtocolError) error_code))); |
| return; |
| } |
| |
| if (!qmi_indication_pdc_get_config_info_output_get_token (output, &token, &error)) { |
| load_carrier_config_abort (task, error); |
| return; |
| } |
| |
| /* Look for the current config in the list, match by token */ |
| for (i = 0; i < ctx->config_list->len; i++) { |
| current_config = &g_array_index (ctx->config_list, ConfigInfo, i); |
| if (current_config->token == token) |
| break; |
| } |
| |
| /* Ignore if not found in the list */ |
| if (i == ctx->config_list->len) |
| return; |
| |
| /* Ignore if already set */ |
| if (current_config->description) |
| return; |
| |
| /* Store total size, version and description of the current config */ |
| if (!qmi_indication_pdc_get_config_info_output_get_total_size (output, ¤t_config->total_size, &error) || |
| !qmi_indication_pdc_get_config_info_output_get_version (output, ¤t_config->version, &error) || |
| !qmi_indication_pdc_get_config_info_output_get_description (output, &description, &error)) { |
| load_carrier_config_abort (task, error); |
| return; |
| } |
| |
| current_config->description = g_strdup (description); |
| ctx->configs_loaded++; |
| |
| /* If not all loaded, wait for more */ |
| if (ctx->configs_loaded < ctx->config_list->len) |
| return; |
| |
| /* Go on */ |
| load_carrier_config_context_cleanup_action (ctx); |
| ctx->step++; |
| load_carrier_config_step (task); |
| } |
| |
| static void |
| list_configs_indication (QmiClientPdc *client, |
| QmiIndicationPdcListConfigsOutput *output, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| LoadCarrierConfigContext *ctx; |
| GError *error = NULL; |
| GArray *configs = NULL; |
| guint i; |
| guint16 error_code = 0; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| if (!qmi_indication_pdc_list_configs_output_get_indication_result (output, &error_code, &error)) { |
| load_carrier_config_abort (task, error); |
| return; |
| } |
| |
| if (error_code != 0) { |
| load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "couldn't list configs: %s", |
| qmi_protocol_error_get_string ((QmiProtocolError) error_code))); |
| return; |
| } |
| |
| if (!qmi_indication_pdc_list_configs_output_get_configs (output, &configs, &error)) { |
| load_carrier_config_abort (task, error); |
| return; |
| } |
| |
| /* If no configs are installed, the module is running with the default one */ |
| if (!configs || !configs->len) { |
| ctx->config_active_default = TRUE; |
| ctx->step = LOAD_CARRIER_CONFIG_STEP_LAST; |
| load_carrier_config_step (task); |
| return; |
| } |
| |
| /* Preallocate config list and request details for each */ |
| mm_obj_dbg (self, "found %u carrier configurations...", configs->len); |
| ctx->config_list = g_array_sized_new (FALSE, TRUE, sizeof (ConfigInfo), configs->len); |
| g_array_set_size (ctx->config_list, configs->len); |
| g_array_set_clear_func (ctx->config_list, (GDestroyNotify) config_info_clear); |
| |
| ctx->get_config_info_indication_id = g_signal_connect (ctx->client, |
| "get-config-info", |
| G_CALLBACK (get_config_info_indication), |
| task); |
| |
| for (i = 0; i < configs->len; i++) { |
| ConfigInfo *current_info; |
| QmiIndicationPdcListConfigsOutputConfigsElement *element; |
| g_autoptr(QmiMessagePdcGetConfigInfoInput) input = NULL; |
| |
| element = &g_array_index (configs, QmiIndicationPdcListConfigsOutputConfigsElement, i); |
| |
| current_info = &g_array_index (ctx->config_list, ConfigInfo, i); |
| current_info->token = ctx->token++; |
| current_info->id = g_array_ref (element->id); |
| current_info->config_type = element->config_type; |
| |
| input = qmi_message_pdc_get_config_info_input_new (); |
| qmi_message_pdc_get_config_info_input_set_type_with_id_v2 (input, element->config_type, current_info->id, NULL); |
| qmi_message_pdc_get_config_info_input_set_token (input, current_info->token, NULL); |
| qmi_client_pdc_get_config_info (ctx->client, input, 10, NULL, NULL, NULL); /* ignore response! */ |
| } |
| } |
| |
| static void |
| list_configs_ready (QmiClientPdc *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessagePdcListConfigsOutput *output; |
| LoadCarrierConfigContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_pdc_list_configs_finish (client, res, &error); |
| if (!output || !qmi_message_pdc_list_configs_output_get_result (output, &error)) { |
| load_carrier_config_abort (task, error); |
| goto out; |
| } |
| |
| ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS, |
| (GSourceFunc) load_carrier_config_timeout, |
| task); |
| ctx->list_configs_indication_id = g_signal_connect (ctx->client, |
| "list-configs", |
| G_CALLBACK (list_configs_indication), |
| task); |
| out: |
| if (output) |
| qmi_message_pdc_list_configs_output_unref (output); |
| } |
| |
| static void |
| load_carrier_config_step (GTask *task) |
| { |
| LoadCarrierConfigContext *ctx; |
| Private *priv; |
| |
| ctx = g_task_get_task_data (task); |
| priv = get_private (g_task_get_source_object (task)); |
| |
| switch (ctx->step) { |
| case LOAD_CARRIER_CONFIG_STEP_FIRST: |
| ctx->step++; |
| /* fall-through */ |
| |
| case LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS: { |
| QmiMessagePdcListConfigsInput *input; |
| |
| input = qmi_message_pdc_list_configs_input_new (); |
| qmi_message_pdc_list_configs_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); |
| qmi_message_pdc_list_configs_input_set_token (input, ctx->token++, NULL); |
| qmi_client_pdc_list_configs (ctx->client, |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)list_configs_ready, |
| task); |
| qmi_message_pdc_list_configs_input_unref (input); |
| return; |
| } |
| |
| case LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT: { |
| QmiMessagePdcGetSelectedConfigInput *input; |
| |
| input = qmi_message_pdc_get_selected_config_input_new (); |
| qmi_message_pdc_get_selected_config_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); |
| qmi_message_pdc_get_selected_config_input_set_token (input, ctx->token++, NULL); |
| qmi_client_pdc_get_selected_config (ctx->client, |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)get_selected_config_ready, |
| task); |
| qmi_message_pdc_get_selected_config_input_unref (input); |
| return; |
| } |
| |
| case LOAD_CARRIER_CONFIG_STEP_LAST: |
| /* We will now store the loaded information so that we can later on use it |
| * if needed during the automatic carrier config switching operation */ |
| g_assert (priv->config_active_i < 0 && !priv->config_active_default); |
| g_assert (ctx->config_active_i >= 0 || ctx->config_active_default); |
| priv->config_list = ctx->config_list ? g_array_ref (ctx->config_list) : NULL; |
| priv->config_active_i = ctx->config_active_i; |
| priv->config_active_default = ctx->config_active_default; |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| break; |
| |
| default: |
| g_assert_not_reached (); |
| } |
| } |
| |
| void |
| mm_shared_qmi_load_carrier_config (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| LoadCarrierConfigContext *ctx; |
| GTask *task; |
| QmiClient *client = NULL; |
| |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| ctx = g_slice_new0 (LoadCarrierConfigContext); |
| ctx->step = LOAD_CARRIER_CONFIG_STEP_FIRST; |
| ctx->config_active_i = -1; |
| g_task_set_task_data (task, ctx, (GDestroyNotify)load_carrier_config_context_free); |
| |
| /* Load PDC client */ |
| client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_PDC, |
| MM_PORT_QMI_FLAG_DEFAULT, |
| NULL); |
| if (!client) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "QMI PDC not supported"); |
| g_object_unref (task); |
| return; |
| } |
| ctx->client = QMI_CLIENT_PDC (g_object_ref (client)); |
| |
| load_carrier_config_step (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load SIM slots (modem interface) */ |
| |
| typedef struct { |
| QmiClientUim *client_uim; |
| GPtrArray *sim_slots; |
| guint active_slot_number; |
| guint active_logical_id; |
| } LoadSimSlotsContext; |
| |
| static void |
| load_sim_slots_context_free (LoadSimSlotsContext *ctx) |
| { |
| g_clear_pointer (&ctx->sim_slots, g_ptr_array_unref); |
| g_clear_object (&ctx->client_uim); |
| g_slice_free (LoadSimSlotsContext, ctx); |
| } |
| |
| static void |
| sim_slot_free (MMBaseSim *sim) |
| { |
| if (sim) |
| g_object_unref (sim); |
| } |
| |
| gboolean |
| mm_shared_qmi_load_sim_slots_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GPtrArray **sim_slots, |
| guint *primary_sim_slot, |
| GError **error) |
| { |
| LoadSimSlotsContext *ctx; |
| |
| if (!g_task_propagate_boolean (G_TASK (res), error)) |
| return FALSE; |
| |
| ctx = g_task_get_task_data (G_TASK (res)); |
| |
| if (sim_slots) |
| *sim_slots = g_steal_pointer (&ctx->sim_slots); |
| if (primary_sim_slot) |
| *primary_sim_slot = ctx->active_slot_number; |
| return TRUE; |
| } |
| |
| static void |
| uim_get_slot_status_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; |
| LoadSimSlotsContext *ctx; |
| MMIfaceModem *self; |
| Private *priv; |
| g_autoptr(GError) error = NULL; |
| GArray *physical_slots = NULL; |
| GArray *ext_information = NULL; |
| GArray *slot_eids = NULL; |
| guint i; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| priv = get_private (MM_SHARED_QMI (self)); |
| |
| output = qmi_client_uim_get_slot_status_finish (client, res, &error); |
| if (!output || |
| !qmi_message_uim_get_slot_status_output_get_result (output, &error) || |
| !qmi_message_uim_get_slot_status_output_get_physical_slot_status (output, &physical_slots, &error)) { |
| if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_DEVICE_UNSUPPORTED) || |
| g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND) || |
| g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED)) |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "QMI SIM slot switch operation not supported"); |
| else |
| g_task_return_error (task, g_steal_pointer (&error)); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Store the slot status before loading all sim slots. |
| * We will use this to check if a hotswap requires a reprobe. */ |
| if (priv->slots_status) |
| g_array_unref (priv->slots_status); |
| priv->slots_status = g_array_ref (physical_slots); |
| |
| /* It's fine if we don't have EID information, but it should be well-formed if present. If it's malformed, |
| * there is probably a modem firmware bug. */ |
| if (qmi_message_uim_get_slot_status_output_get_physical_slot_information (output, &ext_information, NULL) && |
| qmi_message_uim_get_slot_status_output_get_slot_eid (output, &slot_eids, NULL) && |
| (ext_information->len != physical_slots->len || slot_eids->len != physical_slots->len)) { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "UIM Get Slot Status returned malformed response"); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx->sim_slots = g_ptr_array_new_full (physical_slots->len, (GDestroyNotify) sim_slot_free); |
| |
| for (i = 0; i < physical_slots->len; i++) { |
| QmiPhysicalSlotStatusSlot *slot_status; |
| QmiPhysicalSlotInformationSlot *slot_info; |
| MMBaseSim *sim; |
| g_autofree gchar *raw_iccid = NULL; |
| g_autofree gchar *iccid = NULL; |
| g_autofree gchar *eid = NULL; |
| g_autoptr(GError) inner_error = NULL; |
| gboolean sim_active = FALSE; |
| |
| /* Store active slot info */ |
| slot_status = &g_array_index (physical_slots, QmiPhysicalSlotStatusSlot, i); |
| if (slot_status->physical_slot_status == QMI_UIM_SLOT_STATE_ACTIVE) { |
| sim_active = TRUE; |
| ctx->active_logical_id = slot_status->logical_slot; |
| ctx->active_slot_number = i + 1; |
| } |
| |
| if (!slot_status->iccid->len) { |
| mm_obj_dbg (self, "not creating SIM object: no SIM in slot %u", i + 1); |
| g_ptr_array_add (ctx->sim_slots, NULL); |
| continue; |
| } |
| |
| /* This is supposed to be BCD, but some carriers have non-spec-compliant ICCIDs that use |
| * A-F characters as part of the operator-specific part of the ICCID. Parse it as hex and |
| * let mm_3gpp_parse_iccid take care of handling the A-F characters properly. */ |
| raw_iccid = mm_utils_bin2hexstr ((const guint8 *)slot_status->iccid->data, slot_status->iccid->len); |
| if (!raw_iccid) { |
| mm_obj_warn (self, "not creating SIM object: failed to convert ICCID from BCD"); |
| g_ptr_array_add (ctx->sim_slots, NULL); |
| continue; |
| } |
| |
| iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error); |
| if (!iccid) { |
| mm_obj_warn (self, "not creating SIM object: couldn't parse SIM iccid: %s", inner_error->message); |
| g_ptr_array_add (ctx->sim_slots, NULL); |
| continue; |
| } |
| |
| if (ext_information && slot_eids) { |
| slot_info = &g_array_index (ext_information, QmiPhysicalSlotInformationSlot, i); |
| if (slot_info->is_euicc) { |
| QmiSlotEidElement *slot_eid_element; |
| |
| slot_eid_element = &g_array_index (slot_eids, QmiSlotEidElement, i); |
| if (slot_eid_element->eid->len) |
| eid = mm_decode_eid (slot_eid_element->eid->data, slot_eid_element->eid->len); |
| if (!eid) |
| mm_obj_dbg (self, "SIM in slot %d is marked as eUICC, but has malformed EID", i + 1); |
| } |
| } |
| |
| sim = mm_sim_qmi_new_initialized (MM_BASE_MODEM (self), |
| TRUE, /* consider DMS UIM deprecated if we're creating SIM slots */ |
| i + 1, /* slot number is the array index starting at 1 */ |
| sim_active, |
| iccid, |
| NULL, /* imsi unknown */ |
| eid, /* may be NULL, which is fine */ |
| NULL, /* operator id unknown */ |
| NULL, /* operator name unknown */ |
| NULL); /* emergency numbers unknown */ |
| g_ptr_array_add (ctx->sim_slots, sim); |
| } |
| g_assert_cmpuint (ctx->sim_slots->len, ==, physical_slots->len); |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| void |
| mm_shared_qmi_load_sim_slots (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| LoadSimSlotsContext *ctx; |
| GTask *task; |
| QmiClient *client = NULL; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_UIM, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| ctx = g_slice_new0 (LoadSimSlotsContext); |
| ctx->client_uim = QMI_CLIENT_UIM (g_object_ref (client)); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) load_sim_slots_context_free); |
| |
| qmi_client_uim_get_slot_status (ctx->client_uim, |
| NULL, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) uim_get_slot_status_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Set Primary SIM slot (modem interface) */ |
| |
| gboolean |
| mm_shared_qmi_set_primary_sim_slot_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| uim_switch_slot_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(QmiMessageUimSwitchSlotOutput) output = NULL; |
| g_autoptr(GError) error = NULL; |
| MMIfaceModem *self; |
| |
| self = g_task_get_source_object (task); |
| |
| output = qmi_client_uim_switch_slot_finish (client, res, &error); |
| if (!output || !qmi_message_uim_switch_slot_output_get_result (output, &error)) { |
| if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_EXISTS, |
| "SIM slot switch operation not needed"); |
| else |
| g_task_return_error (task, g_steal_pointer (&error)); |
| } else { |
| mm_obj_msg (self, "SIM slot switch operation request successful"); |
| g_task_return_boolean (task, TRUE); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_switch_get_slot_status_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; |
| g_autoptr(QmiMessageUimSwitchSlotInput) input = NULL; |
| MMIfaceModem *self; |
| g_autoptr(GError) error = NULL; |
| GArray *physical_slots = NULL; |
| guint i; |
| guint active_logical_id = 0; |
| guint active_slot_number; |
| guint slot_number; |
| |
| self = g_task_get_source_object (task); |
| slot_number = GPOINTER_TO_UINT (g_task_get_task_data (task)); |
| |
| output = qmi_client_uim_get_slot_status_finish (client, res, &error); |
| if (!output || |
| !qmi_message_uim_get_slot_status_output_get_result (output, &error) || |
| !qmi_message_uim_get_slot_status_output_get_physical_slot_status (output, &physical_slots, &error)) { |
| if (g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_DEVICE_UNSUPPORTED) || |
| g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_QMI_COMMAND) || |
| g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NOT_SUPPORTED)) |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "QMI SIM slot switch operation not supported"); |
| else |
| g_task_return_error (task, g_steal_pointer (&error)); |
| g_object_unref (task); |
| return; |
| } |
| |
| for (i = 0; i < physical_slots->len; i++) { |
| QmiPhysicalSlotStatusSlot *slot_status; |
| |
| /* We look for the currently ACTIVE SIM card only! */ |
| slot_status = &g_array_index (physical_slots, QmiPhysicalSlotStatusSlot, i); |
| if (slot_status->physical_slot_status != QMI_UIM_SLOT_STATE_ACTIVE) |
| continue; |
| |
| active_logical_id = slot_status->logical_slot; |
| active_slot_number = i + 1; |
| } |
| |
| if (!active_logical_id) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "couldn't find active slot logical ID"); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (active_slot_number == slot_number) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_EXISTS, |
| "SIM slot switch operation not needed"); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "requesting active logical id %d switch to SIM slot %u", active_logical_id, slot_number); |
| |
| input = qmi_message_uim_switch_slot_input_new (); |
| qmi_message_uim_switch_slot_input_set_logical_slot (input, (guint8) active_logical_id, NULL); |
| qmi_message_uim_switch_slot_input_set_physical_slot (input, slot_number, NULL); |
| qmi_client_uim_switch_slot (client, |
| input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) uim_switch_slot_ready, |
| task); |
| } |
| |
| void |
| mm_shared_qmi_set_primary_sim_slot (MMIfaceModem *self, |
| guint sim_slot, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| QmiClient *client = NULL; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_UIM, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, GUINT_TO_POINTER (sim_slot), NULL); |
| |
| qmi_client_uim_get_slot_status (QMI_CLIENT_UIM (client), |
| NULL, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) uim_switch_get_slot_status_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* UIM refresh indication handling */ |
| |
| #define REFRESH_START_TIMEOUT_SECS 3 |
| |
| static void |
| uim_refresh_complete (QmiClientUim *client, |
| QmiUimSessionType session_type) |
| { |
| g_autoptr(QmiMessageUimRefreshCompleteInput) refresh_complete_input = NULL; |
| GArray *placeholder_aid; |
| |
| placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); |
| |
| refresh_complete_input = qmi_message_uim_refresh_complete_input_new (); |
| qmi_message_uim_refresh_complete_input_set_session ( |
| refresh_complete_input, |
| session_type, |
| placeholder_aid, /* ignored */ |
| NULL); |
| qmi_message_uim_refresh_complete_input_set_info ( |
| refresh_complete_input, |
| TRUE, |
| NULL); |
| |
| qmi_client_uim_refresh_complete ( |
| client, |
| refresh_complete_input, |
| 10, |
| NULL, |
| NULL, |
| NULL); |
| g_array_unref (placeholder_aid); |
| } |
| |
| static gboolean |
| uim_start_refresh_timeout (MMSharedQmi *self) |
| { |
| Private *priv; |
| |
| priv = get_private (self); |
| priv->uim_refresh_start_timeout_id = 0; |
| |
| mm_obj_dbg (self, "refresh start timed out; trigger SIM change check"); |
| |
| mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), NULL, NULL); |
| |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| uim_refresh_indication_cb (QmiClientUim *client, |
| QmiIndicationUimRefreshOutput *output, |
| MMSharedQmi *self) |
| { |
| QmiUimRefreshStage stage; |
| QmiUimRefreshMode mode; |
| QmiUimSessionType session_type; |
| Private *priv; |
| g_autoptr(GError) error = NULL; |
| |
| priv = get_private (self); |
| |
| if (!qmi_indication_uim_refresh_output_get_event (output, |
| &stage, |
| &mode, |
| &session_type, |
| NULL, |
| NULL, |
| &error)) { |
| mm_obj_warn (self, "couldn't process UIM refresh indication: %s", error->message); |
| return; |
| } |
| |
| mm_obj_dbg (self, "refresh indication received: session type '%s', stage '%s', mode '%s'", |
| qmi_uim_session_type_get_string (session_type), |
| qmi_uim_refresh_stage_get_string (stage), |
| qmi_uim_refresh_mode_get_string (mode)); |
| |
| /* Support only the first slot for now. Primary GW provisioning is used in old modems. */ |
| if (session_type != QMI_UIM_SESSION_TYPE_CARD_SLOT_1 && |
| session_type != QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING) { |
| mm_obj_warn (self, "refresh session type not supported: %s", qmi_uim_session_type_get_string (session_type)); |
| return; |
| } |
| |
| /* Currently we handle UICC Reset type refresh, which can be used |
| * in profile switch scenarios, and Init Full FCN type refresh for |
| * SIM IMSI switch scenarios. In other cases we just trigger 'refresh |
| * complete' during start phase. Signal to notify about potential SIM |
| * profile switch is triggered when the refresh is ending. If it were |
| * triggered in start phase, reading SIM files seems to fail with |
| * an internal error. |
| * |
| * It's possible that 'end-with-success' stage never appears. For that, |
| * we start a timer at 'start' stage and if it expires, the SIM change |
| * check is triggered anyway. */ |
| if (stage == QMI_UIM_REFRESH_STAGE_START) { |
| if (mode == QMI_UIM_REFRESH_MODE_RESET || mode == QMI_UIM_REFRESH_MODE_INIT_FULL_FCN) { |
| if (!priv->uim_refresh_start_timeout_id) |
| priv->uim_refresh_start_timeout_id = g_timeout_add_seconds (REFRESH_START_TIMEOUT_SECS, |
| (GSourceFunc)uim_start_refresh_timeout, |
| self); |
| } else |
| uim_refresh_complete (client, session_type); |
| } else if (stage == QMI_UIM_REFRESH_STAGE_END_WITH_SUCCESS) { |
| if (mode == QMI_UIM_REFRESH_MODE_RESET || mode == QMI_UIM_REFRESH_MODE_INIT_FULL_FCN) { |
| if (priv->uim_refresh_start_timeout_id) { |
| g_source_remove (priv->uim_refresh_start_timeout_id); |
| priv->uim_refresh_start_timeout_id = 0; |
| } |
| mm_iface_modem_check_for_sim_swap (MM_IFACE_MODEM (self), NULL, NULL); |
| } |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* UIM slot status indication handling */ |
| |
| /* Modifies the sim at slot == index+1, based on the content of slot_status. |
| * Primarily used when a hotswap occurs on the inactive slot */ |
| static void |
| update_sim_from_slot_status (MMSharedQmi *self, |
| QmiPhysicalSlotStatusSlot *slot_status, |
| guint slot_index) |
| { |
| g_autoptr(MMBaseSim) sim = NULL; |
| g_autofree gchar *raw_iccid = NULL; |
| g_autofree gchar *iccid = NULL; |
| g_autoptr(GError) inner_error = NULL; |
| |
| mm_obj_dbg (self, "Updating sim at slot %d", slot_index + 1); |
| /* If sim is not present, ask mm_iface_modem to clear the sim object */ |
| if (slot_status->physical_card_status != QMI_UIM_PHYSICAL_CARD_STATE_PRESENT) { |
| mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, NULL); |
| return; |
| } |
| |
| raw_iccid = mm_utils_bin2hexstr ((const guint8 *)slot_status->iccid->data, slot_status->iccid->len); |
| if (!raw_iccid) { |
| mm_obj_warn (self, "not creating SIM object: failed to convert ICCID from BCD"); |
| return; |
| } |
| iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error); |
| if (!iccid) { |
| mm_obj_warn (self, "not creating SIM object: couldn't parse SIM iccid: %s", inner_error->message); |
| return; |
| } |
| sim = mm_sim_qmi_new_initialized (MM_BASE_MODEM (self), |
| TRUE, /* consider DMS UIM deprecated if we're creating SIM slots */ |
| slot_index + 1, /* slot number is the array index starting at 1 */ |
| slot_status->physical_slot_status, /* is_active */ |
| iccid, |
| NULL, /* imsi unknown */ |
| NULL, /* eid unknown */ |
| NULL, /* operator id unknown */ |
| NULL, /* operator name unknown */ |
| NULL); /* emergency numbers unknown */ |
| |
| mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, sim); |
| } |
| |
| /* Checks for equality of two slots. */ |
| static gboolean |
| slot_status_equal (QmiPhysicalSlotStatusSlot *slot_a, |
| QmiPhysicalSlotStatusSlot *slot_b) |
| { |
| guint j; |
| |
| if (slot_a->physical_slot_status != slot_b->physical_slot_status) |
| return FALSE; |
| if (slot_a->physical_card_status != slot_b->physical_card_status) |
| return FALSE; |
| if (slot_a->iccid->len != slot_b->iccid->len) |
| return FALSE; |
| for (j = 0; j < slot_a->iccid->len; j++) { |
| if (g_array_index (slot_a->iccid, guint8, j) != g_array_index (slot_b->iccid, guint8, j)) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /* Checks for equality of QmiPhysicalSlotStatusSlot arrays. |
| * The number of elements in each array is expected to be the number of sim slots in the modem.*/ |
| static gboolean |
| slot_array_status_equal (GArray *slots_status1, |
| GArray *slots_status2, |
| gboolean check_active_slots_only) |
| { |
| guint i; |
| |
| if (!slots_status1 && !slots_status2) |
| return TRUE; |
| if (!slots_status1 || !slots_status2 || slots_status1->len != slots_status2->len) |
| return FALSE; |
| for (i = 0; i < slots_status1->len; i++) { |
| /* Compare slot at index i from slots_status1 and slots_status2 */ |
| QmiPhysicalSlotStatusSlot *slot_a; |
| QmiPhysicalSlotStatusSlot *slot_b; |
| |
| slot_a = &g_array_index (slots_status1, QmiPhysicalSlotStatusSlot, i); |
| slot_b = &g_array_index (slots_status2, QmiPhysicalSlotStatusSlot, i); |
| |
| /* Check that slot_a and slot_b have the same slot status (i.e. active or inactive) */ |
| if (slot_a->physical_slot_status != slot_b->physical_slot_status) |
| return FALSE; |
| /* Once slot_a and slot_b are confirmed to have the same physical slot status, |
| * we will ignore inactive slots if check_active_slots_only is set. */ |
| if (check_active_slots_only && slot_a->physical_slot_status != QMI_UIM_SLOT_STATE_ACTIVE) { |
| g_assert (slot_a->physical_slot_status == slot_b->physical_slot_status); |
| continue; |
| } |
| if (!slot_status_equal (slot_a, slot_b)) |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| static void |
| uim_slot_status_indication_cb (QmiClientUim *client, |
| QmiIndicationUimSlotStatusOutput *output, |
| MMSharedQmi *self) |
| { |
| GArray *new_slots_status = NULL; |
| Private *priv; |
| guint i; |
| g_autoptr(GError) error = NULL; |
| |
| priv = get_private (self); |
| mm_obj_dbg (self, "received slot status indication"); |
| |
| if (!priv->slots_status) { |
| mm_obj_dbg (self, "initial slot status is not loaded yet"); |
| return; |
| } |
| |
| if (!qmi_indication_uim_slot_status_output_get_physical_slot_status (output, |
| &new_slots_status, |
| &error)) { |
| mm_obj_warn (self, "could not process slot status indication: %s", error->message); |
| return; |
| } |
| |
| /* A slot status indication means that |
| * 1) The physical slot to logical slot mapping has changed as a |
| * result of switching the slot. or, |
| * 2) A card has been removed from, or inserted to, the physical slot. or, |
| * 3) A physical slot is powered up or down. */ |
| |
| /* Reprobe if the active slot changed or the information in an |
| * active slot's status changed */ |
| if (!slot_array_status_equal (priv->slots_status, new_slots_status, TRUE)) { |
| mm_obj_dbg (self, "An active slot had a status change, will reprobe the modem"); |
| mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self)); |
| return; |
| } |
| |
| /* An inactive slot changed, we won't be reprobing the modem. |
| * Instead, we will ask mm_iface_modem to update the sim object. |
| * Iterate over each slot to identify the slots that changed state.*/ |
| for (i = 0; i < priv->slots_status->len; i++) { |
| QmiPhysicalSlotStatusSlot *old_slot; |
| QmiPhysicalSlotStatusSlot *new_slot; |
| |
| old_slot = &g_array_index (priv->slots_status, QmiPhysicalSlotStatusSlot, i); |
| new_slot = &g_array_index (new_slots_status, QmiPhysicalSlotStatusSlot, i); |
| |
| if (!slot_status_equal (old_slot, new_slot)) { |
| mm_obj_dbg (self, "Slot %d (inactive) had a status change. Will update sims, but not reprobe", i + 1); |
| update_sim_from_slot_status (self, new_slot, i); |
| } |
| } |
| |
| g_clear_pointer (&priv->slots_status, g_array_unref); |
| priv->slots_status = g_array_ref (new_slots_status); |
| } |
| |
| /*****************************************************************************/ |
| /* SIM hot swap setup */ |
| |
| typedef enum { |
| SETUP_SIM_HOT_SWAP_STEP_FIRST, |
| SETUP_SIM_HOT_SWAP_STEP_UIM_REGISTER_SLOT_STATUS, |
| SETUP_SIM_HOT_SWAP_STEP_UIM_CHECK_SLOT_STATUS, |
| SETUP_SIM_HOT_SWAP_STEP_UIM_SLOT_STATUS_INDICATION, |
| SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ALL, |
| SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ICCID, |
| SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_IMSI, |
| SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_INDICATION, |
| SETUP_SIM_HOT_SWAP_STEP_LAST, |
| } SetupSimHotSwapStep; |
| |
| typedef struct { |
| SetupSimHotSwapStep step; |
| gboolean register_slot_status_supported; |
| gboolean get_slot_status_supported; |
| gboolean refresh_all_supported; |
| gboolean refresh_file_supported; |
| QmiClient *uim_client; |
| gulong uim_slot_status_indication_id; |
| gulong uim_refresh_indication_id; |
| } SetupSimHotSwapContext; |
| |
| static void setup_sim_hot_swap_step (GTask *task); |
| |
| static void |
| setup_sim_hot_swap_context_free (SetupSimHotSwapContext *ctx) |
| { |
| if (ctx->uim_client && ctx->uim_slot_status_indication_id) |
| g_signal_handler_disconnect (ctx->uim_client, ctx->uim_slot_status_indication_id); |
| if (ctx->uim_client && ctx->uim_refresh_indication_id) |
| g_signal_handler_disconnect (ctx->uim_client, ctx->uim_refresh_indication_id); |
| g_clear_object (&ctx->uim_client); |
| g_slice_free (SetupSimHotSwapContext, ctx); |
| } |
| |
| gboolean |
| mm_shared_qmi_setup_sim_hot_swap_finish (MMIfaceModem *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| uim_refresh_register_file_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| g_autoptr(QmiMessageUimRefreshRegisterOutput) output = NULL; |
| g_autoptr(GError) error = NULL; |
| SetupSimHotSwapContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_uim_refresh_register_finish (client, res, &error); |
| if (!output || !qmi_message_uim_refresh_register_output_get_result (output, &error)) |
| mm_obj_dbg (self, "file refresh registration using 'refresh register' failed: %s", error->message); |
| else { |
| mm_obj_dbg (self, "file refresh registered using 'refresh register'"); |
| ctx->refresh_file_supported = TRUE; |
| } |
| |
| /* Go on to next step */ |
| ctx->step++; |
| setup_sim_hot_swap_step (task); |
| } |
| |
| static void |
| uim_refresh_register_file (GTask *task, |
| guint16 file_id, |
| const guint16 *file_path, |
| gsize file_path_len) |
| { |
| MMSharedQmi *self; |
| SetupSimHotSwapContext *ctx; |
| QmiMessageUimRefreshRegisterInputInfoFilesElement file_element; |
| guint8 val; |
| gsize i; |
| g_autoptr(QmiMessageUimRefreshRegisterInput) refresh_register_input = NULL; |
| g_autoptr(GArray) placeholder_aid = NULL; |
| g_autoptr(GArray) file = NULL; |
| g_autoptr(GArray) file_element_path = NULL; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* This is the last resort if 'refresh register all' does not work. It works |
| * on some older modems. Those modems may not also support QMI_UIM_SESSION_TYPE_CARD_SLOT_1 |
| * so we'll use QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING */ |
| |
| mm_obj_dbg (self, "register for refresh file indication"); |
| |
| placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); |
| |
| file = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageUimRefreshRegisterInputInfoFilesElement), 1); |
| |
| file_element_path = g_array_sized_new (FALSE, FALSE, sizeof (guint8), file_path_len * 2); |
| for (i = 0; i < file_path_len; ++i) { |
| val = file_path[i] & 0xFF; |
| g_array_append_val (file_element_path, val); |
| val = (file_path[i] >> 8) & 0xFF; |
| g_array_append_val (file_element_path, val); |
| } |
| |
| |
| memset (&file_element, 0, sizeof (file_element)); |
| file_element.file_id = file_id; |
| file_element.path = file_element_path; |
| g_array_append_val (file, file_element); |
| |
| refresh_register_input = qmi_message_uim_refresh_register_input_new (); |
| qmi_message_uim_refresh_register_input_set_info (refresh_register_input, |
| TRUE, |
| FALSE, |
| file, |
| NULL); |
| qmi_message_uim_refresh_register_input_set_session (refresh_register_input, |
| QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING, |
| placeholder_aid, |
| NULL); |
| |
| qmi_client_uim_refresh_register (QMI_CLIENT_UIM (ctx->uim_client), |
| refresh_register_input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) uim_refresh_register_file_ready, |
| task); |
| } |
| |
| /* Refresh registration and event handling. |
| * This is used for detecting ICCID and IMSI change. |
| */ |
| |
| static void |
| uim_refresh_register_all_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(QmiMessageUimRefreshRegisterAllOutput) output = NULL; |
| g_autoptr(GError) error = NULL; |
| MMIfaceModem *self; |
| SetupSimHotSwapContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_uim_refresh_register_all_finish (client, res, &error); |
| if (!output || !qmi_message_uim_refresh_register_all_output_get_result (output, &error)) { |
| mm_obj_dbg (self, "refresh register all operation failed: %s", error->message); |
| } else { |
| /* Jump to setup refresh indication signal */ |
| mm_obj_dbg (self, "registered for all SIM refresh events"); |
| ctx->refresh_all_supported = TRUE; |
| } |
| |
| ctx->step++; |
| setup_sim_hot_swap_step (task); |
| } |
| |
| static void |
| uim_check_get_slot_status_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(QmiMessageUimGetSlotStatusOutput) output = NULL; |
| g_autoptr(GError) error = NULL; |
| MMIfaceModem *self; |
| SetupSimHotSwapContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_uim_get_slot_status_finish (client, res, &error); |
| if (!output || !qmi_message_uim_get_slot_status_output_get_result (output, &error)) { |
| mm_obj_dbg (self, "slot status retrieval failed: %s", error->message); |
| } else { |
| /* Go on to next step */ |
| mm_obj_dbg (self, "slot status retrieval succeeded"); |
| ctx->get_slot_status_supported = TRUE; |
| } |
| |
| ctx->step++; |
| setup_sim_hot_swap_step (task); |
| } |
| |
| static void |
| uim_register_events_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(QmiMessageUimRegisterEventsOutput) output = NULL; |
| g_autoptr(GError) error = NULL; |
| MMIfaceModem *self; |
| SetupSimHotSwapContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* If event registration fails, go on with initialization. In that case |
| * we cannot use slot status indications to detect eUICC profile switches. */ |
| output = qmi_client_uim_register_events_finish (client, res, &error); |
| if (!output || !qmi_message_uim_register_events_output_get_result (output, &error)) { |
| mm_obj_dbg (self, "not registered for slot status indications: %s", error->message); |
| } else { |
| mm_obj_dbg (self, "registered for slot status indications"); |
| ctx->register_slot_status_supported = TRUE; |
| } |
| |
| /* Go on to next step */ |
| ctx->step++; |
| setup_sim_hot_swap_step (task); |
| } |
| |
| static void |
| setup_sim_hot_swap_step (GTask *task) |
| { |
| MMSharedQmi *self; |
| Private *priv; |
| SetupSimHotSwapContext *ctx; |
| |
| self = g_task_get_source_object (task); |
| priv = get_private (MM_SHARED_QMI (self)); |
| ctx = g_task_get_task_data (task); |
| |
| switch (ctx->step) { |
| case SETUP_SIM_HOT_SWAP_STEP_FIRST: |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_SIM_HOT_SWAP_STEP_UIM_REGISTER_SLOT_STATUS: { |
| g_autoptr(QmiMessageUimRegisterEventsInput) register_events_input = NULL; |
| |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for slot status indications...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| register_events_input = qmi_message_uim_register_events_input_new (); |
| qmi_message_uim_register_events_input_set_event_registration_mask (register_events_input, |
| QMI_UIM_EVENT_REGISTRATION_FLAG_PHYSICAL_SLOT_STATUS, |
| NULL); |
| qmi_client_uim_register_events (QMI_CLIENT_UIM (ctx->uim_client), |
| register_events_input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) uim_register_events_ready, |
| task); |
| return; |
| } |
| |
| case SETUP_SIM_HOT_SWAP_STEP_UIM_CHECK_SLOT_STATUS: |
| if (ctx->register_slot_status_supported) { |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): checking slot status support...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| /* Successful registration does not mean that the modem actually sends |
| * physical slot status indications; invoke Get Slot Status to find out if |
| * the modem really supports slot status. */ |
| qmi_client_uim_get_slot_status (QMI_CLIENT_UIM (ctx->uim_client), |
| NULL, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) uim_check_get_slot_status_ready, |
| task); |
| return; |
| } |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to check for slot status support...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_SIM_HOT_SWAP_STEP_UIM_SLOT_STATUS_INDICATION: |
| if (ctx->register_slot_status_supported && ctx->get_slot_status_supported) { |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): monitoring slot status indications...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| ctx->uim_slot_status_indication_id = g_signal_connect (ctx->uim_client, |
| "slot-status", |
| G_CALLBACK (uim_slot_status_indication_cb), |
| self); |
| } else |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to monitor for slot status indications...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ALL: { |
| g_autoptr(QmiMessageUimRefreshRegisterAllInput) refresh_register_all_input = NULL; |
| g_autoptr(GArray) placeholder_aid = NULL; |
| |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for all refresh events...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| placeholder_aid = g_array_new (FALSE, FALSE, sizeof (guint8)); |
| refresh_register_all_input = qmi_message_uim_refresh_register_all_input_new (); |
| |
| qmi_message_uim_refresh_register_all_input_set_info (refresh_register_all_input, |
| TRUE, |
| NULL); |
| qmi_message_uim_refresh_register_all_input_set_session (refresh_register_all_input, |
| QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING, |
| placeholder_aid, |
| NULL); |
| |
| qmi_client_uim_refresh_register_all (QMI_CLIENT_UIM (ctx->uim_client), |
| refresh_register_all_input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback) uim_refresh_register_all_ready, |
| task); |
| return; |
| } |
| |
| case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_ICCID: |
| /* If refresh all not supported, register for a single file refresh notification */ |
| if (!ctx->refresh_all_supported) { |
| const guint16 file_path[] = { 0x3F00 }; |
| |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for SIM ICCID refresh events...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| uim_refresh_register_file (task, 0x2FE2, file_path, G_N_ELEMENTS (file_path)); |
| return; |
| } |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to register for SIM ICCID refresh events", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_REGISTER_IMSI: |
| /* If refresh all not supported, register for a single file refresh notification */ |
| if (!ctx->refresh_all_supported) { |
| const guint16 file_path[] = { 0x3F00, 0x7FFF }; |
| |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): registering for SIM IMSI refresh events...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| uim_refresh_register_file (task, 0x6F07, file_path, G_N_ELEMENTS (file_path)); |
| return; |
| } |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to register for SIM IMSI refresh events...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_SIM_HOT_SWAP_STEP_UIM_REFRESH_INDICATION: |
| if (ctx->refresh_all_supported || ctx->refresh_file_supported) { |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): monitoring refresh indications...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| ctx->uim_refresh_indication_id = |
| g_signal_connect (ctx->uim_client, |
| "refresh", |
| G_CALLBACK (uim_refresh_indication_cb), |
| self); |
| } else |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): no need to monitor for refresh indications...", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| |
| ctx->step++; |
| /* fall-through */ |
| |
| case SETUP_SIM_HOT_SWAP_STEP_LAST: |
| if (ctx->refresh_all_supported || |
| ctx->refresh_file_supported || |
| (ctx->register_slot_status_supported && ctx->get_slot_status_supported)) { |
| /* at least one method was supported, transfer state to the private |
| * info and return success */ |
| g_assert (!priv->uim_client); |
| priv->uim_client = g_steal_pointer (&ctx->uim_client); |
| if (ctx->uim_slot_status_indication_id) { |
| g_assert (!priv->uim_slot_status_indication_id); |
| priv->uim_slot_status_indication_id = ctx->uim_slot_status_indication_id; |
| ctx->uim_slot_status_indication_id = 0; |
| } |
| if (ctx->uim_refresh_indication_id) { |
| g_assert (!priv->uim_refresh_indication_id); |
| priv->uim_refresh_indication_id = ctx->uim_refresh_indication_id; |
| ctx->uim_refresh_indication_id = 0; |
| } |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): successfully finished", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| g_task_return_boolean (task, TRUE); |
| } else { |
| mm_obj_dbg (self, "setup SIM hot swap (%u/%u): failed", |
| ctx->step, SETUP_SIM_HOT_SWAP_STEP_LAST); |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Couldn't setup SIM hot swap"); |
| } |
| g_object_unref (task); |
| return; |
| |
| default: |
| g_assert_not_reached (); |
| } |
| } |
| |
| void |
| mm_shared_qmi_setup_sim_hot_swap (MMIfaceModem *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| QmiClient *client = NULL; |
| SetupSimHotSwapContext *ctx; |
| |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_UIM, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| ctx = g_slice_new0 (SetupSimHotSwapContext); |
| ctx->step = SETUP_SIM_HOT_SWAP_STEP_FIRST; |
| ctx->uim_client = g_object_ref (client); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)setup_sim_hot_swap_context_free); |
| |
| setup_sim_hot_swap_step (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Set packet service state (3GPP interface) */ |
| |
| typedef struct { |
| QmiClientNas *client; |
| MMModem3gppPacketServiceState packet_service_state; |
| } SetPacketServiceStateContext; |
| |
| static void |
| set_packet_service_state_context_free (SetPacketServiceStateContext *ctx) |
| { |
| g_object_unref (ctx->client); |
| g_slice_free (SetPacketServiceStateContext, ctx); |
| } |
| |
| gboolean |
| mm_shared_qmi_set_packet_service_state_finish (MMIfaceModem3gpp *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| set_packet_service_state_ia_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(GError) error = NULL; |
| g_autoptr(QmiMessageNasAttachDetachOutput) output = NULL; |
| |
| output = qmi_client_nas_attach_detach_finish (client, res, &error); |
| if ((!output || !qmi_message_nas_attach_detach_output_get_result (output, &error)) && |
| !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { |
| g_prefix_error (&error, "Couldn't set packet service state: "); |
| g_task_return_error (task, g_steal_pointer (&error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| set_packet_service_state_ia (GTask *task) |
| { |
| g_autoptr(QmiMessageNasAttachDetachInput) input = NULL; |
| SetPacketServiceStateContext *ctx; |
| |
| input = qmi_message_nas_attach_detach_input_new (); |
| ctx = g_task_get_task_data (task); |
| |
| if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) |
| qmi_message_nas_attach_detach_input_set_action (input, QMI_NAS_PS_ATTACH_ACTION_ATTACH, NULL); |
| else if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED) |
| qmi_message_nas_attach_detach_input_set_action (input, QMI_NAS_PS_ATTACH_ACTION_DETACH, NULL); |
| else |
| g_assert_not_reached (); |
| |
| qmi_client_nas_attach_detach ( |
| ctx->client, |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)set_packet_service_state_ia_ready, |
| task); |
| } |
| |
| static void |
| set_packet_service_state_sssp_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(GError) error = NULL; |
| g_autoptr(QmiMessageNasSetSystemSelectionPreferenceOutput) output = NULL; |
| |
| output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); |
| if ((!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) && |
| !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { |
| g_prefix_error (&error, "Couldn't set packet service state: "); |
| g_task_return_error (task, g_steal_pointer (&error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| set_packet_service_state_sssp (GTask *task) |
| { |
| g_autoptr(QmiMessageNasSetSystemSelectionPreferenceInput) input = NULL; |
| SetPacketServiceStateContext *ctx; |
| |
| input = qmi_message_nas_set_system_selection_preference_input_new (); |
| ctx = g_task_get_task_data (task); |
| |
| if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) |
| qmi_message_nas_set_system_selection_preference_input_set_service_domain_preference ( |
| input, QMI_NAS_SERVICE_DOMAIN_PREFERENCE_PS_ATTACH, NULL); |
| else if (ctx->packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED) |
| qmi_message_nas_set_system_selection_preference_input_set_service_domain_preference ( |
| input, QMI_NAS_SERVICE_DOMAIN_PREFERENCE_PS_DETACH, NULL); |
| else |
| g_assert_not_reached (); |
| |
| qmi_client_nas_set_system_selection_preference ( |
| ctx->client, |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)set_packet_service_state_sssp_ready, |
| task); |
| } |
| |
| void |
| mm_shared_qmi_set_packet_service_state (MMIfaceModem3gpp *self, |
| MMModem3gppPacketServiceState packet_service_state, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| SetPacketServiceStateContext *ctx; |
| QmiClient *client = NULL; |
| Private *priv = NULL; |
| |
| g_assert ((packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) || |
| (packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED)); |
| |
| /* Get NAS client */ |
| if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_NAS, &client, |
| callback, user_data)) |
| return; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| ctx = g_slice_new0 (SetPacketServiceStateContext); |
| ctx->client = QMI_CLIENT_NAS (g_object_ref (client)); |
| ctx->packet_service_state = packet_service_state; |
| g_task_set_task_data (task, ctx, (GDestroyNotify)set_packet_service_state_context_free); |
| |
| priv = get_private (MM_SHARED_QMI (self)); |
| if (priv->feature_nas_ssp == FEATURE_SUPPORTED) |
| set_packet_service_state_sssp (task); |
| else |
| set_packet_service_state_ia (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Location: Set SUPL server */ |
| |
| typedef struct { |
| QmiClient *client; |
| gchar *supl; |
| glong indication_id; |
| guint timeout_id; |
| } SetSuplServerContext; |
| |
| static void |
| set_supl_server_context_free (SetSuplServerContext *ctx) |
| { |
| if (ctx->client) { |
| if (ctx->timeout_id) |
| g_source_remove (ctx->timeout_id); |
| if (ctx->indication_id) |
| g_signal_handler_disconnect (ctx->client, ctx->indication_id); |
| g_object_unref (ctx->client); |
| } |
| g_free (ctx->supl); |
| g_slice_free (SetSuplServerContext, ctx); |
| } |
| |
| static GArray * |
| parse_as_utf16_url (const gchar *supl) |
| { |
| GArray *url; |
| gchar *utf16; |
| gsize utf16_len; |
| |
| utf16 = g_convert (supl, -1, "UTF-16BE", "UTF-8", NULL, &utf16_len, NULL); |
| url = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (guint8), utf16_len), |
| utf16, utf16_len); |
| g_free (utf16); |
| return url; |
| } |
| |
| gboolean |
| mm_shared_qmi_location_set_supl_server_finish (MMIfaceModemLocation *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| pds_set_agps_config_ready (QmiClientPds *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessagePdsSetAgpsConfigOutput *output; |
| GError *error = NULL; |
| |
| output = qmi_client_pds_set_agps_config_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!qmi_message_pds_set_agps_config_output_get_result (output, &error)) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| |
| qmi_message_pds_set_agps_config_output_unref (output); |
| } |
| |
| static void |
| pds_set_supl_server (GTask *task) |
| { |
| MMSharedQmi *self; |
| SetSuplServerContext *ctx; |
| QmiMessagePdsSetAgpsConfigInput *input; |
| guint32 ip; |
| 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_free (str); |
| g_task_return_error (task, error); |
| } else { |
| g_assert (str); |
| g_task_return_pointer (task, str, g_free); |
| } |
| g_object_unref (task); |
| |
| if (output) |
| qmi_message_pds_get_agps_config_output_unref (output); |
| } |
| |
| static void |
| pds_load_supl_server (GTask *task) |
| { |
| MMSharedQmi *self; |
| LoadSuplServerContext *ctx; |
| QmiMessagePdsGetAgpsConfigInput *input; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_pds_get_agps_config_input_new (); |
| |
| /* For multimode devices, prefer UMTS by default */ |
| if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) |
| qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_UMTS, NULL); |
| else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) |
| qmi_message_pds_get_agps_config_input_set_network_mode (input, QMI_PDS_NETWORK_MODE_CDMA, NULL); |
| |
| qmi_client_pds_get_agps_config ( |
| QMI_CLIENT_PDS (ctx->client), |
| input, |
| 10, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)pds_get_agps_config_ready, |
| task); |
| qmi_message_pds_get_agps_config_input_unref (input); |
| } |
| |
| static gboolean |
| loc_location_get_server_indication_timed_out (GTask *task) |
| { |
| LoadSuplServerContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| ctx->timeout_id = 0; |
| |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, |
| "Failed to receive indication with the current server settings"); |
| g_object_unref (task); |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| loc_location_get_server_indication_cb (QmiClientLoc *client, |
| QmiIndicationLocGetServerOutput *output, |
| GTask *task) |
| { |
| QmiLocIndicationStatus status; |
| const gchar *url = NULL; |
| guint32 ipv4_address = 0; |
| guint16 ipv4_port = 0; |
| GError *error = NULL; |
| gchar *str = NULL; |
| |
| if (!qmi_indication_loc_get_server_output_get_indication_status (output, &status, &error)) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| goto out; |
| } |
| |
| if (!mm_error_from_qmi_loc_indication_status (status, &error)) |
| goto out; |
| |
| /* Prefer IP/PORT to URL */ |
| |
| if (qmi_indication_loc_get_server_output_get_ipv4 ( |
| output, |
| &ipv4_address, |
| &ipv4_port, |
| NULL) && |
| ipv4_address != 0 && ipv4_port != 0) { |
| struct in_addr a = { .s_addr = ipv4_address }; |
| gchar buf[INET_ADDRSTRLEN + 1]; |
| |
| memset (buf, 0, sizeof (buf)); |
| |
| if (!inet_ntop (AF_INET, &a, buf, sizeof (buf) - 1)) { |
| error = g_error_new (MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Cannot convert numeric IP address (%u) to string", ipv4_address); |
| goto out; |
| } |
| |
| str = g_strdup_printf ("%s:%u", buf, ipv4_port); |
| goto out; |
| } |
| |
| if (qmi_indication_loc_get_server_output_get_url ( |
| output, |
| &url, |
| NULL) && |
| url && url [0]) { |
| str = g_strdup (url); |
| } |
| |
| if (!str) |
| str = g_strdup (""); |
| |
| out: |
| if (error) { |
| g_free (str); |
| g_task_return_error (task, error); |
| } else { |
| g_assert (str); |
| g_task_return_pointer (task, str, g_free); |
| } |
| g_object_unref (task); |
| |
| } |
| |
| static void |
| loc_get_server_ready (QmiClientLoc *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| LoadSuplServerContext *ctx; |
| QmiMessageLocGetServerOutput *output; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| output = qmi_client_loc_get_server_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!qmi_message_loc_get_server_output_get_result (output, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| qmi_message_loc_get_server_output_unref (output); |
| return; |
| } |
| |
| /* The task ownership is shared between signal and timeout; the one which is |
| * scheduled first will cancel the other. */ |
| ctx->indication_id = g_signal_connect (ctx->client, |
| "get-server", |
| G_CALLBACK (loc_location_get_server_indication_cb), |
| task); |
| ctx->timeout_id = g_timeout_add_seconds (10, |
| (GSourceFunc)loc_location_get_server_indication_timed_out, |
| task); |
| |
| qmi_message_loc_get_server_output_unref (output); |
| } |
| |
| static void |
| loc_load_supl_server (GTask *task) |
| { |
| MMSharedQmi *self; |
| LoadSuplServerContext *ctx; |
| QmiMessageLocGetServerInput *input; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_loc_get_server_input_new (); |
| |
| /* For multimode devices, prefer UMTS by default */ |
| if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) |
| qmi_message_loc_get_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_UMTS_SLP, NULL); |
| else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) |
| qmi_message_loc_get_server_input_set_server_type (input, QMI_LOC_SERVER_TYPE_CDMA_PDE, NULL); |
| |
| qmi_message_loc_get_server_input_set_server_address_type ( |
| input, |
| (QMI_LOC_SERVER_ADDRESS_TYPE_IPV4 | QMI_LOC_SERVER_ADDRESS_TYPE_URL), |
| NULL); |
| |
| qmi_client_loc_get_server ( |
| QMI_CLIENT_LOC (ctx->client), |
| input, |
| 10, |
| NULL, /* cancellable */ |
| (GAsyncReadyCallback)loc_get_server_ready, |
| task); |
| qmi_message_loc_get_server_input_unref (input); |
| } |
| |
| void |
| mm_shared_qmi_location_load_supl_server (MMIfaceModemLocation *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| QmiClient *client; |
| GTask *task; |
| LoadSuplServerContext *ctx; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| ctx = g_slice_new0 (LoadSuplServerContext); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)load_supl_server_context_free); |
| |
| /* Prefer PDS */ |
| client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_PDS, |
| MM_PORT_QMI_FLAG_DEFAULT, |
| NULL); |
| if (client) { |
| ctx->client = g_object_ref (client); |
| pds_load_supl_server (task); |
| return; |
| } |
| |
| /* Otherwise LOC */ |
| client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), |
| QMI_SERVICE_LOC, |
| MM_PORT_QMI_FLAG_DEFAULT, |
| NULL); |
| if (client) { |
| ctx->client = g_object_ref (client); |
| loc_load_supl_server (task); |
| return; |
| } |
| |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Couldn't find any PDS/LOC client"); |
| g_object_unref (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Location: internal helper: stop gps engine */ |
| |
| static gboolean |
| stop_gps_engine_finish (MMSharedQmi *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| pds_gps_service_state_stop_ready (QmiClientPds *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| Private *priv; |
| QmiMessagePdsSetGpsServiceStateOutput *output; |
| GError *error = NULL; |
| |
| output = qmi_client_pds_set_gps_service_state_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!qmi_message_pds_set_gps_service_state_output_get_result (output, &error)) { |
| if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) { |
| g_prefix_error (&error, "Couldn't set GPS service state: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| qmi_message_pds_set_gps_service_state_output_unref (output); |
| return; |
| } |
| |
| g_error_free (error); |
| } |
| |
| qmi_message_pds_set_gps_service_state_output_unref (output); |
| |
| self = g_task_get_source_object (task); |
| priv = get_private (self); |
| |
| if (priv->pds_client) { |
| if (priv->pds_location_event_report_indication_id != 0) { |
| g_signal_handler_disconnect (priv->pds_client, priv->pds_location_event_report_indication_id); |
| priv->pds_location_event_report_indication_id = 0; |
| } |
| g_clear_object (&priv->pds_client); |
| } |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| loc_stop_ready (QmiClientLoc *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSharedQmi *self; |
| Private *priv; |
| QmiMessageLocStopOutput *output; |
| GError *error = NULL; |
| |
| output = qmi_client_loc_stop_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!qmi_message_loc_stop_output_get_result (output, &error)) { |
| g_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_strfreev (priv->loc_assistance_data_servers); |
| priv->loc_assistance_data_servers = (gchar **) g_ptr_array_free (tmp, FALSE); |
| |
| supported = TRUE; |
| } |
| |
| if (qmi_indication_loc_get_predicted_orbits_data_source_output_get_allowed_sizes ( |
| output, |
| &priv->loc_assistance_data_max_file_size, |
| &priv->loc_assistance_data_max_part_size, |
| NULL) && |
| priv->loc_assistance_data_max_file_size > 0 && |
| priv->loc_assistance_data_max_part_size > 0) { |
| supported = TRUE; |
| } |
| |
| out: |
| if (error) |
| g_task_return_error (task, error); |
| 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; |
| } |