diff options
author | Mathieu Trudel-Lapierre <mathieu-tl@ubuntu.com> | 2013-06-11 10:35:42 -0400 |
---|---|---|
committer | Guido Günther <agx@sigxcpu.org> | 2014-02-05 08:38:35 +0100 |
commit | ff30c94ae57db55d37453d4dd28d9eef2cfc5685 (patch) | |
tree | c04f1f7e3de8b1c0a4a08cd46c04be04611bbca1 /src/mm-broadband-modem-qmi.c | |
parent | 995105da294e1a7ea6cf6f640455dd9b73b08f49 (diff) | |
parent | 13ed135b9ae78c692dc359976eb8b54d0a3629b8 (diff) |
Imported Debian patch 0.7.991-1debian/0.7.991-1
Diffstat (limited to 'src/mm-broadband-modem-qmi.c')
-rw-r--r-- | src/mm-broadband-modem-qmi.c | 8693 |
1 files changed, 8693 insertions, 0 deletions
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c new file mode 100644 index 0000000..cf63578 --- /dev/null +++ b/src/mm-broadband-modem-qmi.c @@ -0,0 +1,8693 @@ +/* -*- 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) 2012 Google Inc. + */ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> + +#include "mm-broadband-modem-qmi.h" + +#include "ModemManager.h" +#include "mm-log.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-qmi.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-3gpp.h" +#include "mm-iface-modem-3gpp-ussd.h" +#include "mm-iface-modem-cdma.h" +#include "mm-iface-modem-messaging.h" +#include "mm-iface-modem-location.h" +#include "mm-iface-modem-firmware.h" +#include "mm-sim-qmi.h" +#include "mm-bearer-qmi.h" +#include "mm-sms-qmi.h" + +static void iface_modem_init (MMIfaceModem *iface); +static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface); +static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface); +static void iface_modem_cdma_init (MMIfaceModemCdma *iface); +static void iface_modem_messaging_init (MMIfaceModemMessaging *iface); +static void iface_modem_location_init (MMIfaceModemLocation *iface); +static void iface_modem_firmware_init (MMIfaceModemFirmware *iface); + +static MMIfaceModemLocation *iface_modem_location_parent; + +G_DEFINE_TYPE_EXTENDED (MMBroadbandModemQmi, mm_broadband_modem_qmi, MM_TYPE_BROADBAND_MODEM, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_CDMA, iface_modem_cdma_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init)) + +struct _MMBroadbandModemQmiPrivate { + /* Cached device IDs, retrieved by the modem interface when loading device + * IDs, and used afterwards in the 3GPP and CDMA interfaces. */ + gchar *imei; + gchar *meid; + gchar *esn; + + /* Cached supported radio interfaces; in order to load supported modes */ + GArray *supported_radio_interfaces; + + /* Cached supported frequency bands; in order to handle ANY */ + GArray *supported_bands; + + /* 3GPP and CDMA share unsolicited events setup/enable/disable/cleanup */ + gboolean unsolicited_events_enabled; + gboolean unsolicited_events_setup; + guint event_report_indication_id; +#if defined WITH_NEWEST_QMI_COMMANDS + guint signal_info_indication_id; +#endif /* WITH_NEWEST_QMI_COMMANDS */ + + /* 3GPP/CDMA registration helpers */ + gchar *current_operator_id; + gchar *current_operator_description; + gboolean unsolicited_registration_events_enabled; + gboolean unsolicited_registration_events_setup; + guint serving_system_indication_id; +#if defined WITH_NEWEST_QMI_COMMANDS + guint system_info_indication_id; +#endif /* WITH_NEWEST_QMI_COMMANDS */ + + /* CDMA activation helpers */ + MMModemCdmaActivationState activation_state; + guint activation_event_report_indication_id; + gpointer activation_ctx; + + /* Messaging helpers */ + gboolean messaging_unsolicited_events_enabled; + gboolean messaging_unsolicited_events_setup; + guint messaging_event_report_indication_id; + + /* Location helpers */ + MMModemLocationSource enabled_sources; + guint location_event_report_indication_id; + + /* Firmware helpers */ + GList *firmware_list; + MMFirmwareProperties *current_firmware; +}; + +/*****************************************************************************/ + +static QmiClient * +peek_qmi_client (MMBroadbandModemQmi *self, + QmiService service, + GError **error) +{ + MMQmiPort *port; + QmiClient *client; + + port = mm_base_modem_peek_port_qmi (MM_BASE_MODEM (self)); + if (!port) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't peek QMI port"); + return NULL; + } + + client = mm_qmi_port_peek_client (port, + service, + MM_QMI_PORT_FLAG_DEFAULT); + if (!client) + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't peek client for service '%s'", + qmi_service_get_string (service)); + + return client; +} + +static gboolean +ensure_qmi_client (MMBroadbandModemQmi *self, + QmiService service, + QmiClient **o_client, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GError *error = NULL; + QmiClient *client; + + client = peek_qmi_client (self, service, &error); + if (!client) { + g_simple_async_report_take_gerror_in_idle ( + G_OBJECT (self), + callback, + user_data, + error); + return FALSE; + } + + *o_client = client; + return TRUE; +} + +/*****************************************************************************/ +/* Power cycle */ + +static gboolean +power_cycle_finish (MMBroadbandModemQmi *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +power_cycle_set_operating_mode_reset_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsSetOperatingModeOutput *output; + GError *error = NULL; + + 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_simple_async_result_take_error (simple, error); + } else { + mm_info ("Modem is being rebooted now"); + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } + + if (output) + qmi_message_dms_set_operating_mode_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +power_cycle_set_operating_mode_offline_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsSetOperatingModeInput *input; + QmiMessageDmsSetOperatingModeOutput *output; + GError *error = NULL; + + output = qmi_client_dms_set_operating_mode_finish (client, res, &error); + if (!output) { + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + return; + } + + if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) { + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + 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)power_cycle_set_operating_mode_reset_ready, + simple); + qmi_message_dms_set_operating_mode_input_unref (input); +} + +static void +power_cycle (MMBroadbandModemQmi *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiMessageDmsSetOperatingModeInput *input; + GSimpleAsyncResult *simple; + QmiClient *client; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + simple = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + power_cycle); + + /* 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)power_cycle_set_operating_mode_offline_ready, + simple); + qmi_message_dms_set_operating_mode_input_unref (input); +} + +/*****************************************************************************/ +/* Create Bearer (Modem interface) */ + +static MMBearer * +modem_create_bearer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + MMBearer *bearer; + + bearer = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); + mm_dbg ("New bearer created at DBus path '%s'", mm_bearer_get_path (bearer)); + + return g_object_ref (bearer); +} + +static void +modem_create_bearer (MMIfaceModem *self, + MMBearerProperties *properties, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBearer *bearer; + GSimpleAsyncResult *result; + + /* Set a new ref to the bearer object as result */ + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_create_bearer); + + /* We just create a MMBearerQmi */ + mm_dbg ("Creating QMI bearer in QMI modem"); + bearer = mm_bearer_qmi_new (MM_BROADBAND_MODEM_QMI (self), + properties); + + g_simple_async_result_set_op_res_gpointer (result, bearer, g_object_unref); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Current Capabilities loading (Modem interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *nas_client; + QmiClientDms *dms_client; + GSimpleAsyncResult *result; + gboolean run_get_system_selection_preference; + gboolean run_get_technology_preference; + + MMQmiCapabilitiesContext capabilities_context; +} LoadCurrentCapabilitiesContext; + +static MMModemCapability +modem_load_current_capabilities_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_CAPABILITY_NONE; + + return ((MMModemCapability) GPOINTER_TO_UINT ( + g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res)))); +} + +static void +load_current_capabilities_context_complete_and_free (LoadCurrentCapabilitiesContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->nas_client); + g_object_unref (ctx->dms_client); + g_object_unref (ctx->self); + g_slice_free (LoadCurrentCapabilitiesContext, ctx); +} + +static void load_current_capabilities_context_step (LoadCurrentCapabilitiesContext *ctx); + +static void +load_current_capabilities_get_capabilities_ready (QmiClientDms *client, + GAsyncResult *res, + LoadCurrentCapabilitiesContext *ctx) +{ + QmiMessageDmsGetCapabilitiesOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_get_capabilities_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + } else if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get Capabilities: "); + g_simple_async_result_take_error (ctx->result, error); + } else { + guint i; + GArray *radio_interface_list; + + 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); + + 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)); + } + } + + if (output) + qmi_message_dms_get_capabilities_output_unref (output); + + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + GUINT_TO_POINTER (mm_modem_capability_from_qmi_capabilities_context (&ctx->capabilities_context)), + NULL); + load_current_capabilities_context_complete_and_free (ctx); +} + +static void +load_current_capabilities_get_technology_preference_ready (QmiClientNas *client, + GAsyncResult *res, + LoadCurrentCapabilitiesContext *ctx) +{ + QmiMessageNasGetTechnologyPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_get_technology_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_get_technology_preference_output_get_result (output, &error)) { + mm_dbg ("Couldn't get technology preference: %s", error->message); + g_error_free (error); + } else { + qmi_message_nas_get_technology_preference_output_get_active ( + output, + &ctx->capabilities_context.nas_tp_mask, + NULL, /* duration */ + NULL); + } + + if (output) + qmi_message_nas_get_technology_preference_output_unref (output); + + /* Mark as TP already run */ + ctx->run_get_technology_preference = FALSE; + load_current_capabilities_context_step (ctx); +} + +static void +load_current_capabilities_get_system_selection_preference_ready (QmiClientNas *client, + GAsyncResult *res, + LoadCurrentCapabilitiesContext *ctx) +{ + QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("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_dbg ("Couldn't get system selection preference: %s", error->message); + g_error_free (error); + } else { + 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); + + /* Mark as SSP already run */ + ctx->run_get_system_selection_preference = FALSE; + load_current_capabilities_context_step (ctx); +} + +static void +load_current_capabilities_context_step (LoadCurrentCapabilitiesContext *ctx) +{ + if (ctx->run_get_system_selection_preference) { + qmi_client_nas_get_system_selection_preference ( + ctx->nas_client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)load_current_capabilities_get_system_selection_preference_ready, + ctx); + return; + } + + if (ctx->run_get_technology_preference) { + qmi_client_nas_get_technology_preference ( + ctx->nas_client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)load_current_capabilities_get_technology_preference_ready, + ctx); + return; + } + + qmi_client_dms_get_capabilities ( + ctx->dms_client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)load_current_capabilities_get_capabilities_ready, + ctx); +} + +static void +modem_load_current_capabilities (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadCurrentCapabilitiesContext *ctx; + QmiClient *nas_client = NULL; + QmiClient *dms_client = NULL; + + /* Best way to get current capabilities (ie, enabled radios) is + * Get System Selection Preference's "mode preference" TLV, but that's + * only supported by NAS >= 1.1, meaning older Gobi devices don't + * implement it. + * + * On these devices, the DMS Get Capabilities call appears to report + * currently enabled radios, but this does not take the user's + * technology preference into account. + * + * So in the absence of System Selection Preference, we check the + * Technology Preference first, and if that is "AUTO" we fall back to + * Get Capabilities. + */ + + mm_dbg ("loading current capabilities..."); + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &nas_client, + callback, user_data)) + return; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &dms_client, + callback, user_data)) + return; + + ctx = g_slice_new0 (LoadCurrentCapabilitiesContext); + ctx->self = g_object_ref (self); + ctx->nas_client = g_object_ref (nas_client); + ctx->dms_client = g_object_ref (dms_client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_current_capabilities); + + /* System selection preference introduced in NAS 1.1 */ + ctx->run_get_system_selection_preference = qmi_client_check_version (nas_client, 1, 1); + ctx->run_get_technology_preference = TRUE; + + load_current_capabilities_context_step (ctx); +} + +/*****************************************************************************/ +/* Supported capabilities loading (Modem interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + GSimpleAsyncResult *result; +} LoadSupportedCapabilitiesContext; + +static void +load_supported_capabilities_context_complete_and_free (LoadSupportedCapabilitiesContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_slice_free (LoadSupportedCapabilitiesContext, ctx); +} + +static GArray * +modem_load_supported_capabilities_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + return g_array_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); +} + +static void +dms_get_capabilities_ready (QmiClientDms *client, + GAsyncResult *res, + LoadSupportedCapabilitiesContext *ctx) +{ + QmiMessageDmsGetCapabilitiesOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_get_capabilities_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + } else if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get supported capabilities: "); + g_simple_async_result_take_error (ctx->result, error); + } else { + guint i; + MMModemCapability mask = MM_MODEM_CAPABILITY_NONE; + MMModemCapability single; + GArray *radio_interface_list; + GArray *supported_combinations; + GArray *filtered_combinations; + + 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); + + for (i = 0; i < radio_interface_list->len; i++) { + mask |= mm_modem_capability_from_qmi_radio_interface (g_array_index (radio_interface_list, + QmiDmsRadioInterface, + i)); + } + + /* Cache supported radio interfaces */ + if (ctx->self->priv->supported_radio_interfaces) + g_array_unref (ctx->self->priv->supported_radio_interfaces); + ctx->self->priv->supported_radio_interfaces = g_array_ref (radio_interface_list); + + supported_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), 7); + + /* Add all possible supported capability combinations, we will filter + * them out afterwards */ + + /* GSM/UMTS */ + single = MM_MODEM_CAPABILITY_GSM_UMTS; + g_array_append_val (supported_combinations, single); + /* CDMA/EVDO */ + single = MM_MODEM_CAPABILITY_CDMA_EVDO; + g_array_append_val (supported_combinations, single); + /* LTE only */ + single = MM_MODEM_CAPABILITY_LTE; + g_array_append_val (supported_combinations, single); + /* GSM/UMTS + CDMA/EVDO */ + single = (MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_GSM_UMTS); + g_array_append_val (supported_combinations, single); + /* GSM/UMTS + LTE */ + single = (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE); + g_array_append_val (supported_combinations, single); + /* CDMA/EVDO + LTE */ + single = (MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE); + g_array_append_val (supported_combinations, single); + /* GSM/UMTS + CDMA/EVDO + LTE */ + single = (MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE); + g_array_append_val (supported_combinations, single); + + /* Now filter out based on the real capabilities of the modem */ + filtered_combinations = mm_filter_supported_capabilities (mask, + supported_combinations); + g_array_unref (supported_combinations); + + g_simple_async_result_set_op_res_gpointer (ctx->result, + filtered_combinations, + (GDestroyNotify) g_array_unref); + } + + if (output) + qmi_message_dms_get_capabilities_output_unref (output); + + load_supported_capabilities_context_complete_and_free (ctx); +} + +static void +modem_load_supported_capabilities (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadSupportedCapabilitiesContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + ctx = g_slice_new (LoadSupportedCapabilitiesContext); + ctx->self = g_object_ref (self); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_supported_capabilities); + + mm_dbg ("loading supported capabilities..."); + qmi_client_dms_get_capabilities (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_capabilities_ready, + ctx); +} + +/*****************************************************************************/ +/* Current capabilities setting (Modem interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *client; + GSimpleAsyncResult *result; + MMModemCapability capabilities; + gboolean run_set_system_selection_preference; + gboolean run_set_technology_preference; +} SetCurrentCapabilitiesContext; + +static void +set_current_capabilities_context_complete_and_free (SetCurrentCapabilitiesContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (SetCurrentCapabilitiesContext, ctx); +} + +static gboolean +set_current_capabilities_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +capabilities_power_cycle_ready (MMBroadbandModemQmi *self, + GAsyncResult *res, + SetCurrentCapabilitiesContext *ctx) +{ + GError *error = NULL; + + if (!power_cycle_finish (self, res, &error)) + g_simple_async_result_take_error (ctx->result, error); + else + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + set_current_capabilities_context_complete_and_free (ctx); +} + +static void +capabilities_power_cycle (SetCurrentCapabilitiesContext *ctx) +{ + /* Power cycle the modem */ + power_cycle (ctx->self, + (GAsyncReadyCallback)capabilities_power_cycle_ready, + ctx); +} + +static void set_current_capabilities_context_step (SetCurrentCapabilitiesContext *ctx); + +static void +capabilities_set_technology_preference_ready (QmiClientNas *client, + GAsyncResult *res, + SetCurrentCapabilitiesContext *ctx) +{ + QmiMessageNasSetTechnologyPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_set_technology_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_set_technology_preference_output_get_result (output, &error) && + !g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + mm_dbg ("Couldn't set technology preference: %s", error->message); + g_error_free (error); + qmi_message_nas_set_technology_preference_output_unref (output); + } else { + if (error) + g_error_free (error); + + /* Good! now reboot the modem */ + capabilities_power_cycle (ctx); + qmi_message_nas_set_technology_preference_output_unref (output); + return; + } + + ctx->run_set_technology_preference = FALSE; + set_current_capabilities_context_step (ctx); +} + +static void +capabilities_set_system_selection_preference_ready (QmiClientNas *client, + GAsyncResult *res, + SetCurrentCapabilitiesContext *ctx) +{ + QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) { + mm_dbg ("Couldn't set system selection preference: %s", error->message); + g_error_free (error); + qmi_message_nas_set_system_selection_preference_output_unref (output); + } else { + /* Good! now reboot the modem */ + capabilities_power_cycle (ctx); + qmi_message_nas_set_system_selection_preference_output_unref (output); + return; + } + + /* Try with the deprecated command */ + ctx->run_set_system_selection_preference = FALSE; + set_current_capabilities_context_step (ctx); +} + +static void +set_current_capabilities_context_step (SetCurrentCapabilitiesContext *ctx) +{ + if (ctx->run_set_system_selection_preference) { + QmiMessageNasSetSystemSelectionPreferenceInput *input; + QmiNasRatModePreference pref; + + 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_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unhandled capabilities setting: '%s'", + str); + g_free (str); + set_current_capabilities_context_complete_and_free (ctx); + 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, /* cancellable */ + (GAsyncReadyCallback)capabilities_set_system_selection_preference_ready, + ctx); + qmi_message_nas_set_system_selection_preference_input_unref (input); + return; + } + + if (ctx->run_set_technology_preference) { + QmiMessageNasSetTechnologyPreferenceInput *input; + QmiNasRadioTechnologyPreference pref; + + 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_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unhandled capabilities setting: '%s'", + str); + g_free (str); + set_current_capabilities_context_complete_and_free (ctx); + 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)capabilities_set_technology_preference_ready, + ctx); + qmi_message_nas_set_technology_preference_input_unref (input); + return; + } + + g_simple_async_result_set_error ( + ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Setting capabilities is not supported by this device"); + set_current_capabilities_context_complete_and_free (ctx); +} + +static void +set_current_capabilities (MMIfaceModem *self, + MMModemCapability capabilities, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetCurrentCapabilitiesContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + ctx = g_slice_new0 (SetCurrentCapabilitiesContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + set_current_capabilities); + ctx->capabilities = capabilities; + + /* System selection preference introduced in NAS 1.1 */ + ctx->run_set_system_selection_preference = qmi_client_check_version (client, 1, 1); + + /* Technology preference introduced in NAS 1.0, so always available */ + ctx->run_set_technology_preference = TRUE; + + set_current_capabilities_context_step (ctx); +} + +/*****************************************************************************/ +/* Manufacturer loading (Modem interface) */ + +static gchar * +modem_load_manufacturer_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gchar *manufacturer; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + manufacturer = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded manufacturer: %s", manufacturer); + return manufacturer; +} + +static void +dms_get_manufacturer_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsGetManufacturerOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_get_manufacturer_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_get_manufacturer_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get Manufacturer: "); + g_simple_async_result_take_error (simple, error); + } else { + const gchar *str; + + qmi_message_dms_get_manufacturer_output_get_manufacturer (output, &str, NULL); + g_simple_async_result_set_op_res_gpointer (simple, + g_strdup (str), + (GDestroyNotify)g_free); + } + + if (output) + qmi_message_dms_get_manufacturer_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_manufacturer (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_manufacturer); + + mm_dbg ("loading manufacturer..."); + qmi_client_dms_get_manufacturer (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_manufacturer_ready, + result); +} + +/*****************************************************************************/ +/* Model loading (Modem interface) */ + +static gchar * +modem_load_model_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gchar *model; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + model = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded model: %s", model); + return model; +} + +static void +dms_get_model_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + 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_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_get_model_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get Model: "); + g_simple_async_result_take_error (simple, error); + } else { + const gchar *str; + + qmi_message_dms_get_model_output_get_model (output, &str, NULL); + g_simple_async_result_set_op_res_gpointer (simple, + g_strdup (str), + (GDestroyNotify)g_free); + } + + if (output) + qmi_message_dms_get_model_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_model (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_model); + + mm_dbg ("loading model..."); + qmi_client_dms_get_model (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_model_ready, + result); +} + +/*****************************************************************************/ +/* Revision loading (Modem interface) */ + +static gchar * +modem_load_revision_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gchar *revision; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + revision = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded revision: %s", revision); + return revision; +} + +static void +dms_get_revision_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsGetRevisionOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_get_revision_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_get_revision_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get Revision: "); + g_simple_async_result_take_error (simple, error); + } else { + const gchar *str; + + qmi_message_dms_get_revision_output_get_revision (output, &str, NULL); + g_simple_async_result_set_op_res_gpointer (simple, + g_strdup (str), + (GDestroyNotify)g_free); + } + + if (output) + qmi_message_dms_get_revision_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_revision (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_revision); + + mm_dbg ("loading revision..."); + qmi_client_dms_get_revision (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_revision_ready, + result); +} + +/*****************************************************************************/ +/* Equipment Identifier loading (Modem interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClient *client; + GSimpleAsyncResult *result; +} LoadEquipmentIdentifierContext; + +static void +load_equipment_identifier_context_complete_and_free (LoadEquipmentIdentifierContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gchar * +modem_load_equipment_identifier_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gchar *equipment_identifier; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + equipment_identifier = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded equipment identifier: %s", equipment_identifier); + return equipment_identifier; +} + +static void +dms_get_ids_ready (QmiClientDms *client, + GAsyncResult *res, + LoadEquipmentIdentifierContext *ctx) +{ + QmiMessageDmsGetIdsOutput *output = NULL; + GError *error = NULL; + const gchar *str; + guint len; + + output = qmi_client_dms_get_ids_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + load_equipment_identifier_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_dms_get_ids_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get IDs: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_dms_get_ids_output_unref (output); + load_equipment_identifier_context_complete_and_free (ctx); + return; + } + + /* In order: + * If we have a IMEI, use it... + * Otherwise, if we have a ESN, use it... + * Otherwise, if we have a MEID, use it... + * Otherwise, 'unknown' + */ + + if (qmi_message_dms_get_ids_output_get_imei (output, &str, NULL) && + str[0] != '\0' && str[0] != '0') { + g_free (ctx->self->priv->imei); + ctx->self->priv->imei = g_strdup (str); + } + + if (qmi_message_dms_get_ids_output_get_esn (output, &str, NULL) && + str[0] != '\0' && str[0] != '0') { + g_free (ctx->self->priv->esn); + len = strlen (str); + if (len == 7) + ctx->self->priv->esn = g_strdup_printf ("0%s", str); /* zero-pad to 8 chars */ + else if (len == 8) + ctx->self->priv->esn = g_strdup (str); + else + g_warn_if_reached (); + } + + if (qmi_message_dms_get_ids_output_get_meid (output, &str, NULL) && + str[0] != '\0' && str[0] != '0') { + g_free (ctx->self->priv->meid); + len = strlen (str); + if (len == 14) + ctx->self->priv->meid = g_strdup (str); + else + g_warn_if_reached (); + } + + if (ctx->self->priv->imei) + str = ctx->self->priv->imei; + else if (ctx->self->priv->esn) + str = ctx->self->priv->esn; + else if (ctx->self->priv->meid) + str = ctx->self->priv->meid; + else + str = "unknown"; + + g_simple_async_result_set_op_res_gpointer (ctx->result, + g_strdup (str), + (GDestroyNotify)g_free); + + qmi_message_dms_get_ids_output_unref (output); + load_equipment_identifier_context_complete_and_free (ctx); +} + +static void +modem_load_equipment_identifier (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadEquipmentIdentifierContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + ctx = g_new (LoadEquipmentIdentifierContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_equipment_identifier); + + mm_dbg ("loading equipment identifier..."); + qmi_client_dms_get_ids (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_ids_ready, + ctx); +} + +/*****************************************************************************/ +/* Device identifier loading (Modem interface) */ + +static gchar * +modem_load_device_identifier_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gchar *device_identifier; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + device_identifier = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded device identifier: %s", device_identifier); + return device_identifier; +} + +static void +modem_load_device_identifier (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + gchar *device_identifier; + + mm_dbg ("loading device identifier..."); + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_device_identifier); + + /* Just use dummy ATI/ATI1 replies, all the other internal info should be + * enough for uniqueness */ + device_identifier = mm_broadband_modem_create_device_identifier (MM_BROADBAND_MODEM (self), "", ""); + g_simple_async_result_set_op_res_gpointer (result, + device_identifier, + (GDestroyNotify)g_free); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Own Numbers loading (Modem interface) */ + +static GStrv +modem_load_own_numbers_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + gchar **own_numbers; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + own_numbers = g_new0 (gchar *, 2); + own_numbers[0] = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded own numbers: %s", own_numbers[0]); + return own_numbers; +} + +static void +dms_get_msisdn_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsGetMsisdnOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_get_msisdn_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_get_msisdn_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get MSISDN: "); + g_simple_async_result_take_error (simple, error); + } else { + const gchar *str = NULL; + + qmi_message_dms_get_msisdn_output_get_msisdn (output, &str, NULL); + g_simple_async_result_set_op_res_gpointer (simple, + g_strdup (str), + (GDestroyNotify)g_free); + } + + if (output) + qmi_message_dms_get_msisdn_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_own_numbers (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_own_numbers); + + mm_dbg ("loading own numbers..."); + qmi_client_dms_get_msisdn (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_msisdn_ready, + result); +} + +/*****************************************************************************/ +/* Check if unlock required (Modem interface) */ + +static MMModemLock +modem_load_unlock_required_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_LOCK_UNKNOWN; + + return (MMModemLock) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res))); +} + +static void +dms_uim_get_pin_status_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsUimGetPinStatusOutput *output; + GError *error = NULL; + + output = qmi_client_dms_uim_get_pin_status_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) { + /* When no SIM inserted, an internal error when checking PIN status + * needs to be fatal so that we mark the modem unusable. */ + if (g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_INTERNAL) || + g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_UIM_UNINITIALIZED)) { + g_simple_async_result_set_error (simple, + MM_MOBILE_EQUIPMENT_ERROR, + MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE, + "Couldn't get PIN status: %s", + error->message); + g_error_free (error); + } else { + g_prefix_error (&error, "Couldn't get PIN status: "); + g_simple_async_result_take_error (simple, error); + } + } else { + MMModemLock lock = MM_MODEM_LOCK_UNKNOWN; + QmiDmsUimPinStatus current_status; + + if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status ( + output, + ¤t_status, + NULL, /* verify_retries_left */ + NULL, /* unblock_retries_left */ + NULL)) + lock = mm_modem_lock_from_qmi_uim_pin_status (current_status, TRUE); + + if (lock == MM_MODEM_LOCK_NONE && + qmi_message_dms_uim_get_pin_status_output_get_pin2_status ( + output, + ¤t_status, + NULL, /* verify_retries_left */ + NULL, /* unblock_retries_left */ + NULL)) + lock = mm_modem_lock_from_qmi_uim_pin_status (current_status, FALSE); + + g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (lock), NULL); + } + + if (output) + qmi_message_dms_uim_get_pin_status_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_unlock_required (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_unlock_required); + + /* CDMA-only modems don't need this */ + if (mm_iface_modem_is_cdma_only (self)) { + mm_dbg ("Skipping unlock check in CDMA-only modem..."); + g_simple_async_result_set_op_res_gpointer (result, + GUINT_TO_POINTER (MM_MODEM_LOCK_NONE), + NULL); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + mm_dbg ("loading unlock required..."); + qmi_client_dms_uim_get_pin_status (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_uim_get_pin_status_ready, + result); +} + +/*****************************************************************************/ +/* Check if unlock retries (Modem interface) */ + +static MMUnlockRetries * +modem_load_unlock_retries_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + return MM_UNLOCK_RETRIES (g_object_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)))); +} + +static void +retry_count_dms_uim_get_pin_status_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsUimGetPinStatusOutput *output; + GError *error = NULL; + + output = qmi_client_dms_uim_get_pin_status_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get unlock retries: "); + g_simple_async_result_take_error (simple, error); + } else { + MMUnlockRetries *retries; + guint8 verify_retries_left; + guint8 unblock_retries_left; + + retries = mm_unlock_retries_new (); + + if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status ( + output, + NULL, /* current_status */ + &verify_retries_left, + &unblock_retries_left, + NULL)) { + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN, verify_retries_left); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK, unblock_retries_left); + } + + if (qmi_message_dms_uim_get_pin_status_output_get_pin2_status ( + output, + NULL, /* current_status */ + &verify_retries_left, + &unblock_retries_left, + NULL)) { + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PIN2, verify_retries_left); + mm_unlock_retries_set (retries, MM_MODEM_LOCK_SIM_PUK2, unblock_retries_left); + } + + g_simple_async_result_set_op_res_gpointer (simple, retries, g_object_unref); + } + + if (output) + qmi_message_dms_uim_get_pin_status_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_unlock_retries (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_unlock_retries); + + mm_dbg ("loading unlock retries..."); + qmi_client_dms_uim_get_pin_status (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)retry_count_dms_uim_get_pin_status_ready, + result); +} + +/*****************************************************************************/ +/* Load supported bands (Modem interface) */ + +static GArray * +modem_load_supported_bands_finish (MMIfaceModem *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + if (self->priv->supported_bands) + g_array_unref (self->priv->supported_bands); + + /* Cache the supported bands value */ + self->priv->supported_bands = g_array_ref (g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res))); + + return g_array_ref (self->priv->supported_bands); +} + +static void +dms_get_band_capabilities_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsGetBandCapabilitiesOutput *output; + GError *error = NULL; + + output = qmi_client_dms_get_band_capabilities_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_get_band_capabilities_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get band capabilities: "); + g_simple_async_result_take_error (simple, error); + } else { + GArray *mm_bands; + QmiDmsBandCapability qmi_bands = 0; + QmiDmsLteBandCapability qmi_lte_bands = 0; + + 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); + + mm_bands = mm_modem_bands_from_qmi_band_capabilities (qmi_bands, qmi_lte_bands); + + if (mm_bands->len == 0) { + g_array_unref (mm_bands); + g_simple_async_result_set_error (simple, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse the list of supported bands"); + } else { + g_simple_async_result_set_op_res_gpointer (simple, + mm_bands, + (GDestroyNotify)g_array_unref); + } + } + + if (output) + qmi_message_dms_get_band_capabilities_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_supported_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_supported_bands); + + mm_dbg ("loading band capabilities..."); + qmi_client_dms_get_band_capabilities (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_band_capabilities_ready, + result); +} + +/*****************************************************************************/ +/* Load current bands (Modem interface) */ + +static GArray * +modem_load_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + return (GArray *) g_array_ref (g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res))); +} + +#if defined WITH_NEWEST_QMI_COMMANDS + +static void +nas_get_rf_band_information_ready (QmiClientNas *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageNasGetRfBandInformationOutput *output; + GError *error = NULL; + + output = qmi_client_nas_get_rf_band_information_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_nas_get_rf_band_information_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get current band information: "); + g_simple_async_result_take_error (simple, error); + } else { + GArray *mm_bands; + GArray *info_array = NULL; + + qmi_message_nas_get_rf_band_information_output_get_list (output, &info_array, NULL); + + mm_bands = mm_modem_bands_from_qmi_rf_band_information_array (info_array); + + if (mm_bands->len == 0) { + g_array_unref (mm_bands); + g_simple_async_result_set_error (simple, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse the list of current bands"); + } else { + g_simple_async_result_set_op_res_gpointer (simple, + mm_bands, + (GDestroyNotify)g_array_unref); + } + } + + if (output) + qmi_message_nas_get_rf_band_information_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +#endif /* WITH_NEWEST_QMI_COMMANDS */ + +static void +load_bands_get_system_selection_preference_ready (QmiClientNas *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get system selection preference: "); + g_simple_async_result_take_error (simple, error); + } else { + GArray *mm_bands; + QmiNasBandPreference band_preference_mask = 0; + QmiNasLteBandPreference lte_band_preference_mask = 0; + + 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); + + mm_bands = mm_modem_bands_from_qmi_band_preference (band_preference_mask, + lte_band_preference_mask); + + if (mm_bands->len == 0) { + g_array_unref (mm_bands); + g_simple_async_result_set_error (simple, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse the list of current bands"); + } else { + gchar *str; + + str = qmi_nas_band_preference_build_string_from_mask (band_preference_mask); + mm_dbg ("Bands reported in system selection preference: '%s'", str); + g_free (str); + + g_simple_async_result_set_op_res_gpointer (simple, + mm_bands, + (GDestroyNotify)g_array_unref); + } + } + + if (output) + qmi_message_nas_get_system_selection_preference_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_load_current_bands (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_current_bands); + + mm_dbg ("loading current bands..."); + +#if defined WITH_NEWEST_QMI_COMMANDS + /* Introduced in NAS 1.19 */ + if (qmi_client_check_version (ctx->client, 1, 19)) { + qmi_client_nas_get_rf_band_information (QMI_CLIENT_NAS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)nas_get_rf_band_information_ready, + result); + return; + } +#endif + + 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, + result); +} + +/*****************************************************************************/ +/* Set current bands (Modem interface) */ + +static gboolean +set_current_bands_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +bands_set_system_selection_preference_ready (QmiClientNas *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set system selection preference: "); + g_simple_async_result_take_error (simple, error); + + } else + /* Good! TODO: do we really need to wait for the indication? */ + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + + if (output) + qmi_message_nas_set_system_selection_preference_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +set_current_bands (MMIfaceModem *_self, + GArray *bands_array, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + QmiMessageNasSetSystemSelectionPreferenceInput *input; + GSimpleAsyncResult *result; + QmiClient *client = NULL; + QmiNasBandPreference qmi_bands = 0; + QmiNasLteBandPreference qmi_lte_bands = 0; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + set_current_bands); + + /* Handle ANY separately */ + if (bands_array->len == 1 && + g_array_index (bands_array, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + if (!self->priv->supported_bands) { + g_simple_async_result_set_error (result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot handle 'ANY' if supported bands are unknown"); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + mm_modem_bands_to_qmi_band_preference (self->priv->supported_bands, + &qmi_bands, + &qmi_lte_bands); + } else + mm_modem_bands_to_qmi_band_preference (bands_array, + &qmi_bands, + &qmi_lte_bands); + + 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)) + qmi_message_nas_set_system_selection_preference_input_set_lte_band_preference (input, qmi_lte_bands, NULL); + + qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL); + + qmi_client_nas_set_system_selection_preference ( + QMI_CLIENT_NAS (client), + input, + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)bands_set_system_selection_preference_ready, + result); + qmi_message_nas_set_system_selection_preference_input_unref (input); +} + +/*****************************************************************************/ +/* Load supported modes (Modem interface) */ + +static GArray * +modem_load_supported_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_array_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); +} + +static void +modem_load_supported_modes (MMIfaceModem *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + GSimpleAsyncResult *result; + GArray *combinations; + MMModemModeCombination mode; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_supported_modes); + + if (!self->priv->supported_radio_interfaces) { + g_simple_async_result_set_error (result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot load supported modes, no radio interface list"); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + /* Build combinations + * + * (1) If current capabilities [GSM/UMTS]: + * [2G only] + * [3G only] + * [2G + 3G] + * [2G + 3G] 2G preferred + * [2G + 3G] 3G preferred + * + * (2) If current capabilities [CDMA/EVDO]: + * [2G only] + * [3G only] + * + * (3) If current capabilities [LTE]: + * [4G only] + * + * (4) If current capabilities [GSM/UMTS + CDMA/EVDO]: + * [2G only] + * [3G only] + * [2G + 3G] + * [2G + 3G] 2G preferred + * [2G + 3G] 3G preferred + * + * (5) If current capabilities [GSM/UMTS + LTE]: + * [2G + 3G + 4G] + * + * (6) If current capabilities [CDMA/EVDO + LTE]: + * [2G + 3G + 4G] + * + * (7) If current capabilities [GSM/UMTS + CDMA/EVDO + LTE]: + * [2G + 3G + 4G] + */ + + combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5); + + /* LTE only, don't allow further mode switching */ + if (mm_iface_modem_is_3gpp_lte_only (_self)) { + /* 4G only */ + mode.allowed = MM_MODEM_MODE_4G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + /* LTE and others, only allow to have all, no further preference */ + else if (mm_iface_modem_is_3gpp_lte (_self)) { + /* 2G, 3G and 4G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + } + /* Non-LTE modem, include allowed and preferred combinations */ + else { + MMModemMode mask_all; + guint i; + GArray *all; + GArray *filtered; + + + /* Build all, based on the supported radio interfaces */ + mask_all = MM_MODEM_MODE_NONE; + for (i = 0; i < self->priv->supported_radio_interfaces->len; i++) + mask_all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (self->priv->supported_radio_interfaces, + QmiDmsRadioInterface, + i)); + + + /* 2G only */ + mode.allowed = MM_MODEM_MODE_2G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 3G only */ + mode.allowed = MM_MODEM_MODE_3G; + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, mode); + /* 2G and 3G, 2G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_2G; + g_array_append_val (combinations, mode); + /* 2G and 3G, 3G preferred */ + mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G); + mode.preferred = MM_MODEM_MODE_3G; + g_array_append_val (combinations, mode); + + /* Filter out those unsupported modes */ + mode.allowed = mask_all; + mode.preferred = MM_MODEM_MODE_NONE; + all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + g_array_append_val (all, mode); + filtered = mm_filter_supported_modes (all, combinations); + g_array_unref (all); + g_array_unref (combinations); + combinations = filtered; + } + + g_simple_async_result_set_op_res_gpointer (result, combinations, (GDestroyNotify) g_array_unref); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Load supported IP families (Modem interface) */ + +static MMBearerIpFamily +modem_load_supported_ip_families_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_BEARER_IP_FAMILY_NONE; + + return (MMBearerIpFamily) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res))); +} + +static void +modem_load_supported_ip_families (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_load_supported_ip_families); + + if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) + /* CDMA-only: IPv4 */ + g_simple_async_result_set_op_res_gpointer ( + result, + GUINT_TO_POINTER (MM_BEARER_IP_FAMILY_IPV4), + NULL); + else + /* Assume IPv4 + IPv6 supported */ + g_simple_async_result_set_op_res_gpointer ( + result, + GUINT_TO_POINTER (MM_BEARER_IP_FAMILY_IPV4 | MM_BEARER_IP_FAMILY_IPV6 | MM_BEARER_IP_FAMILY_IPV4V6), + NULL); + + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Load signal quality (Modem interface) */ + +/* Limit the value betweeen [-113,-51] and scale it to a percentage */ +#define STRENGTH_TO_QUALITY(strength) \ + (guint8)(100 - ((CLAMP (strength, -113, -51) + 51) * 100 / (-113 + 51))) + +static gboolean +qmi_dbm_valid (gint8 dbm, QmiNasRadioInterface radio_interface) +{ + /* Different radio interfaces have different signal quality bounds */ + switch (radio_interface) { + case QMI_NAS_RADIO_INTERFACE_CDMA_1X: + case QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO: + return (dbm > -125 && dbm < -30); + case QMI_NAS_RADIO_INTERFACE_UMTS: + return (dbm > -125 && dbm < -30); + default: + break; + } + return TRUE; +} + +typedef struct { + MMBroadbandModemQmi *self; + QmiClient *client; + GSimpleAsyncResult *result; +} LoadSignalQualityContext; + +static void +load_signal_quality_context_complete_and_free (LoadSignalQualityContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static guint +load_signal_quality_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return 0; + + return GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res))); +} + +#if defined WITH_NEWEST_QMI_COMMANDS + +static gboolean +signal_info_get_quality (MMBroadbandModemQmi *self, + QmiMessageNasGetSignalInfoOutput *output, + gint8 *out_quality) +{ + gint8 rssi_max = 0; + gint8 rssi; + + g_assert (out_quality != NULL); + + /* We do not report per-technology signal quality, so just get the highest + * one of the ones reported. */ + + if (qmi_message_nas_get_signal_info_output_get_cdma_signal_strength (output, &rssi, NULL, NULL)) { + mm_dbg ("RSSI (CDMA): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_CDMA_1X)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_message_nas_get_signal_info_output_get_hdr_signal_strength (output, &rssi, NULL, NULL, NULL, NULL)) { + mm_dbg ("RSSI (HDR): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_message_nas_get_signal_info_output_get_gsm_signal_strength (output, &rssi, NULL)) { + mm_dbg ("RSSI (GSM): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_GSM)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_message_nas_get_signal_info_output_get_wcdma_signal_strength (output, &rssi, NULL, NULL)) { + mm_dbg ("RSSI (WCDMA): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_UMTS)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_message_nas_get_signal_info_output_get_lte_signal_strength (output, &rssi, NULL, NULL, NULL, NULL)) { + mm_dbg ("RSSI (LTE): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_LTE)) + rssi = MAX (rssi, rssi_max); + } + + /* This RSSI comes as negative dBms */ + *out_quality = STRENGTH_TO_QUALITY (rssi_max); + + mm_dbg ("RSSI: %d dBm --> %u%%", rssi_max, *out_quality); + + return (rssi_max < 0); +} + +static void +get_signal_info_ready (QmiClientNas *client, + GAsyncResult *res, + LoadSignalQualityContext *ctx) +{ + QmiMessageNasGetSignalInfoOutput *output; + GError *error = NULL; + guint quality = 0; + + output = qmi_client_nas_get_signal_info_finish (client, res, &error); + if (!output) { + g_simple_async_result_take_error (ctx->result, error); + load_signal_quality_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_nas_get_signal_info_output_get_result (output, &error)) { + qmi_message_nas_get_signal_info_output_unref (output); + g_simple_async_result_take_error (ctx->result, error); + load_signal_quality_context_complete_and_free (ctx); + return; + } + + if (!signal_info_get_quality (ctx->self, output, &quality)) { + qmi_message_nas_get_signal_info_output_unref (output); + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Signal info reported invalid signal strength."); + load_signal_quality_context_complete_and_free (ctx); + return; + } + + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + GUINT_TO_POINTER (quality), + NULL); + + qmi_message_nas_get_signal_info_output_unref (output); + load_signal_quality_context_complete_and_free (ctx); +} + +#endif /* WITH_NEWEST_QMI_COMMANDS */ + +static gboolean +signal_strength_get_quality_and_access_tech (MMBroadbandModemQmi *self, + QmiMessageNasGetSignalStrengthOutput *output, + guint8 *o_quality, + MMModemAccessTechnology *o_act) +{ + GArray *array = NULL; + gint8 signal_max = 0; + QmiNasRadioInterface main_interface; + MMModemAccessTechnology act; + + /* We do not report per-technology signal quality, so just get the highest + * one of the ones reported. */ + + /* The mandatory one is always present */ + qmi_message_nas_get_signal_strength_output_get_signal_strength (output, &signal_max, &main_interface, NULL); + mm_dbg ("Signal strength (%s): %d dBm", + qmi_nas_radio_interface_get_string (main_interface), + signal_max); + + /* Treat results as invalid if main signal strength is invalid */ + if (!qmi_dbm_valid (signal_max, main_interface)) + return FALSE; + + act = mm_modem_access_technology_from_qmi_radio_interface (main_interface); + + /* On multimode devices we may get more */ + if (qmi_message_nas_get_signal_strength_output_get_strength_list (output, &array, NULL)) { + guint i; + + for (i = 0; i < array->len; i++) { + QmiMessageNasGetSignalStrengthOutputStrengthListElement *element; + + element = &g_array_index (array, QmiMessageNasGetSignalStrengthOutputStrengthListElement, i); + + mm_dbg ("Signal strength (%s): %d dBm", + qmi_nas_radio_interface_get_string (element->radio_interface), + element->strength); + + if (qmi_dbm_valid (element->strength, element->radio_interface)) { + signal_max = MAX (element->strength, signal_max); + act |= mm_modem_access_technology_from_qmi_radio_interface (element->radio_interface); + } + } + } + + if (signal_max < 0) { + /* This signal strength comes as negative dBms */ + *o_quality = STRENGTH_TO_QUALITY (signal_max); + *o_act = act; + + mm_dbg ("Signal strength: %d dBm --> %u%%", signal_max, *o_quality); + } + + return (signal_max < 0); +} + +static void +get_signal_strength_ready (QmiClientNas *client, + GAsyncResult *res, + LoadSignalQualityContext *ctx) +{ + QmiMessageNasGetSignalStrengthOutput *output; + GError *error = NULL; + guint8 quality = 0; + MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + + output = qmi_client_nas_get_signal_strength_finish (client, res, &error); + if (!output) { + g_simple_async_result_take_error (ctx->result, error); + load_signal_quality_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_nas_get_signal_strength_output_get_result (output, &error)) { + qmi_message_nas_get_signal_strength_output_unref (output); + g_simple_async_result_take_error (ctx->result, error); + load_signal_quality_context_complete_and_free (ctx); + return; + } + + if (!signal_strength_get_quality_and_access_tech (ctx->self, output, &quality, &act)) { + qmi_message_nas_get_signal_strength_output_unref (output); + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "GetSignalStrength signal strength invalid."); + load_signal_quality_context_complete_and_free (ctx); + return; + } + + /* We update the access technologies directly here when loading signal + * quality. It goes a bit out of context, but we can do it nicely */ + mm_iface_modem_update_access_technologies ( + MM_IFACE_MODEM (ctx->self), + act, + (MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK | MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK)); + + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + GUINT_TO_POINTER (quality), + NULL); + + qmi_message_nas_get_signal_strength_output_unref (output); + load_signal_quality_context_complete_and_free (ctx); +} + +static void +load_signal_quality (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadSignalQualityContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + ctx = g_new0 (LoadSignalQualityContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + load_signal_quality); + + mm_dbg ("loading signal quality..."); + +#if defined WITH_NEWEST_QMI_COMMANDS + /* Signal info introduced in NAS 1.8 */ + if (qmi_client_check_version (ctx->client, 1, 8)) { + qmi_client_nas_get_signal_info (QMI_CLIENT_NAS (ctx->client), + NULL, + 10, + NULL, + (GAsyncReadyCallback)get_signal_info_ready, + ctx); + return; + } +#endif /* WITH_NEWEST_QMI_COMMANDS */ + + qmi_client_nas_get_signal_strength (QMI_CLIENT_NAS (ctx->client), + NULL, + 10, + NULL, + (GAsyncReadyCallback)get_signal_strength_ready, + ctx); +} + +/*****************************************************************************/ +/* Powering up the modem (Modem interface) */ + +static gboolean +modem_power_up_down_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +dms_set_operating_mode_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsSetOperatingModeOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_set_operating_mode_finish (client, res, &error); + if (!output) { + if (g_error_matches (error, + QMI_CORE_ERROR, + QMI_CORE_ERROR_UNSUPPORTED)) { + mm_dbg ("Device doesn't support operating mode setting. Ignoring power up/down"); + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_error_free (error); + } else { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } + } else if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set operating mode: "); + g_simple_async_result_take_error (simple, error); + } else { + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } + + if (output) + qmi_message_dms_set_operating_mode_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +common_power_up_down (MMIfaceModem *self, + QmiDmsOperatingMode mode, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiMessageDmsSetOperatingModeInput *input; + GSimpleAsyncResult *result; + QmiClient *client = NULL; + GError *error = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + common_power_up_down); + + input = qmi_message_dms_set_operating_mode_input_new (); + if (!qmi_message_dms_set_operating_mode_input_set_mode ( + input, + mode, + &error)) { + qmi_message_dms_set_operating_mode_input_unref (input); + g_simple_async_result_take_error (result, error); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + mm_dbg ("Setting device operating mode..."); + qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (client), + input, + 20, + NULL, + (GAsyncReadyCallback)dms_set_operating_mode_ready, + result); + qmi_message_dms_set_operating_mode_input_unref (input); +} + +static void +modem_power_down (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_power_up_down (self, + QMI_DMS_OPERATING_MODE_LOW_POWER, + callback, + user_data); +} + +static void +modem_power_up (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_power_up_down (self, + QMI_DMS_OPERATING_MODE_ONLINE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Power state loading (Modem interface) */ + +static MMModemPowerState +load_power_state_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_POWER_STATE_UNKNOWN; + + return (MMModemPowerState)GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); +} + +static void +dms_get_operating_mode_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageDmsGetOperatingModeOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_dms_get_operating_mode_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_get_operating_mode_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get operating mode: "); + g_simple_async_result_take_error (simple, error); + } else { + QmiDmsOperatingMode mode = QMI_DMS_OPERATING_MODE_UNKNOWN; + + qmi_message_dms_get_operating_mode_output_get_mode (output, &mode, NULL); + + switch (mode) { + case QMI_DMS_OPERATING_MODE_ONLINE: + g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (MM_MODEM_POWER_STATE_ON), NULL); + break; + case QMI_DMS_OPERATING_MODE_LOW_POWER: + case QMI_DMS_OPERATING_MODE_PERSISTENT_LOW_POWER: + case QMI_DMS_OPERATING_MODE_MODE_ONLY_LOW_POWER: + g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (MM_MODEM_POWER_STATE_LOW), NULL); + break; + case QMI_DMS_OPERATING_MODE_OFFLINE: + g_simple_async_result_set_op_res_gpointer (simple, GUINT_TO_POINTER (MM_MODEM_POWER_STATE_OFF), NULL); + break; + default: + g_simple_async_result_set_error (simple, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unhandled power state: '%s' (%u)", + qmi_dms_operating_mode_get_string (mode), + mode); + break; + } + } + + if (output) + qmi_message_dms_get_operating_mode_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +load_power_state (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + load_power_state); + + mm_dbg ("Getting device operating mode..."); + qmi_client_dms_get_operating_mode (QMI_CLIENT_DMS (client), + NULL, + 5, + NULL, + (GAsyncReadyCallback)dms_get_operating_mode_ready, + result); +} + +/*****************************************************************************/ +/* Create SIM (Modem interface) */ + +static MMSim * +create_sim_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_sim_qmi_new_finish (res, error); +} + +static void +create_sim (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + /* New QMI SIM */ + mm_sim_qmi_new (MM_BASE_MODEM (self), + NULL, /* cancellable */ + callback, + user_data); +} + +/*****************************************************************************/ +/* Factory reset (Modem interface) */ + +static gboolean +modem_factory_reset_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +dms_restore_factory_defaults_ready (QmiClientDms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + 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_simple_async_result_take_error (simple, error); + } else if (!qmi_message_dms_restore_factory_defaults_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't restore factory defaults: "); + g_simple_async_result_take_error (simple, error); + } else { + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } + + if (output) + qmi_message_dms_restore_factory_defaults_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_factory_reset (MMIfaceModem *self, + const gchar *code, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiMessageDmsRestoreFactoryDefaultsInput *input; + GSimpleAsyncResult *result; + QmiClient *client = NULL; + GError *error = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_factory_reset); + + 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_simple_async_result_take_error (result, error); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + mm_dbg ("performing a factory reset..."); + qmi_client_dms_restore_factory_defaults (QMI_CLIENT_DMS (client), + input, + 10, + NULL, + (GAsyncReadyCallback)dms_restore_factory_defaults_ready, + result); +} + +/*****************************************************************************/ +/* Load current modes (Modem interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *client; + GSimpleAsyncResult *result; + gboolean run_get_system_selection_preference; + gboolean run_get_technology_preference; +} LoadCurrentModesContext; + +typedef struct { + MMModemMode allowed; + MMModemMode preferred; +} LoadCurrentModesResult; + +static void +load_current_modes_context_complete_and_free (LoadCurrentModesContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +load_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + MMModemMode *allowed, + MMModemMode *preferred, + GError **error) +{ + LoadCurrentModesResult *result; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return FALSE; + + result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); + *allowed = result->allowed; + *preferred = result->preferred; + return TRUE; +} + +static void load_current_modes_context_step (LoadCurrentModesContext *ctx); + +static void +get_technology_preference_ready (QmiClientNas *client, + GAsyncResult *res, + LoadCurrentModesContext *ctx) +{ + LoadCurrentModesResult *result = NULL; + QmiMessageNasGetTechnologyPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_get_technology_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_get_technology_preference_output_get_result (output, &error)) { + mm_dbg ("Couldn't get technology preference: %s", error->message); + g_error_free (error); + } else { + MMModemMode allowed; + QmiNasRadioTechnologyPreference preference_mask; + + 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); + mm_dbg ("Unsupported modes reported: '%s'", str); + g_free (str); + } else { + /* We got a valid value from here */ + result = g_new (LoadCurrentModesResult, 1); + result->allowed = allowed; + result->preferred = MM_MODEM_MODE_NONE; + } + } + + if (output) + qmi_message_nas_get_technology_preference_output_unref (output); + + if (!result) { + ctx->run_get_technology_preference = FALSE; + load_current_modes_context_step (ctx); + return; + } + + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + result, + (GDestroyNotify)g_free); + load_current_modes_context_complete_and_free (ctx); +} + +static void +current_modes_get_system_selection_preference_ready (QmiClientNas *client, + GAsyncResult *res, + LoadCurrentModesContext *ctx) +{ + LoadCurrentModesResult *result = NULL; + QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL; + GError *error = NULL; + QmiNasRatModePreference mode_preference_mask = 0; + + output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("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_dbg ("Couldn't get system selection preference: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_get_system_selection_preference_output_get_mode_preference ( + output, + &mode_preference_mask, + NULL)) { + mm_dbg ("Mode preference not reported in system selection preference"); + } else { + MMModemMode allowed; + + 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); + mm_dbg ("Unsupported modes reported: '%s'", str); + g_free (str); + } else { + QmiNasGsmWcdmaAcquisitionOrderPreference gsm_or_wcdma; + + /* We got a valid value from here */ + result = g_new (LoadCurrentModesResult, 1); + result->allowed = allowed; + result->preferred = MM_MODEM_MODE_NONE; + + if ((mode_preference_mask & QMI_NAS_RAT_MODE_PREFERENCE_GSM) && + (mode_preference_mask & QMI_NAS_RAT_MODE_PREFERENCE_UMTS) && + 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); + } + } + } + + if (output) + qmi_message_nas_get_system_selection_preference_output_unref (output); + + if (!result) { + /* Try with the deprecated command */ + ctx->run_get_system_selection_preference = FALSE; + load_current_modes_context_step (ctx); + return; + } + + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + result, + (GDestroyNotify)g_free); + load_current_modes_context_complete_and_free (ctx); +} + +static void +load_current_modes_context_step (LoadCurrentModesContext *ctx) +{ + if (ctx->run_get_system_selection_preference) { + qmi_client_nas_get_system_selection_preference ( + ctx->client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)current_modes_get_system_selection_preference_ready, + ctx); + return; + } + + if (ctx->run_get_technology_preference) { + qmi_client_nas_get_technology_preference ( + ctx->client, + NULL, /* no input */ + 5, + NULL, /* cancellable */ + (GAsyncReadyCallback)get_technology_preference_ready, + ctx); + return; + } + + g_simple_async_result_set_error ( + ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Loading current modes is not supported by this device"); + load_current_modes_context_complete_and_free (ctx); +} + +static void +load_current_modes (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadCurrentModesContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + ctx = g_new0 (LoadCurrentModesContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + load_current_modes); + + /* System selection preference introduced in NAS 1.1 */ + ctx->run_get_system_selection_preference = qmi_client_check_version (client, 1, 1); + + /* Technology preference introduced in NAS 1.0, so always available */ + ctx->run_get_technology_preference = TRUE; + + load_current_modes_context_step (ctx); +} + +/*****************************************************************************/ +/* Set allowed modes (Modem interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *client; + GSimpleAsyncResult *result; + MMModemMode allowed; + MMModemMode preferred; + gboolean run_set_system_selection_preference; + gboolean run_set_technology_preference; +} SetCurrentModesContext; + +static void +set_current_modes_context_complete_and_free (SetCurrentModesContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (SetCurrentModesContext, ctx); +} + +static gboolean +set_current_modes_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void set_current_modes_context_step (SetCurrentModesContext *ctx); + +static void +set_technology_preference_ready (QmiClientNas *client, + GAsyncResult *res, + SetCurrentModesContext *ctx) +{ + QmiMessageNasSetTechnologyPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_set_technology_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_set_technology_preference_output_get_result (output, &error) && + !g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + mm_dbg ("Couldn't set technology preference: %s", error->message); + g_error_free (error); + qmi_message_nas_set_technology_preference_output_unref (output); + } else { + if (error) + g_error_free (error); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + set_current_modes_context_complete_and_free (ctx); + qmi_message_nas_set_technology_preference_output_unref (output); + return; + } + + ctx->run_set_technology_preference = FALSE; + set_current_modes_context_step (ctx); +} + +static void +allowed_modes_set_system_selection_preference_ready (QmiClientNas *client, + GAsyncResult *res, + SetCurrentModesContext *ctx) +{ + QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) { + mm_dbg ("Couldn't set system selection preference: %s", error->message); + g_error_free (error); + qmi_message_nas_set_system_selection_preference_output_unref (output); + } else { + /* Good! TODO: do we really need to wait for the indication? */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + set_current_modes_context_complete_and_free (ctx); + qmi_message_nas_set_system_selection_preference_output_unref (output); + return; + } + + /* Try with the deprecated command */ + ctx->run_set_system_selection_preference = FALSE; + set_current_modes_context_step (ctx); +} + +static void +set_current_modes_context_step (SetCurrentModesContext *ctx) +{ + if (ctx->run_set_system_selection_preference) { + QmiMessageNasSetSystemSelectionPreferenceInput *input; + QmiNasRatModePreference pref; + + pref = mm_modem_mode_to_qmi_rat_mode_preference (ctx->allowed, + mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->self)), + mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self))); + if (!pref) { + gchar *str; + + str = mm_modem_mode_build_string_from_mask (ctx->allowed); + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unhandled allowed mode setting: '%s'", + str); + g_free (str); + set_current_modes_context_complete_and_free (ctx); + 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); + + /* Only set acquisition order preference if both 2G and 3G given as allowed */ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self)) && + ctx->allowed & MM_MODEM_MODE_2G && + ctx->allowed & MM_MODEM_MODE_3G) { + QmiNasGsmWcdmaAcquisitionOrderPreference order; + + order = mm_modem_mode_to_qmi_gsm_wcdma_acquisition_order_preference (ctx->preferred); + qmi_message_nas_set_system_selection_preference_input_set_gsm_wcdma_acquisition_order_preference (input, order, 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, /* cancellable */ + (GAsyncReadyCallback)allowed_modes_set_system_selection_preference_ready, + ctx); + qmi_message_nas_set_system_selection_preference_input_unref (input); + return; + } + + if (ctx->run_set_technology_preference) { + QmiMessageNasSetTechnologyPreferenceInput *input; + QmiNasRadioTechnologyPreference pref; + + if (ctx->preferred != MM_MODEM_MODE_NONE) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot set specific preferred mode"); + set_current_modes_context_complete_and_free (ctx); + return; + } + + pref = mm_modem_mode_to_qmi_radio_technology_preference (ctx->allowed, + mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->self))); + if (!pref) { + gchar *str; + + str = mm_modem_mode_build_string_from_mask (ctx->allowed); + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Unhandled allowed mode setting: '%s'", + str); + g_free (str); + set_current_modes_context_complete_and_free (ctx); + 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_technology_preference_ready, + ctx); + qmi_message_nas_set_technology_preference_input_unref (input); + return; + } + + g_simple_async_result_set_error ( + ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "Setting allowed modes is not supported by this device"); + set_current_modes_context_complete_and_free (ctx); +} + +static void +set_current_modes (MMIfaceModem *self, + MMModemMode allowed, + MMModemMode preferred, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetCurrentModesContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + ctx = g_slice_new0 (SetCurrentModesContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + set_current_modes); + + if (allowed == MM_MODEM_MODE_ANY && ctx->preferred == MM_MODEM_MODE_NONE) { + ctx->allowed = MM_MODEM_MODE_NONE; + if (mm_iface_modem_is_2g (self)) + ctx->allowed |= MM_MODEM_MODE_2G; + if (mm_iface_modem_is_3g (self)) + ctx->allowed |= MM_MODEM_MODE_3G; + if (mm_iface_modem_is_4g (self)) + ctx->allowed |= MM_MODEM_MODE_4G; + ctx->preferred = MM_MODEM_MODE_NONE; + } else { + ctx->allowed = allowed; + ctx->preferred = preferred; + } + + /* System selection preference introduced in NAS 1.1 */ + ctx->run_set_system_selection_preference = qmi_client_check_version (client, 1, 1); + + /* Technology preference introduced in NAS 1.0, so always available */ + ctx->run_set_technology_preference = TRUE; + + set_current_modes_context_step (ctx); +} + +/*****************************************************************************/ +/* IMEI loading (3GPP interface) */ + +static gchar * +modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + gchar *imei; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + imei = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded IMEI: %s", imei); + return imei; +} + +static void +modem_3gpp_load_imei (MMIfaceModem3gpp *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_3gpp_load_imei); + + if (self->priv->imei) + g_simple_async_result_set_op_res_gpointer (result, + self->priv->imei, + NULL); + else + g_simple_async_result_set_error (result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Device doesn't report a valid IMEI"); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Facility locks status loading (3GPP interface) */ + +typedef struct { + MMBroadbandModem *self; + GSimpleAsyncResult *result; + QmiClient *client; + guint current; + MMModem3gppFacility facilities; + MMModem3gppFacility locks; +} LoadEnabledFacilityLocksContext; + +static void get_next_facility_lock_status (LoadEnabledFacilityLocksContext *ctx); + +static void +load_enabled_facility_locks_context_complete_and_free (LoadEnabledFacilityLocksContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static MMModem3gppFacility +modem_3gpp_load_enabled_facility_locks_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_3GPP_FACILITY_NONE; + + return ((MMModem3gppFacility) GPOINTER_TO_UINT ( + g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)))); +} + +static void +dms_uim_get_ck_status_ready (QmiClientDms *client, + GAsyncResult *res, + LoadEnabledFacilityLocksContext *ctx) +{ + gchar *facility_str; + QmiMessageDmsUimGetCkStatusOutput *output; + + facility_str = mm_modem_3gpp_facility_build_string_from_mask (1 << ctx->current); + output = qmi_client_dms_uim_get_ck_status_finish (client, res, NULL); + if (!output || + !qmi_message_dms_uim_get_ck_status_output_get_result (output, NULL)) { + /* On errors, we'll just assume disabled */ + mm_dbg ("Couldn't query facility '%s' status, assuming disabled", facility_str); + ctx->locks &= ~(1 << ctx->current); + } else { + QmiDmsUimFacilityState state; + guint8 verify_retries_left; + guint8 unblock_retries_left; + + qmi_message_dms_uim_get_ck_status_output_get_ck_status ( + output, + &state, + &verify_retries_left, + &unblock_retries_left, + NULL); + + mm_dbg ("Facility '%s' is: '%s'", + facility_str, + qmi_dms_uim_facility_state_get_string (state)); + + if (state == QMI_DMS_UIM_FACILITY_STATE_ACTIVATED || + state == QMI_DMS_UIM_FACILITY_STATE_BLOCKED) { + ctx->locks |= (1 << ctx->current); + } + } + + if (output) + qmi_message_dms_uim_get_ck_status_output_unref (output); + g_free (facility_str); + + /* And go on with the next one */ + ctx->current++; + get_next_facility_lock_status (ctx); +} + +static void +get_next_facility_lock_status (LoadEnabledFacilityLocksContext *ctx) +{ + guint i; + + for (i = ctx->current; i < sizeof (MMModem3gppFacility) * 8; i++) { + guint32 facility = 1 << i; + + /* Found the next one to query! */ + if (ctx->facilities & facility) { + QmiMessageDmsUimGetCkStatusInput *input; + + /* Keep the current one */ + ctx->current = i; + + /* Query current */ + input = qmi_message_dms_uim_get_ck_status_input_new (); + qmi_message_dms_uim_get_ck_status_input_set_facility ( + input, + mm_3gpp_facility_to_qmi_uim_facility (facility), + NULL); + qmi_client_dms_uim_get_ck_status (QMI_CLIENT_DMS (ctx->client), + input, + 5, + NULL, + (GAsyncReadyCallback)dms_uim_get_ck_status_ready, + ctx); + qmi_message_dms_uim_get_ck_status_input_unref (input); + return; + } + } + + /* No more facilities to query, all done */ + g_simple_async_result_set_op_res_gpointer (ctx->result, + GUINT_TO_POINTER (ctx->locks), + NULL); + load_enabled_facility_locks_context_complete_and_free (ctx); +} + +static void +modem_3gpp_load_enabled_facility_locks (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadEnabledFacilityLocksContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + ctx = g_new (LoadEnabledFacilityLocksContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_3gpp_load_enabled_facility_locks); + /* Set initial list of facilities to query */ + ctx->facilities = (MM_MODEM_3GPP_FACILITY_PH_SIM | + MM_MODEM_3GPP_FACILITY_NET_PERS | + MM_MODEM_3GPP_FACILITY_NET_SUB_PERS | + MM_MODEM_3GPP_FACILITY_PROVIDER_PERS | + MM_MODEM_3GPP_FACILITY_CORP_PERS); + ctx->locks = MM_MODEM_3GPP_FACILITY_NONE; + ctx->current = 0; + + get_next_facility_lock_status (ctx); +} + +/*****************************************************************************/ +/* Scan networks (3GPP interface) */ + +static GList * +modem_3gpp_scan_networks_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + /* We return the GList as it is */ + return (GList *) g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); +} + +static MMModem3gppNetworkAvailability +network_availability_from_qmi_nas_network_status (QmiNasNetworkStatus qmi) +{ + if (qmi & QMI_NAS_NETWORK_STATUS_CURRENT_SERVING) + return MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT; + + if (qmi & QMI_NAS_NETWORK_STATUS_AVAILABLE) { + if (qmi & QMI_NAS_NETWORK_STATUS_FORBIDDEN) + return MM_MODEM_3GPP_NETWORK_AVAILABILITY_FORBIDDEN; + return MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE; + } + + return MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN; +} + +static MM3gppNetworkInfo * +get_3gpp_network_info (QmiMessageNasNetworkScanOutputNetworkInformationElement *element) +{ + GString *aux; + MM3gppNetworkInfo *info; + + info = g_new (MM3gppNetworkInfo, 1); + info->status = network_availability_from_qmi_nas_network_status (element->network_status); + + aux = g_string_new (""); + /* MCC always 3 digits */ + g_string_append_printf (aux, "%.3"G_GUINT16_FORMAT, element->mcc); + /* Guess about MNC, if < 100 assume it's 2 digits, no PCS info here */ + if (element->mnc >= 100) + g_string_append_printf (aux, "%.3"G_GUINT16_FORMAT, element->mnc); + else + g_string_append_printf (aux, "%.2"G_GUINT16_FORMAT, element->mnc); + + info->operator_code = g_string_free (aux, FALSE); + info->operator_short = NULL; + info->operator_long = g_strdup (element->description); + info->access_tech = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + + return info; +} + +static MMModemAccessTechnology +get_3gpp_access_technology (GArray *array, + gboolean *array_used_flags, + guint16 mcc, + guint16 mnc) +{ + guint i; + + for (i = 0; i < array->len; i++) { + QmiMessageNasNetworkScanOutputRadioAccessTechnologyElement *element; + + if (array_used_flags[i]) + continue; + + element = &g_array_index (array, QmiMessageNasNetworkScanOutputRadioAccessTechnologyElement, i); + if (element->mcc == mcc && + element->mnc == mnc) { + array_used_flags[i] = TRUE; + return mm_modem_access_technology_from_qmi_radio_interface (element->radio_interface); + } + } + + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; +} + +static void +nas_network_scan_ready (QmiClientNas *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageNasNetworkScanOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_network_scan_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_nas_network_scan_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't scan networks: "); + g_simple_async_result_take_error (simple, error); + } else { + GList *scan_result = NULL; + GArray *info_array = NULL; + + if (qmi_message_nas_network_scan_output_get_network_information (output, &info_array, NULL)) { + GArray *rat_array = NULL; + gboolean *rat_array_used_flags = NULL; + guint i; + + /* Get optional RAT array */ + qmi_message_nas_network_scan_output_get_radio_access_technology (output, &rat_array, NULL); + if (rat_array) + rat_array_used_flags = g_new0 (gboolean, rat_array->len); + + for (i = 0; i < info_array->len; i++) { + QmiMessageNasNetworkScanOutputNetworkInformationElement *info_element; + MM3gppNetworkInfo *info; + + info_element = &g_array_index (info_array, QmiMessageNasNetworkScanOutputNetworkInformationElement, i); + + info = get_3gpp_network_info (info_element); + if (rat_array) + info->access_tech = get_3gpp_access_technology (rat_array, + rat_array_used_flags, + info_element->mcc, + info_element->mnc); + + scan_result = g_list_append (scan_result, info); + } + + g_free (rat_array_used_flags); + } + + /* We *require* a callback in the async method, as we're not setting a + * GDestroyNotify callback */ + g_simple_async_result_set_op_res_gpointer (simple, scan_result, NULL); + } + + if (output) + qmi_message_nas_network_scan_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_3gpp_scan_networks (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + /* We will pass the GList in the GSimpleAsyncResult, so we must + * ensure that there is a callback so that we get it properly + * passed to the caller and deallocated afterwards */ + g_assert (callback != NULL); + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_3gpp_scan_networks); + + mm_dbg ("Scanning networks..."); + qmi_client_nas_network_scan (QMI_CLIENT_NAS (client), + NULL, + 100, + NULL, + (GAsyncReadyCallback)nas_network_scan_ready, + result); +} + +/*****************************************************************************/ +/* Load operator name (3GPP interface) */ + +static gchar * +modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + + if (self->priv->current_operator_description) + return g_strdup (self->priv->current_operator_description); + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Current operator description is still unknown"); + return NULL; +} + +static void +modem_3gpp_load_operator_name (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + /* Just finish the async operation */ + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_3gpp_load_operator_name); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Load operator code (3GPP interface) */ + +static gchar * +modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + + if (self->priv->current_operator_id) + return g_strdup (self->priv->current_operator_id); + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Current operator MCC/MNC is still unknown"); + return NULL; +} + +static void +modem_3gpp_load_operator_code (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + /* Just finish the async operation */ + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_3gpp_load_operator_code); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Register in network (3GPP interface) */ + +static gboolean +modem_3gpp_register_in_network_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +initiate_network_register_ready (QmiClientNas *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + GError *error = NULL; + QmiMessageNasInitiateNetworkRegisterOutput *output; + + output = qmi_client_nas_initiate_network_register_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_nas_initiate_network_register_output_get_result (output, &error)) { + /* NOFX is not an error, they actually play pretty well */ + if (g_error_matches (error, + QMI_PROTOCOL_ERROR, + QMI_PROTOCOL_ERROR_NO_EFFECT)) { + g_error_free (error); + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } else { + g_prefix_error (&error, "Couldn't initiate network register: "); + g_simple_async_result_take_error (simple, error); + } + } else + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + + if (output) + qmi_message_nas_initiate_network_register_output_unref (output); + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +modem_3gpp_register_in_network (MMIfaceModem3gpp *self, + const gchar *operator_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + guint16 mcc = 0; + guint16 mnc = 0; + QmiClient *client = NULL; + QmiMessageNasInitiateNetworkRegisterInput *input; + GError *error = NULL; + + /* Parse input MCC/MNC */ + if (operator_id && !mm_3gpp_parse_operator_id (operator_id, &mcc, &mnc, &error)) { + g_assert (error != NULL); + g_simple_async_report_take_gerror_in_idle (G_OBJECT (self), + callback, + user_data, + error); + return; + } + + /* Get NAS client */ + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + 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, + 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, + g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_3gpp_register_in_network)); + + qmi_message_nas_initiate_network_register_input_unref (input); +} + +/*****************************************************************************/ +/* Registration checks (3GPP interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *client; + GSimpleAsyncResult *result; +} Run3gppRegistrationChecksContext; + +static void +run_3gpp_registration_checks_context_complete_and_free (Run3gppRegistrationChecksContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +modem_3gpp_run_registration_checks_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +common_process_serving_system_3gpp (MMBroadbandModemQmi *self, + QmiMessageNasGetServingSystemOutput *response_output, + QmiIndicationNasServingSystemOutput *indication_output) +{ + QmiNasRegistrationState registration_state; + QmiNasAttachState cs_attach_state; + QmiNasAttachState ps_attach_state; + QmiNasNetworkType selected_network; + GArray *radio_interfaces; + GArray *data_service_capabilities; + QmiNasRoamingIndicatorStatus roaming; + guint16 mcc; + guint16 mnc; + const gchar *description; + gboolean has_pcs_digit; + guint16 lac; + guint32 cid; + MMModemAccessTechnology mm_access_technologies; + MMModem3gppRegistrationState mm_cs_registration_state; + MMModem3gppRegistrationState mm_ps_registration_state; + + if (response_output) + qmi_message_nas_get_serving_system_output_get_serving_system ( + response_output, + ®istration_state, + &cs_attach_state, + &ps_attach_state, + &selected_network, + &radio_interfaces, + NULL); + else + qmi_indication_nas_serving_system_output_get_serving_system ( + indication_output, + ®istration_state, + &cs_attach_state, + &ps_attach_state, + &selected_network, + &radio_interfaces, + NULL); + + /* Build access technologies mask */ + data_service_capabilities = NULL; + if (response_output) + qmi_message_nas_get_serving_system_output_get_data_service_capability (response_output, &data_service_capabilities, NULL); + else + qmi_indication_nas_serving_system_output_get_data_service_capability (indication_output, &data_service_capabilities, NULL); + + if (data_service_capabilities) + mm_access_technologies = + mm_modem_access_technologies_from_qmi_data_capability_array (data_service_capabilities); + else + mm_access_technologies = + mm_modem_access_technologies_from_qmi_radio_interface_array (radio_interfaces); + + /* Only process 3GPP info. + * Seen the case already where 'selected_network' gives UNKNOWN but we still + * have valid LTE info around. */ + if (selected_network == QMI_NAS_NETWORK_TYPE_3GPP || + (selected_network == QMI_NAS_NETWORK_TYPE_UNKNOWN && + (mm_access_technologies & MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK))) { + mm_dbg ("Processing 3GPP info..."); + } else { + MMModem3gppRegistrationState reg_state_3gpp; + + mm_dbg ("No 3GPP info given..."); + g_free (self->priv->current_operator_id); + self->priv->current_operator_id = NULL; + g_free (self->priv->current_operator_description); + self->priv->current_operator_description = NULL; + + if (registration_state == QMI_NAS_REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) + reg_state_3gpp = MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING; + else + reg_state_3gpp = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + + mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_3gpp); + mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_3gpp); + mm_iface_modem_3gpp_update_access_technologies (MM_IFACE_MODEM_3GPP (self), MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), 0, 0); + return; + } + + /* Get roaming status. + * TODO: QMI may report per-access-technology roaming indicators, for when + * the modem is connected to more than one network. How to handle those? */ + roaming = QMI_NAS_ROAMING_INDICATOR_STATUS_OFF; + if (response_output) + qmi_message_nas_get_serving_system_output_get_roaming_indicator (response_output, &roaming, NULL); + else + qmi_indication_nas_serving_system_output_get_roaming_indicator (indication_output, &roaming, NULL); + + /* Build MM registration states */ + mm_cs_registration_state = + mm_modem_3gpp_registration_state_from_qmi_registration_state ( + cs_attach_state, + registration_state, + (roaming == QMI_NAS_ROAMING_INDICATOR_STATUS_ON)); + mm_ps_registration_state = + mm_modem_3gpp_registration_state_from_qmi_registration_state ( + ps_attach_state, + registration_state, + (roaming == QMI_NAS_ROAMING_INDICATOR_STATUS_ON)); + + /* Get and cache operator ID/name */ + if ((response_output && + qmi_message_nas_get_serving_system_output_get_current_plmn ( + response_output, + &mcc, + &mnc, + &description, + NULL)) || + (indication_output && + qmi_indication_nas_serving_system_output_get_current_plmn ( + indication_output, + &mcc, + &mnc, + &description, + NULL))) { + /* When we don't have information about leading PCS digit, guess best */ + g_free (self->priv->current_operator_id); + if (mnc >= 100) + self->priv->current_operator_id = + g_strdup_printf ("%.3" G_GUINT16_FORMAT "%.3" G_GUINT16_FORMAT, + mcc, + mnc); + else + self->priv->current_operator_id = + g_strdup_printf ("%.3" G_GUINT16_FORMAT "%.2" G_GUINT16_FORMAT, + mcc, + mnc); + + g_free (self->priv->current_operator_description); + self->priv->current_operator_description = g_strdup (description); + } + + /* If MNC comes with PCS digit, we must make sure the additional + * leading '0' is added */ + if (((response_output && + qmi_message_nas_get_serving_system_output_get_mnc_pcs_digit_include_status ( + response_output, + &mcc, + &mnc, + &has_pcs_digit, + NULL)) || + (indication_output && + qmi_indication_nas_serving_system_output_get_mnc_pcs_digit_include_status ( + indication_output, + &mcc, + &mnc, + &has_pcs_digit, + NULL))) && + has_pcs_digit) { + g_free (self->priv->current_operator_id); + self->priv->current_operator_id = + g_strdup_printf ("%.3" G_GUINT16_FORMAT "%.3" G_GUINT16_FORMAT, + mcc, + mnc); + } + + /* Get 3GPP location LAC and CI */ + lac = 0; + cid = 0; + if (response_output) { + qmi_message_nas_get_serving_system_output_get_lac_3gpp (response_output, &lac, NULL); + qmi_message_nas_get_serving_system_output_get_cid_3gpp (response_output, &cid, NULL); + } else { + qmi_indication_nas_serving_system_output_get_lac_3gpp (indication_output, &lac, NULL); + qmi_indication_nas_serving_system_output_get_cid_3gpp (indication_output, &cid, NULL); + } + + /* Report new registration states */ + mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), mm_cs_registration_state); + mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), mm_ps_registration_state); + mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), lac, cid); + + /* Note: don't update access technologies with the ones retrieved here; they + * are not really the 'current' access technologies */ +} + +static void +get_serving_system_3gpp_ready (QmiClientNas *client, + GAsyncResult *res, + Run3gppRegistrationChecksContext *ctx) +{ + QmiMessageNasGetServingSystemOutput *output; + GError *error = NULL; + + output = qmi_client_nas_get_serving_system_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + run_3gpp_registration_checks_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_nas_get_serving_system_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get serving system: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_nas_get_serving_system_output_unref (output); + run_3gpp_registration_checks_context_complete_and_free (ctx); + return; + } + + common_process_serving_system_3gpp (ctx->self, output, NULL); + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + qmi_message_nas_get_serving_system_output_unref (output); + run_3gpp_registration_checks_context_complete_and_free (ctx); +} + +#if defined WITH_NEWEST_QMI_COMMANDS + +static gboolean +process_common_info (QmiNasServiceStatus service_status, + gboolean domain_valid, + QmiNasNetworkServiceDomain domain, + gboolean roaming_status_valid, + QmiNasRoamingStatus roaming_status, + gboolean forbidden_valid, + gboolean forbidden, + gboolean lac_valid, + guint16 lac, + gboolean cid_valid, + guint32 cid, + gboolean network_id_valid, + const gchar *mcc, + const gchar *mnc, + MMModem3gppRegistrationState *mm_cs_registration_state, + MMModem3gppRegistrationState *mm_ps_registration_state, + guint16 *mm_lac, + guint32 *mm_cid, + gchar **mm_operator_id) +{ + MMModem3gppRegistrationState tmp_registration_state; + gboolean apply_cs; + gboolean apply_ps; + + if (service_status != QMI_NAS_SERVICE_STATUS_LIMITED && + service_status != QMI_NAS_SERVICE_STATUS_AVAILABLE && + service_status != QMI_NAS_SERVICE_STATUS_LIMITED_REGIONAL) + return FALSE; + + /* If we don't have domain, unknown */ + if (!domain_valid) + tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + else if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_NONE) + tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING; + else if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_UNKNOWN) + tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + else { + /* If we have CS or PS service domain, assume registered for now */ + if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_CS) + apply_ps = FALSE; + else if (domain == QMI_NAS_NETWORK_SERVICE_DOMAIN_PS) + apply_cs = FALSE; + + /* Check if we really are roaming or forbidden */ + if (forbidden_valid && forbidden) + tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_DENIED; + else { + if (roaming_status_valid && roaming_status == QMI_NAS_ROAMING_STATUS_ON) + tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING; + else + tmp_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_HOME; + + /* If we're registered either at home or roaming, try to get LAC/CID */ + if (lac_valid) + *mm_lac = lac; + if (cid_valid) + *mm_cid = cid; + } + } + + if (apply_cs) + *mm_cs_registration_state = tmp_registration_state; + if (apply_ps) + *mm_cs_registration_state = tmp_registration_state; + + if (network_id_valid) { + *mm_operator_id = g_malloc (7); + memcpy (*mm_operator_id, mcc, 3); + if (mnc[2] == 0xFF) { + memcpy (*mm_operator_id, mnc, 2); + (*mm_operator_id)[5] = '\0'; + } else { + memcpy (*mm_operator_id, mnc, 3); + (*mm_operator_id)[6] = '\0'; + } + } + + return TRUE; +} + +static gboolean +process_gsm_info (QmiMessageNasGetSystemInfoOutput *response_output, + QmiIndicationNasSystemInfoOutput *indication_output, + MMModem3gppRegistrationState *mm_cs_registration_state, + MMModem3gppRegistrationState *mm_ps_registration_state, + guint16 *mm_lac, + guint32 *mm_cid, + gchar **mm_operator_id) +{ + QmiNasServiceStatus service_status; + gboolean domain_valid; + QmiNasNetworkServiceDomain domain; + gboolean roaming_status_valid; + QmiNasRoamingStatus roaming_status; + gboolean forbidden_valid; + gboolean forbidden; + gboolean lac_valid; + guint16 lac; + gboolean cid_valid; + guint32 cid; + gboolean network_id_valid; + const gchar *mcc; + const gchar *mnc; + + g_assert ((response_output != NULL && indication_output == NULL) || + (response_output == NULL && indication_output != NULL)); + + *mm_ps_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + *mm_cs_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + *mm_lac = 0; + *mm_cid = 0; + g_free (*mm_operator_id); + *mm_operator_id = NULL; + + if (response_output) { + if (!qmi_message_nas_get_system_info_output_get_gsm_service_status ( + response_output, + &service_status, + NULL, /* true_service_status */ + NULL, /* preferred_data_path */ + NULL) || + !qmi_message_nas_get_system_info_output_get_gsm_system_info ( + response_output, + &domain_valid, &domain, + NULL, NULL, /* service_capability */ + &roaming_status_valid, &roaming_status, + &forbidden_valid, &forbidden, + &lac_valid, &lac, + &cid_valid, &cid, + NULL, NULL, NULL, /* registration_reject_info */ + &network_id_valid, &mcc, &mnc, + &egprs_support_valid, &egprs_support, + NULL, NULL, /* dtm_support */ + NULL)) { + mm_dbg ("No GSM service reported"); + /* No GSM service */ + return FALSE; + } + } else { + if (!qmi_indication_nas_system_info_output_get_gsm_service_status ( + indication_output, + &service_status, + NULL, /* true_service_status */ + NULL, /* preferred_data_path */ + NULL) || + !qmi_indication_nas_system_info_output_get_gsm_system_info ( + indication_output, + &domain_valid, &domain, + NULL, NULL, /* service_capability */ + &roaming_status_valid, &roaming_status, + &forbidden_valid, &forbidden, + &lac_valid, &lac, + &cid_valid, &cid, + NULL, NULL, NULL, /* registration_reject_info */ + &network_id_valid, &mcc, &mnc, + &egprs_support_valid, &egprs_support, + NULL, NULL, /* dtm_support */ + NULL)) { + mm_dbg ("No GSM service reported"); + /* No GSM service */ + return FALSE; + } + } + + if (!process_common_info (service_status, + domain_valid, domain, + roaming_status_valid, roaming_status, + forbidden_valid, forbidden, + lac_valid, lac, + cid_valid, cid, + network_id_valid, mcc, mnc, + mm_cs_registration_state, + mm_ps_registration_state, + mm_lac, + mm_cid, + mm_operator_id)) { + mm_dbg ("No GSM service registered"); + return FALSE; + } + + return TRUE; +} + +static gboolean +process_wcdma_info (QmiMessageNasGetSystemInfoOutput *response_output, + QmiIndicationNasSystemInfoOutput *indication_output, + MMModem3gppRegistrationState *mm_cs_registration_state, + MMModem3gppRegistrationState *mm_ps_registration_state, + guint16 *mm_lac, + guint32 *mm_cid, + gchar **mm_operator_id) +{ + QmiNasServiceStatus service_status; + gboolean domain_valid; + QmiNasNetworkServiceDomain domain; + gboolean roaming_status_valid; + QmiNasRoamingStatus roaming_status; + gboolean forbidden_valid; + gboolean forbidden; + gboolean lac_valid; + guint16 lac; + gboolean cid_valid; + guint32 cid; + gboolean network_id_valid; + const gchar *mcc; + const gchar *mnc; + gboolean hs_service_valid; + QmiNasWcdmaHsService hs_service; + + g_assert ((response_output != NULL && indication_output == NULL) || + (response_output == NULL && indication_output != NULL)); + + *mm_ps_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + *mm_cs_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + *mm_lac = 0; + *mm_cid = 0; + g_free (*mm_operator_id); + *mm_operator_id = NULL; + + if (response_output) { + if (!qmi_message_nas_get_system_info_output_get_wcdma_service_status ( + response_output, + &service_status, + NULL, /* true_service_status */ + NULL, /* preferred_data_path */ + NULL) || + !qmi_message_nas_get_system_info_output_get_wcdma_system_info ( + response_output, + &domain_valid, &domain, + NULL, NULL, /* service_capability */ + &roaming_status_valid, &roaming_status, + &forbidden_valid, &forbidden, + &lac_valid, &lac, + &cid_valid, &cid, + NULL, NULL, NULL, /* registration_reject_info */ + &network_id_valid, &mcc, &mnc, + NULL, NULL, /* hs_call_status */ + &hs_service_valid, &hs_service, + NULL, NULL, /* primary_scrambling_code */ + NULL)) { + mm_dbg ("No WCDMA service reported"); + /* No GSM service */ + return FALSE; + } + } else { + if (!qmi_indication_nas_system_info_output_get_wcdma_service_status ( + indication_output, + &service_status, + NULL, /* true_service_status */ + NULL, /* preferred_data_path */ + NULL) || + !qmi_indication_nas_system_info_output_get_wcdma_system_info ( + indication_output, + &domain_valid, &domain, + NULL, NULL, /* service_capability */ + &roaming_status_valid, &roaming_status, + &forbidden_valid, &forbidden, + &lac_valid, &lac, + &cid_valid, &cid, + NULL, NULL, NULL, /* registration_reject_info */ + &network_id_valid, &mcc, &mnc, + NULL, NULL, /* hs_call_status */ + &hs_service_valid, &hs_service, + NULL, NULL, /* primary_scrambling_code */ + NULL)) { + mm_dbg ("No WCDMA service reported"); + /* No GSM service */ + return FALSE; + } + } + + if (!process_common_info (service_status, + domain_valid, domain, + roaming_status_valid, roaming_status, + forbidden_valid, forbidden, + lac_valid, lac, + cid_valid, cid, + network_id_valid, mcc, mnc, + mm_cs_registration_state, + mm_ps_registration_state, + mm_lac, + mm_cid, + mm_operator_id)) { + mm_dbg ("No WCDMA service registered"); + return FALSE; + } + + return TRUE; +} + +static gboolean +process_lte_info (QmiMessageNasGetSystemInfoOutput *response_output, + QmiIndicationNasSystemInfoOutput *indication_output, + MMModem3gppRegistrationState *mm_cs_registration_state, + MMModem3gppRegistrationState *mm_ps_registration_state, + guint16 *mm_lac, + guint32 *mm_cid, + gchar **mm_operator_id) +{ + QmiNasServiceStatus service_status; + gboolean domain_valid; + QmiNasNetworkServiceDomain domain; + gboolean roaming_status_valid; + QmiNasRoamingStatus roaming_status; + gboolean forbidden_valid; + gboolean forbidden; + gboolean lac_valid; + guint16 lac; + gboolean cid_valid; + guint32 cid; + gboolean network_id_valid; + const gchar *mcc; + const gchar *mnc; + + g_assert ((response_output != NULL && indication_output == NULL) || + (response_output == NULL && indication_output != NULL)); + + *mm_ps_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + *mm_cs_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + *mm_lac = 0; + *mm_cid = 0; + g_free (*mm_operator_id); + *mm_operator_id = NULL; + + if (response_output) { + if (!qmi_message_nas_get_system_info_output_get_lte_service_status ( + response_output, + &service_status, + NULL, /* true_service_status */ + NULL, /* preferred_data_path */ + NULL) || + !qmi_message_nas_get_system_info_output_get_lte_system_info ( + response_output, + &domain_valid, &domain, + NULL, NULL, /* service_capability */ + &roaming_status_valid, &roaming_status, + &forbidden_valid, &forbidden, + &lac_valid, &lac, + &cid_valid, &cid, + NULL, NULL, NULL, /* registration_reject_info */ + &network_id_valid, &mcc, &mnc, + NULL, NULL, /* tac */ + NULL)) { + mm_dbg ("No LTE service reported"); + /* No GSM service */ + return FALSE; + } + } else { + if (!qmi_indication_nas_system_info_output_get_lte_service_status ( + indication_output, + &service_status, + NULL, /* true_service_status */ + NULL, /* preferred_data_path */ + NULL) || + !qmi_indication_nas_system_info_output_get_lte_system_info ( + indication_output, + &domain_valid, &domain, + NULL, NULL, /* service_capability */ + &roaming_status_valid, &roaming_status, + &forbidden_valid, &forbidden, + &lac_valid, &lac, + &cid_valid, &cid, + NULL, NULL, NULL, /* registration_reject_info */ + &network_id_valid, &mcc, &mnc, + NULL, NULL, /* tac */ + NULL)) { + mm_dbg ("No LTE service reported"); + /* No GSM service */ + return FALSE; + } + } + + if (!process_common_info (service_status, + domain_valid, domain, + roaming_status_valid, roaming_status, + forbidden_valid, forbidden, + lac_valid, lac, + cid_valid, cid, + network_id_valid, mcc, mnc, + mm_cs_registration_state, + mm_ps_registration_state, + mm_lac, + mm_cid, + mm_operator_id)) { + mm_dbg ("No LTE service registered"); + return FALSE; + } + + return TRUE; +} + +static void +common_process_system_info_3gpp (MMBroadbandModemQmi *self, + QmiMessageNasGetSystemInfoOutput *response_output, + QmiIndicationNasSystemInfoOutput *indication_output) +{ + MMModem3gppRegistrationState cs_registration_state; + MMModem3gppRegistrationState ps_registration_state; + guint16 lac; + guint32 cid; + gchar *operator_id; + + ps_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + cs_registration_state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; + lac = 0; + cid = 0; + operator_id = NULL; + + /* Process infos, with the following priority: + * LTE > WCDMA > GSM + * The first one giving results will be the one reported. + */ + if (!process_lte_info (response_output, indication_output, + &cs_registration_state, + &ps_registration_state, + &lac, + &cid, + &operator_id) && + !process_wcdma_info (response_output, indication_output, + &cs_registration_state, + &ps_registration_state, + &lac, + &cid, + &operator_id) && + !process_gsm_info (response_output, indication_output, + &cs_registration_state, + &ps_registration_state, + &lac, + &cid, + &operator_id)) { + mm_dbg ("No service (GSM, WCDMA or LTE) reported"); + } + + /* Cache current operator ID */ + if (operator_id) { + g_free (self->priv->current_operator_id); + self->priv->current_operator_id = operator_id; + } + + /* Report new registration states */ + mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), cs_registration_state); + mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), ps_registration_state); + mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), lac, cid); +} + +static void +get_system_info_ready (QmiClientNas *client, + GAsyncResult *res, + Run3gppRegistrationChecksContext *ctx) +{ + QmiMessageNasGetSystemInfoOutput *output; + GError *error = NULL; + + output = qmi_client_nas_get_system_info_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + run_3gpp_registration_checks_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_nas_get_system_info_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get system info: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_nas_get_system_info_output_unref (output); + run_3gpp_registration_checks_context_complete_and_free (ctx); + return; + } + + common_process_system_info_3gpp (ctx->self, output, NULL); + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + qmi_message_nas_get_system_info_output_unref (output); + run_3gpp_registration_checks_context_complete_and_free (ctx); +} + +#endif /* WITH_NEWEST_QMI_COMMANDS */ + +static void +modem_3gpp_run_registration_checks (MMIfaceModem3gpp *self, + gboolean cs_supported, + gboolean ps_supported, + gboolean eps_supported, + GAsyncReadyCallback callback, + gpointer user_data) +{ + Run3gppRegistrationChecksContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + ctx = g_new0 (Run3gppRegistrationChecksContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_3gpp_run_registration_checks); + +#if defined WITH_NEWEST_QMI_COMMANDS + /* System Info was added in NAS 1.8 */ + if (qmi_client_check_version (client, 1, 8)) { + qmi_client_nas_get_system_info (ctx->client, + NULL, + 10, + NULL, + (GAsyncReadyCallback)get_system_info_ready, + ctx); + return; + } +#endif /* WITH_NEWEST_QMI_COMMANDS */ + + qmi_client_nas_get_serving_system (ctx->client, + NULL, + 10, + NULL, + (GAsyncReadyCallback)get_serving_system_3gpp_ready, + ctx); +} + +/*****************************************************************************/ +/* Enable/Disable unsolicited registration events (3GPP interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *client; + GSimpleAsyncResult *result; + gboolean enable; /* TRUE for enabling, FALSE for disabling */ +} UnsolicitedRegistrationEventsContext; + +static void +unsolicited_registration_events_context_complete_and_free (UnsolicitedRegistrationEventsContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static UnsolicitedRegistrationEventsContext * +unsolicited_registration_events_context_new (MMBroadbandModemQmi *self, + QmiClient *client, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UnsolicitedRegistrationEventsContext *ctx; + + ctx = g_new0 (UnsolicitedRegistrationEventsContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + unsolicited_registration_events_context_new); + ctx->enable = enable; + return ctx; +} + +static gboolean +modem_3gpp_enable_disable_unsolicited_registration_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +ri_serving_system_or_system_info_ready (QmiClientNas *client, + GAsyncResult *res, + UnsolicitedRegistrationEventsContext *ctx) +{ + QmiMessageNasRegisterIndicationsOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_register_indications_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: '%s'", error->message); + g_error_free (error); + } else if (!qmi_message_nas_register_indications_output_get_result (output, &error)) { + mm_dbg ("Couldn't register indications: '%s'", error->message); + g_error_free (error); + } + + if (output) + qmi_message_nas_register_indications_output_unref (output); + + /* Just ignore errors for now */ + ctx->self->priv->unsolicited_registration_events_enabled = ctx->enable; + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + unsolicited_registration_events_context_complete_and_free (ctx); +} + +static void +common_enable_disable_unsolicited_registration_events_serving_system (UnsolicitedRegistrationEventsContext *ctx) +{ + QmiMessageNasRegisterIndicationsInput *input; + + input = qmi_message_nas_register_indications_input_new (); + qmi_message_nas_register_indications_input_set_serving_system_events (input, ctx->enable, NULL); + qmi_client_nas_register_indications ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)ri_serving_system_or_system_info_ready, + ctx); + qmi_message_nas_register_indications_input_unref (input); +} + +#if defined WITH_NEWEST_QMI_COMMANDS +static void +common_enable_disable_unsolicited_registration_events_system_info (UnsolicitedRegistrationEventsContext *ctx) +{ + QmiMessageNasRegisterIndicationsInput *input; + + input = qmi_message_nas_register_indications_input_new (); + qmi_message_nas_register_indications_input_set_system_info (input, ctx->enable, NULL); + qmi_client_nas_register_indications ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)ri_serving_system_or_system_info_ready, + ctx); + qmi_message_nas_register_indications_input_unref (input); +} +#endif /* WITH_NEWEST_QMI_COMMANDS */ + +static void +modem_3gpp_disable_unsolicited_registration_events (MMIfaceModem3gpp *self, + gboolean cs_supported, + gboolean ps_supported, + gboolean eps_supported, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UnsolicitedRegistrationEventsContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + ctx = unsolicited_registration_events_context_new (MM_BROADBAND_MODEM_QMI (self), + client, + FALSE, + callback, + user_data); + +#if defined WITH_NEWEST_QMI_COMMANDS + /* System Info was added in NAS 1.8 */ + if (qmi_client_check_version (client, 1, 8)) { + common_enable_disable_unsolicited_registration_events_system_info (ctx); + return; + } +#endif /* WITH_NEWEST_QMI_COMMANDS */ + + /* Ability to explicitly enable/disable serving system indications was + * added in NAS 1.2 */ + if (qmi_client_check_version (client, 1, 2)) { + common_enable_disable_unsolicited_registration_events_serving_system (ctx); + return; + } + + /* Devices with NAS < 1.2 will just always issue serving system indications */ + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Device doesn't allow disabling registration events"); + ctx->self->priv->unsolicited_registration_events_enabled = FALSE; + unsolicited_registration_events_context_complete_and_free (ctx); +} + +static void +modem_3gpp_enable_unsolicited_registration_events (MMIfaceModem3gpp *self, + gboolean cs_supported, + gboolean ps_supported, + gboolean eps_supported, + GAsyncReadyCallback callback, + gpointer user_data) +{ + UnsolicitedRegistrationEventsContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + ctx = unsolicited_registration_events_context_new (MM_BROADBAND_MODEM_QMI (self), + client, + TRUE, + callback, + user_data); + + /* Ability to explicitly enable/disable serving system indications was + * added in NAS 1.2 */ + if (qmi_client_check_version (client, 1, 2)) { + common_enable_disable_unsolicited_registration_events_serving_system (ctx); + return; + } + + /* Devices with NAS < 1.2 will just always issue serving system indications */ + mm_dbg ("Assuming serving system indications are always enabled"); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + ctx->self->priv->unsolicited_registration_events_enabled = TRUE; + unsolicited_registration_events_context_complete_and_free (ctx); +} + +/*****************************************************************************/ +/* Registration checks (CDMA interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientNas *client; + GSimpleAsyncResult *result; +} RunCdmaRegistrationChecksContext; + +static void +run_cdma_registration_checks_context_complete_and_free (RunCdmaRegistrationChecksContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (RunCdmaRegistrationChecksContext, ctx); +} + +static gboolean +modem_cdma_run_registration_checks_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +common_process_serving_system_cdma (MMBroadbandModemQmi *self, + QmiMessageNasGetServingSystemOutput *response_output, + QmiIndicationNasServingSystemOutput *indication_output) +{ + QmiNasRegistrationState registration_state; + QmiNasNetworkType selected_network; + GArray *radio_interfaces; + GArray *data_service_capabilities; + MMModemAccessTechnology mm_access_technologies; + MMModemCdmaRegistrationState mm_cdma1x_registration_state; + MMModemCdmaRegistrationState mm_evdo_registration_state; + guint16 sid = 0; + guint16 nid = 0; + guint16 bs_id = 0; + gint32 bs_longitude = MM_LOCATION_LONGITUDE_UNKNOWN; + gint32 bs_latitude = MM_LOCATION_LATITUDE_UNKNOWN; + + if (response_output) + qmi_message_nas_get_serving_system_output_get_serving_system ( + response_output, + ®istration_state, + NULL, /* cs_attach_state */ + NULL, /* ps_attach_state */ + &selected_network, + &radio_interfaces, + NULL); + else + qmi_indication_nas_serving_system_output_get_serving_system ( + indication_output, + ®istration_state, + NULL, /* cs_attach_state */ + NULL, /* ps_attach_state */ + &selected_network, + &radio_interfaces, + NULL); + + /* Build access technologies mask */ + data_service_capabilities = NULL; + if (response_output) + qmi_message_nas_get_serving_system_output_get_data_service_capability (response_output, + &data_service_capabilities, + NULL); + else + qmi_indication_nas_serving_system_output_get_data_service_capability (indication_output, + &data_service_capabilities, + NULL); + if (data_service_capabilities) + mm_access_technologies = + mm_modem_access_technologies_from_qmi_data_capability_array (data_service_capabilities); + else + mm_access_technologies = + mm_modem_access_technologies_from_qmi_radio_interface_array (radio_interfaces); + + /* Only process 3GPP2 info */ + if (selected_network == QMI_NAS_NETWORK_TYPE_3GPP2 || + (selected_network == QMI_NAS_NETWORK_TYPE_UNKNOWN && + (mm_access_technologies & MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK))) { + mm_dbg ("Processing CDMA info..."); + } else { + mm_dbg ("No CDMA info given..."); + mm_iface_modem_cdma_update_cdma1x_registration_state (MM_IFACE_MODEM_CDMA (self), + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN, + 0, 0); + mm_iface_modem_cdma_update_evdo_registration_state (MM_IFACE_MODEM_CDMA (self), + MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN); + mm_iface_modem_cdma_update_access_technologies (MM_IFACE_MODEM_CDMA (self), + MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); + mm_iface_modem_location_cdma_bs_clear (MM_IFACE_MODEM_LOCATION (self)); + return; + } + + /* Get SID/NID */ + if (response_output) + qmi_message_nas_get_serving_system_output_get_cdma_system_id (response_output, &sid, &nid, NULL); + else + qmi_indication_nas_serving_system_output_get_cdma_system_id (indication_output, &sid, &nid, NULL); + + /* Get BS location */ + if (response_output) + qmi_message_nas_get_serving_system_output_get_cdma_base_station_info (response_output, &bs_id, &bs_latitude, &bs_longitude, NULL); + else + qmi_indication_nas_serving_system_output_get_cdma_base_station_info (indication_output, &bs_id, &bs_latitude, &bs_longitude, NULL); + + /* Build generic registration states */ + if (mm_access_technologies & MM_IFACE_MODEM_CDMA_ALL_CDMA1X_ACCESS_TECHNOLOGIES_MASK) + mm_cdma1x_registration_state = mm_modem_cdma_registration_state_from_qmi_registration_state (registration_state); + else + mm_cdma1x_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + + if (mm_access_technologies & MM_IFACE_MODEM_CDMA_ALL_EVDO_ACCESS_TECHNOLOGIES_MASK) + mm_evdo_registration_state = mm_modem_cdma_registration_state_from_qmi_registration_state (registration_state); + else + mm_evdo_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; + + /* Process per-technology roaming flags */ + if (response_output) { + GArray *array; + + if (qmi_message_nas_get_serving_system_output_get_roaming_indicator_list (response_output, &array, NULL)) { + guint i; + + for (i = 0; i < array->len; i++) { + QmiMessageNasGetServingSystemOutputRoamingIndicatorListElement *element; + + element = &g_array_index (array, QmiMessageNasGetServingSystemOutputRoamingIndicatorListElement, i); + + if (element->radio_interface == QMI_NAS_RADIO_INTERFACE_CDMA_1X && + mm_cdma1x_registration_state == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED) { + if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_ON) + mm_cdma1x_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; + else if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_OFF) + mm_cdma1x_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; + } else if (element->radio_interface == QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO && + mm_evdo_registration_state == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED) { + if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_ON) + mm_evdo_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; + else if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_OFF) + mm_evdo_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; + } + } + } + } else { + GArray *array; + + if (qmi_indication_nas_serving_system_output_get_roaming_indicator_list (indication_output, &array, NULL)) { + guint i; + + for (i = 0; i < array->len; i++) { + QmiIndicationNasServingSystemOutputRoamingIndicatorListElement *element; + + element = &g_array_index (array, QmiIndicationNasServingSystemOutputRoamingIndicatorListElement, i); + + if (element->radio_interface == QMI_NAS_RADIO_INTERFACE_CDMA_1X && + mm_cdma1x_registration_state == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED) { + if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_ON) + mm_cdma1x_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; + else if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_OFF) + mm_cdma1x_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; + } else if (element->radio_interface == QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO && + mm_evdo_registration_state == MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED) { + if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_ON) + mm_evdo_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING; + else if (element->roaming_indicator == QMI_NAS_ROAMING_INDICATOR_STATUS_OFF) + mm_evdo_registration_state = MM_MODEM_CDMA_REGISTRATION_STATE_HOME; + } + } + } + } + + /* Note: don't rely on the 'Detailed Service Status', it's not always given. */ + + /* Report new registration states */ + mm_iface_modem_cdma_update_cdma1x_registration_state (MM_IFACE_MODEM_CDMA (self), + mm_cdma1x_registration_state, + sid, + nid); + mm_iface_modem_cdma_update_evdo_registration_state (MM_IFACE_MODEM_CDMA (self), + mm_evdo_registration_state); + + /* Note: don't update access technologies with the ones retrieved here; they + * are not really the 'current' access technologies */ + + /* Longitude and latitude given in units of 0.25 secs + * Note that multiplying by 0.25 is like dividing by 4, so 60*60*4=14400 */ +#define QMI_LONGITUDE_TO_DEGREES(longitude) \ + (longitude != MM_LOCATION_LONGITUDE_UNKNOWN ? \ + (((gdouble)longitude) / 14400.0) : \ + MM_LOCATION_LONGITUDE_UNKNOWN) +#define QMI_LATITUDE_TO_DEGREES(latitude) \ + (latitude != MM_LOCATION_LATITUDE_UNKNOWN ? \ + (((gdouble)latitude) / 14400.0) : \ + MM_LOCATION_LATITUDE_UNKNOWN) + + mm_iface_modem_location_cdma_bs_update (MM_IFACE_MODEM_LOCATION (self), + QMI_LONGITUDE_TO_DEGREES (bs_longitude), + QMI_LATITUDE_TO_DEGREES (bs_latitude)); +} + +static void +get_serving_system_cdma_ready (QmiClientNas *client, + GAsyncResult *res, + RunCdmaRegistrationChecksContext *ctx) +{ + QmiMessageNasGetServingSystemOutput *output; + GError *error = NULL; + + output = qmi_client_nas_get_serving_system_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + run_cdma_registration_checks_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_nas_get_serving_system_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get serving system: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_nas_get_serving_system_output_unref (output); + run_cdma_registration_checks_context_complete_and_free (ctx); + return; + } + + common_process_serving_system_cdma (ctx->self, output, NULL); + + qmi_message_nas_get_serving_system_output_unref (output); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + run_cdma_registration_checks_context_complete_and_free (ctx); +} + +static void +modem_cdma_run_registration_checks (MMIfaceModemCdma *self, + gboolean cdma1x_supported, + gboolean evdo_supported, + GAsyncReadyCallback callback, + gpointer user_data) +{ + RunCdmaRegistrationChecksContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + /* Setup context */ + ctx = g_slice_new0 (RunCdmaRegistrationChecksContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_cdma_run_registration_checks); + + /* TODO: Run Get System Info in NAS >= 1.8 */ + + qmi_client_nas_get_serving_system (ctx->client, + NULL, + 10, + NULL, + (GAsyncReadyCallback)get_serving_system_cdma_ready, + ctx); +} + +/*****************************************************************************/ +/* Load initial activation state (CDMA interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientDms *client; + GSimpleAsyncResult *result; +} LoadActivationStateContext; + +static void +load_activation_state_context_complete_and_free (LoadActivationStateContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (LoadActivationStateContext, ctx); +} + +static MMModemCdmaActivationState +modem_cdma_load_activation_state_finish (MMIfaceModemCdma *_self, + GAsyncResult *res, + GError **error) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_CDMA_ACTIVATION_STATE_UNKNOWN; + + /* Cache the value and also return it */ + self->priv->activation_state = + (MMModemCdmaActivationState) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + + return self->priv->activation_state; +} + +static void +get_activation_state_ready (QmiClientDms *client, + GAsyncResult *res, + LoadActivationStateContext *ctx) +{ + QmiDmsActivationState state = QMI_DMS_ACTIVATION_STATE_NOT_ACTIVATED; + QmiMessageDmsGetActivationStateOutput *output; + GError *error = NULL; + + output = qmi_client_dms_get_activation_state_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + load_activation_state_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_dms_get_activation_state_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't get activation state: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_dms_get_activation_state_output_unref (output); + load_activation_state_context_complete_and_free (ctx); + return; + } + + qmi_message_dms_get_activation_state_output_get_info (output, &state, NULL); + qmi_message_dms_get_activation_state_output_unref (output); + + g_simple_async_result_set_op_res_gpointer ( + ctx->result, + GUINT_TO_POINTER (mm_modem_cdma_activation_state_from_qmi_activation_state (state)), + NULL); + load_activation_state_context_complete_and_free (ctx); +} + +static void +modem_cdma_load_activation_state (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadActivationStateContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + /* Setup context */ + ctx = g_slice_new0 (LoadActivationStateContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_cdma_load_activation_state); + + qmi_client_dms_get_activation_state (ctx->client, + NULL, + 10, + NULL, + (GAsyncReadyCallback)get_activation_state_ready, + ctx); +} + +/*****************************************************************************/ +/* OTA activation (CDMA interface) */ + +typedef enum { + CDMA_ACTIVATION_STEP_FIRST, + CDMA_ACTIVATION_STEP_ENABLE_INDICATIONS, + CDMA_ACTIVATION_STEP_REQUEST_ACTIVATION, + CDMA_ACTIVATION_STEP_WAIT_UNTIL_FINISHED, + CDMA_ACTIVATION_STEP_POWER_CYCLE, + CDMA_ACTIVATION_STEP_LAST +} CdmaActivationStep; + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientDms *client; + GSimpleAsyncResult *result; + CdmaActivationStep step; + gchar *carrier_code; +} CdmaActivationContext; + +static void +cdma_activation_context_complete_and_free (CdmaActivationContext *ctx) +{ + /* Cleanup the activation context from the private info */ + ctx->self->priv->activation_ctx = NULL; + + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx->carrier_code); + g_slice_free (CdmaActivationContext, ctx); +} + +static gboolean +modem_cdma_activate_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void cdma_activation_context_step (CdmaActivationContext *ctx); + +static void +cdma_activation_disable_indications (CdmaActivationContext *ctx) +{ + QmiMessageDmsSetEventReportInput *input; + + /* Remove the signal handler */ + g_assert (ctx->self->priv->activation_event_report_indication_id != 0); + g_signal_handler_disconnect (ctx->client, ctx->self->priv->activation_event_report_indication_id); + ctx->self->priv->activation_event_report_indication_id = 0; + + /* Disable the activation state change indications; don't worry about the result */ + input = qmi_message_dms_set_event_report_input_new (); + qmi_message_dms_set_event_report_input_set_activation_state_reporting (input, FALSE, NULL); + qmi_client_dms_set_event_report (ctx->client, input, 5, NULL, NULL, NULL); + qmi_message_dms_set_event_report_input_unref (input); +} + +static void +activation_power_cycle_ready (MMBroadbandModemQmi *self, + GAsyncResult *res, + CdmaActivationContext *ctx) +{ + GError *error = NULL; + + if (!power_cycle_finish (self, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + cdma_activation_context_complete_and_free (ctx); + return; + } + + /* And go on to next step */ + ctx->step++; + cdma_activation_context_step (ctx); +} + +static void +activation_event_report_indication_cb (QmiClientDms *client, + QmiIndicationDmsEventReportOutput *output, + MMBroadbandModemQmi *self) +{ + QmiDmsActivationState state; + MMModemCdmaActivationState new; + GError *error; + + /* If the indication doesn't have any activation state info, just return */ + if (!qmi_indication_dms_event_report_output_get_activation_state (output, &state, NULL)) + return; + + mm_dbg ("Activation state update: '%s'", + qmi_dms_activation_state_get_string (state)); + + new = mm_modem_cdma_activation_state_from_qmi_activation_state (state); + + if (self->priv->activation_state != new) + mm_info ("Activation state changed: '%s'-->'%s'", + mm_modem_cdma_activation_state_get_string (self->priv->activation_state), + mm_modem_cdma_activation_state_get_string (new)); + + /* Cache the new value */ + self->priv->activation_state = new; + + /* We consider a not-activated report in the indication as a failure */ + error = (new == MM_MODEM_CDMA_ACTIVATION_STATE_NOT_ACTIVATED ? + g_error_new (MM_CDMA_ACTIVATION_ERROR, + MM_CDMA_ACTIVATION_ERROR_UNKNOWN, + "Activation process failed") : + NULL); + + /* Update activation state in the interface */ + mm_iface_modem_cdma_update_activation_state (MM_IFACE_MODEM_CDMA (self), new, error); + + /* Now, if we have a FINAL state, finish the ongoing activation state request */ + if (new != MM_MODEM_CDMA_ACTIVATION_STATE_ACTIVATING) { + CdmaActivationContext *ctx; + + g_assert (self->priv->activation_ctx != NULL); + ctx = (CdmaActivationContext *)self->priv->activation_ctx; + + /* Disable further indications. */ + cdma_activation_disable_indications (ctx); + + /* If there is any error, finish the async method */ + if (error) { + g_simple_async_result_take_error (ctx->result, error); + cdma_activation_context_complete_and_free (ctx); + return; + } + + /* Otherwise, go on to next step */ + ctx->step++; + cdma_activation_context_step (ctx); + return; + } + + mm_dbg ("Activation process still ongoing..."); +} + +static void +activate_automatic_ready (QmiClientDms *client, + GAsyncResult *res, + CdmaActivationContext *ctx) +{ + QmiMessageDmsActivateAutomaticOutput *output; + GError *error = NULL; + + output = qmi_client_dms_activate_automatic_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + cdma_activation_disable_indications (ctx); + cdma_activation_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_dms_activate_automatic_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't request OTA activation: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_dms_activate_automatic_output_unref (output); + cdma_activation_disable_indications (ctx); + cdma_activation_context_complete_and_free (ctx); + return; + } + + qmi_message_dms_activate_automatic_output_unref (output); + + /* Keep on */ + ctx->step++; + cdma_activation_context_step (ctx); +} + +static void +ser_activation_state_ready (QmiClientDms *client, + GAsyncResult *res, + CdmaActivationContext *ctx) +{ + QmiMessageDmsSetEventReportOutput *output; + GError *error = NULL; + + /* We cannot ignore errors, we NEED the indications to finish the + * activation request properly */ + + output = qmi_client_dms_set_event_report_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + cdma_activation_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_dms_set_event_report_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set event report: "); + g_simple_async_result_take_error (ctx->result, error); + qmi_message_dms_set_event_report_output_unref (output); + cdma_activation_context_complete_and_free (ctx); + return; + } + + qmi_message_dms_set_event_report_output_unref (output); + + /* Setup the indication handler */ + g_assert (ctx->self->priv->activation_event_report_indication_id == 0); + ctx->self->priv->activation_event_report_indication_id = + g_signal_connect (client, + "event-report", + G_CALLBACK (activation_event_report_indication_cb), + ctx->self); + + /* Keep on */ + ctx->step++; + cdma_activation_context_step (ctx); +} + +static void +cdma_activation_context_step (CdmaActivationContext *ctx) +{ + switch (ctx->step) { + case CDMA_ACTIVATION_STEP_FIRST: + ctx->step++; + /* Fall down to next step */ + + case CDMA_ACTIVATION_STEP_ENABLE_INDICATIONS: { + QmiMessageDmsSetEventReportInput *input; + + mm_info ("Automatic activation step [1/5]: enabling indications"); + + input = qmi_message_dms_set_event_report_input_new (); + qmi_message_dms_set_event_report_input_set_activation_state_reporting (input, TRUE, NULL); + qmi_client_dms_set_event_report ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)ser_activation_state_ready, + ctx); + qmi_message_dms_set_event_report_input_unref (input); + return; + } + + case CDMA_ACTIVATION_STEP_REQUEST_ACTIVATION: { + QmiMessageDmsActivateAutomaticInput *input; + + mm_info ("Automatic activation step [2/5]: requesting activation"); + + input = qmi_message_dms_activate_automatic_input_new (); + qmi_message_dms_activate_automatic_input_set_activation_code (input, ctx->carrier_code, NULL); + qmi_client_dms_activate_automatic (ctx->client, + input, + 10, + NULL, + (GAsyncReadyCallback)activate_automatic_ready, + ctx); + qmi_message_dms_activate_automatic_input_unref (input); + return; + } + + case CDMA_ACTIVATION_STEP_WAIT_UNTIL_FINISHED: + mm_info ("Automatic activation step [3/5]: waiting for activation state updates"); + return; + + case CDMA_ACTIVATION_STEP_POWER_CYCLE: + mm_info ("Automatic activation step [4/5]: power-cycling..."); + power_cycle (ctx->self, + (GAsyncReadyCallback)activation_power_cycle_ready, + ctx); + return; + + case CDMA_ACTIVATION_STEP_LAST: + mm_info ("Automatic activation step [5/5]: finished"); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + cdma_activation_context_complete_and_free (ctx); + return; + + default: + g_assert_not_reached (); + } +} + +static void +modem_cdma_activate (MMIfaceModemCdma *_self, + const gchar *carrier_code, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + GSimpleAsyncResult *result; + CdmaActivationContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_cdma_activate); + + /* Fail if we have already an activation ongoing */ + if (self->priv->activation_ctx) { + g_simple_async_result_set_error ( + result, + MM_CORE_ERROR, + MM_CORE_ERROR_IN_PROGRESS, + "An activation operation is already in progress"); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + /* Setup context */ + ctx = g_slice_new0 (CdmaActivationContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = result; + ctx->carrier_code = g_strdup (carrier_code); + ctx->step = CDMA_ACTIVATION_STEP_FIRST; + + /* We keep the activation context in the private data, so that we don't + * allow multiple activation requests at the same time. */ + self->priv->activation_ctx = ctx; + cdma_activation_context_step (ctx); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited registration event handlers + * (3GPP and CDMA interface) */ + +static gboolean +common_setup_cleanup_unsolicited_registration_events_finish (MMBroadbandModemQmi *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +#if defined WITH_NEWEST_QMI_COMMANDS +static void +system_info_indication_cb (QmiClientNas *client, + QmiIndicationNasSystemInfoOutput *output, + MMBroadbandModemQmi *self) +{ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + common_process_system_info_3gpp (self, NULL, output); +} +#endif + +static void +serving_system_indication_cb (QmiClientNas *client, + QmiIndicationNasServingSystemOutput *output, + MMBroadbandModemQmi *self) +{ + if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) + common_process_serving_system_3gpp (self, NULL, output); + else if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + common_process_serving_system_cdma (self, NULL, output); +} + +static void +common_setup_cleanup_unsolicited_registration_events (MMBroadbandModemQmi *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + common_setup_cleanup_unsolicited_registration_events); + + if (enable == self->priv->unsolicited_registration_events_setup) { + mm_dbg ("Unsolicited registration events already %s; skipping", + enable ? "setup" : "cleanup"); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + /* Store new state */ + self->priv->unsolicited_registration_events_setup = enable; + +#if defined WITH_NEWEST_QMI_COMMANDS + /* Signal info introduced in NAS 1.8 */ + if (qmi_client_check_version (client, 1, 8)) { + /* Connect/Disconnect "System Info" indications */ + if (enable) { + g_assert (self->priv->system_info_indication_id == 0); + self->priv->system_info_indication_id = + g_signal_connect (client, + "system-info", + G_CALLBACK (system_info_indication_cb), + self); + } else { + g_assert (self->priv->system_info_indication_id != 0); + g_signal_handler_disconnect (client, self->priv->system_info_indication_id); + self->priv->system_info_indication_id = 0; + } + } else +#endif /* WITH_NEWEST_QMI_COMMANDS */ + { + /* Connect/Disconnect "Serving System" indications */ + if (enable) { + g_assert (self->priv->serving_system_indication_id == 0); + self->priv->serving_system_indication_id = + g_signal_connect (client, + "serving-system", + G_CALLBACK (serving_system_indication_cb), + self); + } else { + g_assert (self->priv->serving_system_indication_id != 0); + g_signal_handler_disconnect (client, self->priv->serving_system_indication_id); + self->priv->serving_system_indication_id = 0; + } + } + + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited registration events (3GPP interface) */ + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_registration_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) + +{ return common_setup_cleanup_unsolicited_registration_events_finish (MM_BROADBAND_MODEM_QMI (self), res, error); +} + +static void +modem_3gpp_cleanup_unsolicited_registration_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_unsolicited_registration_events (MM_BROADBAND_MODEM_QMI (self), + FALSE, + callback, + user_data); +} + +static void +modem_3gpp_setup_unsolicited_registration_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_unsolicited_registration_events (MM_BROADBAND_MODEM_QMI (self), + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* MEID loading (CDMA interface) */ + +static gchar * +modem_cdma_load_meid_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + gchar *meid; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + meid = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded MEID: %s", meid); + return meid; +} + +static void +modem_cdma_load_meid (MMIfaceModemCdma *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_cdma_load_meid); + + if (self->priv->meid) + g_simple_async_result_set_op_res_gpointer (result, + self->priv->meid, + NULL); + else + g_simple_async_result_set_error (result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Device doesn't report a valid MEID"); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* ESN loading (CDMA interface) */ + +static gchar * +modem_cdma_load_esn_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + gchar *esn; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + esn = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))); + mm_dbg ("loaded ESN: %s", esn); + return esn; +} + +static void +modem_cdma_load_esn (MMIfaceModemCdma *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + modem_cdma_load_esn); + + if (self->priv->esn) + g_simple_async_result_set_op_res_gpointer (result, + self->priv->esn, + NULL); + else + g_simple_async_result_set_error (result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Device doesn't report a valid ESN"); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Enabling/disabling unsolicited events (3GPP and CDMA interface) + * + * If NAS >= 1.8: + * - Config Signal Info (only when enabling) + * - Register Indications with Signal Info + * + * If NAS < 1.8: + * - Set Event Report with Signal Strength + */ + +typedef struct { + MMBroadbandModemQmi *self; + GSimpleAsyncResult *result; + QmiClientNas *client; + gboolean enable; +} EnableUnsolicitedEventsContext; + +static void +enable_unsolicited_events_context_complete_and_free (EnableUnsolicitedEventsContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +common_enable_disable_unsolicited_events_finish (MMBroadbandModemQmi *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +ser_signal_strength_ready (QmiClientNas *client, + GAsyncResult *res, + EnableUnsolicitedEventsContext *ctx) +{ + QmiMessageNasSetEventReportOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_set_event_report_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: '%s'", error->message); + g_error_free (error); + } else if (!qmi_message_nas_set_event_report_output_get_result (output, &error)) { + mm_dbg ("Couldn't set event report: '%s'", error->message); + g_error_free (error); + } + + if (output) + qmi_message_nas_set_event_report_output_unref (output); + + /* Just ignore errors for now */ + ctx->self->priv->unsolicited_events_enabled = ctx->enable; + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + enable_unsolicited_events_context_complete_and_free (ctx); +} + +static void +common_enable_disable_unsolicited_events_signal_strength (EnableUnsolicitedEventsContext *ctx) +{ + /* The device doesn't really like to have many threshold values, so don't + * grow this array without checking first */ + static const gint8 thresholds_data[] = { -80, -40, 0, 40, 80 }; + QmiMessageNasSetEventReportInput *input; + GArray *thresholds; + + input = qmi_message_nas_set_event_report_input_new (); + + /* Prepare thresholds, separated 20 each */ + thresholds = g_array_sized_new (FALSE, FALSE, sizeof (gint8), G_N_ELEMENTS (thresholds_data)); + + /* Only set thresholds during enable */ + if (ctx->enable) + g_array_append_vals (thresholds, thresholds_data, G_N_ELEMENTS (thresholds_data)); + + qmi_message_nas_set_event_report_input_set_signal_strength_indicator ( + input, + ctx->enable, + thresholds, + NULL); + g_array_unref (thresholds); + qmi_client_nas_set_event_report ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)ser_signal_strength_ready, + ctx); + qmi_message_nas_set_event_report_input_unref (input); +} + +#if defined WITH_NEWEST_QMI_COMMANDS + +static void +ri_signal_info_ready (QmiClientNas *client, + GAsyncResult *res, + EnableUnsolicitedEventsContext *ctx) +{ + QmiMessageNasRegisterIndicationsOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_register_indications_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: '%s'", error->message); + g_error_free (error); + } else if (!qmi_message_nas_register_indications_output_get_result (output, &error)) { + mm_dbg ("Couldn't register indications: '%s'", error->message); + g_error_free (error); + } + + if (output) + qmi_message_nas_register_indications_output_unref (output); + + /* Just ignore errors for now */ + ctx->self->priv->unsolicited_events_enabled = ctx->enable; + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + enable_unsolicited_events_context_complete_and_free (ctx); +} + +static void +common_enable_disable_unsolicited_events_signal_info (EnableUnsolicitedEventsContext *ctx) +{ + QmiMessageNasRegisterIndicationsInput *input; + + input = qmi_message_nas_register_indications_input_new (); + qmi_message_nas_register_indications_input_set_signal_info (input, ctx->enable, NULL); + qmi_client_nas_register_indications ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)ri_signal_info_ready, + ctx); + qmi_message_nas_register_indications_input_unref (input); +} + +static void +config_signal_info_ready (QmiClientNas *client, + GAsyncResult *res, + EnableUnsolicitedEventsContext *ctx) +{ + QmiMessageNasConfigSignalInfoOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_nas_config_signal_info_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: '%s'", error->message); + g_error_free (error); + } else if (!qmi_message_nas_config_signal_info_output_get_result (output, &error)) { + mm_dbg ("Couldn't config signal info: '%s'", error->message); + g_error_free (error); + } + + if (output) + qmi_message_nas_config_signal_info_output_unref (output); + + /* Keep on */ + common_enable_disable_unsolicited_events_signal_info (ctx); +} + +static void +common_enable_disable_unsolicited_events_signal_info_config (EnableUnsolicitedEventsContext *ctx) +{ + /* RSSI values go between -105 and -60 for 3GPP technologies, + * and from -105 to -90 in 3GPP2 technologies (approx). */ + static const gint8 thresholds_data[] = { -100, -97, -95, -92, -90, -85, -80, -75, -70, -65 }; + QmiMessageNasConfigSignalInfoInput *input; + GArray *thresholds; + + /* Signal info config only to be run when enabling */ + if (!ctx->enable) { + common_enable_disable_unsolicited_events_signal_info (ctx); + return; + } + + input = qmi_message_nas_config_signal_info_input_new (); + + /* Prepare thresholds, separated 20 each */ + thresholds = g_array_sized_new (FALSE, FALSE, sizeof (gint8), G_N_ELEMENTS (thresholds_data)); + g_array_append_vals (thresholds, thresholds_data, G_N_ELEMENTS (thresholds_data)); + + qmi_message_nas_config_signal_info_input_set_rssi_threshold ( + input, + thresholds, + NULL); + g_array_unref (thresholds); + qmi_client_nas_config_signal_info ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)config_signal_info_ready, + ctx); + qmi_message_nas_config_signal_info_input_unref (input); +} + +#endif /* WITH_NEWEST_QMI_COMMANDS */ + +static void +common_enable_disable_unsolicited_events (MMBroadbandModemQmi *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnableUnsolicitedEventsContext *ctx; + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + common_enable_disable_unsolicited_events); + + if (enable == self->priv->unsolicited_events_enabled) { + mm_dbg ("Unsolicited events already %s; skipping", + enable ? "enabled" : "disabled"); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + ctx = g_new0 (EnableUnsolicitedEventsContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->enable = enable; + ctx->result = result; + +#if defined WITH_NEWEST_QMI_COMMANDS + /* Signal info introduced in NAS 1.8 */ + if (qmi_client_check_version (client, 1, 8)) { + common_enable_disable_unsolicited_events_signal_info_config (ctx); + return; + } +#endif /* WITH_NEWEST_QMI_COMMANDS */ + + common_enable_disable_unsolicited_events_signal_strength (ctx); +} + +/*****************************************************************************/ +/* Enable/Disable unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_QMI (self), res, error); +} + +static void +modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + FALSE, + callback, + user_data); +} + +static void +modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Enable/Disable unsolicited events (CDMA interface) */ + +static gboolean +modem_cdma_enable_disable_unsolicited_events_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_QMI (self), res, error); +} + +static void +modem_cdma_disable_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + FALSE, + callback, + user_data); +} + +static void +modem_cdma_enable_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_enable_disable_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited event handlers (3GPP and CDMA interface) */ + +static gboolean +common_setup_cleanup_unsolicited_events_finish (MMBroadbandModemQmi *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +event_report_indication_cb (QmiClientNas *client, + QmiIndicationNasEventReportOutput *output, + MMBroadbandModemQmi *self) +{ + gint8 signal_strength; + QmiNasRadioInterface signal_strength_radio_interface; + + if (qmi_indication_nas_event_report_output_get_signal_strength ( + output, + &signal_strength, + &signal_strength_radio_interface, + NULL)) { + if (qmi_dbm_valid (signal_strength, signal_strength_radio_interface)) { + guint8 quality; + + /* This signal strength comes as negative dBms */ + quality = STRENGTH_TO_QUALITY (signal_strength); + + mm_dbg ("Signal strength indication (%s): %d dBm --> %u%%", + qmi_nas_radio_interface_get_string (signal_strength_radio_interface), + signal_strength, + quality); + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); + mm_iface_modem_update_access_technologies ( + MM_IFACE_MODEM (self), + mm_modem_access_technology_from_qmi_radio_interface (signal_strength_radio_interface), + (MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK | MM_IFACE_MODEM_CDMA_ALL_ACCESS_TECHNOLOGIES_MASK)); + } else { + mm_dbg ("Ignoring invalid signal strength (%s): %d dBm", + qmi_nas_radio_interface_get_string (signal_strength_radio_interface), + signal_strength); + } + } +} + +#if defined WITH_NEWEST_QMI_COMMANDS + +static void +signal_info_indication_cb (QmiClientNas *client, + QmiIndicationNasSignalInfoOutput *output, + MMBroadbandModemQmi *self) +{ + gint8 rssi_max = 0; + gint8 rssi; + guint8 quality; + + /* We do not report per-technology signal quality, so just get the highest + * one of the ones reported. TODO: When several technologies are in use, if + * the indication only contains the data of the one which passed a threshold + * value, we'll need to have an internal cache of per-technology values, in + * order to report always the one with the maximum value. */ + + if (qmi_indication_nas_signal_info_output_get_cdma_signal_strength (output, &rssi, NULL, NULL)) { + mm_dbg ("RSSI (CDMA): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_CDMA_1X)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_indication_nas_signal_info_output_get_hdr_signal_strength (output, &rssi, NULL, NULL, NULL, NULL)) { + mm_dbg ("RSSI (HDR): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_indication_nas_signal_info_output_get_gsm_signal_strength (output, &rssi, NULL)) { + mm_dbg ("RSSI (GSM): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_GSM)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_indication_nas_signal_info_output_get_wcdma_signal_strength (output, &rssi, NULL, NULL)) { + mm_dbg ("RSSI (WCDMA): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_UMTS)) + rssi = MAX (rssi, rssi_max); + } + + if (qmi_indication_nas_signal_info_output_get_lte_signal_strength (output, &rssi, NULL, NULL, NULL, NULL)) { + mm_dbg ("RSSI (LTE): %d dBm", rssi); + if (qmi_dbm_valid (rssi, QMI_NAS_RADIO_INTERFACE_LTE)) + rssi = MAX (rssi, rssi_max); + } + + if (rssi_max < 0) { + /* This RSSI comes as negative dBms */ + quality = STRENGTH_TO_QUALITY (rssi_max); + + mm_dbg ("RSSI: %d dBm --> %u%%", rssi_max, quality); + + mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality); + } else + mm_dbg ("Ignoring invalid signal strength: %d dBm", rssi_max); +} + +#endif /* WITH_NEWEST_QMI_COMMANDS */ + +static void +common_setup_cleanup_unsolicited_events (MMBroadbandModemQmi *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_NAS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + common_enable_disable_unsolicited_events); + + if (enable == self->priv->unsolicited_events_setup) { + mm_dbg ("Unsolicited events already %s; skipping", + enable ? "setup" : "cleanup"); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + /* Store new state */ + self->priv->unsolicited_events_setup = enable; + + /* Connect/Disconnect "Event Report" indications */ + if (enable) { + g_assert (self->priv->event_report_indication_id == 0); + self->priv->event_report_indication_id = + g_signal_connect (client, + "event-report", + G_CALLBACK (event_report_indication_cb), + self); + } else { + g_assert (self->priv->event_report_indication_id != 0); + g_signal_handler_disconnect (client, self->priv->event_report_indication_id); + self->priv->event_report_indication_id = 0; + } + +#if defined WITH_NEWEST_QMI_COMMANDS + /* Connect/Disconnect "Signal Info" indications. + * Signal info introduced in NAS 1.8 */ + if (qmi_client_check_version (client, 1, 8)) { + if (enable) { + g_assert (self->priv->signal_info_indication_id == 0); + self->priv->signal_info_indication_id = + g_signal_connect (client, + "signal-info", + G_CALLBACK (signal_info_indication_cb), + self); + } else { + g_assert (self->priv->signal_info_indication_id != 0); + g_signal_handler_disconnect (client, self->priv->signal_info_indication_id); + self->priv->signal_info_indication_id = 0; + } + } +#endif /* WITH_NEWEST_QMI_COMMANDS */ + + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Enable/Disable unsolicited events (3GPP interface) */ + +static gboolean +modem_3gpp_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gpp *self, + GAsyncResult *res, + GError **error) +{ + return common_setup_cleanup_unsolicited_events_finish (MM_BROADBAND_MODEM_QMI (self), res, error); +} + +static void +modem_3gpp_cleanup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + FALSE, + callback, + user_data); +} + +static void +modem_3gpp_setup_unsolicited_events (MMIfaceModem3gpp *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Enable/Disable unsolicited events (CDMA interface) */ + +static gboolean +modem_cdma_setup_cleanup_unsolicited_events_finish (MMIfaceModemCdma *self, + GAsyncResult *res, + GError **error) +{ + return common_setup_cleanup_unsolicited_events_finish (MM_BROADBAND_MODEM_QMI (self), res, error); +} + +static void +modem_cdma_cleanup_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + FALSE, + callback, + user_data); +} + +static void +modem_cdma_setup_unsolicited_events (MMIfaceModemCdma *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Check support (Messaging interface) */ + +static gboolean +messaging_check_support_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + /* no error expected here */ + return g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res)); +} + +static void +messaging_check_support (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + gboolean supported; + MMQmiPort *port; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + messaging_check_support); + + port = mm_base_modem_peek_port_qmi (MM_BASE_MODEM (self)); + if (!port) + supported = FALSE; + else + /* If we have support for the WMS client, messaging is supported */ + supported = !!mm_qmi_port_peek_client (port, + QMI_SERVICE_WMS, + MM_QMI_PORT_FLAG_DEFAULT); + + /* We only handle 3GPP messaging (PDU based) currently, so just ignore + * CDMA-only QMI modems */ + if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self)) && supported) { + mm_dbg ("Messaging capabilities supported by this modem, " + "but 3GPP2 messaging not supported yet by ModemManager"); + supported = FALSE; + } else + mm_dbg ("Messaging capabilities %s by this modem", + supported ? "supported" : "not supported"); + + g_simple_async_result_set_op_res_gboolean (result, supported); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Load supported storages (Messaging interface) */ + +static gboolean +messaging_load_supported_storages_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GArray **mem1, + GArray **mem2, + GArray **mem3, + GError **error) +{ + MMSmsStorage supported [2] = { MM_SMS_STORAGE_SM, MM_SMS_STORAGE_ME }; + + *mem1 = g_array_append_vals (g_array_sized_new (FALSE, FALSE, sizeof (MMSmsStorage), 2), + supported, 2); + *mem2 = g_array_ref (*mem1); + *mem3 = g_array_ref (*mem1); + + return TRUE; +} + +static void +messaging_load_supported_storages (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + messaging_load_supported_storages); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Set default storage (Messaging interface) */ + +static gboolean +messaging_set_default_storage_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +wms_set_routes_ready (QmiClientWms *client, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + QmiMessageWmsSetRoutesOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_wms_set_routes_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (simple, error); + } else if (!qmi_message_wms_set_routes_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set routes: "); + g_simple_async_result_take_error (simple, error); + } else { + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + } + + if (output) + qmi_message_wms_set_routes_output_unref (output); + + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +messaging_set_default_storage (MMIfaceModemMessaging *self, + MMSmsStorage storage, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + QmiMessageWmsSetRoutesInput *input; + GArray *routes_array; + QmiMessageWmsSetRoutesInputRouteListElement route; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_WMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + messaging_set_default_storage); + + /* Build routes array and add it as input + * Just worry about Class 0 and Class 1 messages for now */ + input = qmi_message_wms_set_routes_input_new (); + routes_array = g_array_sized_new (FALSE, FALSE, sizeof (route), 2); + route.message_type = QMI_WMS_MESSAGE_TYPE_POINT_TO_POINT; + route.message_class = QMI_WMS_MESSAGE_CLASS_0; + route.storage = mm_sms_storage_to_qmi_storage_type (storage); + route.receipt_action = QMI_WMS_RECEIPT_ACTION_STORE_AND_NOTIFY; + g_array_append_val (routes_array, route); + route.message_class = QMI_WMS_MESSAGE_CLASS_1; + g_array_append_val (routes_array, route); + qmi_message_wms_set_routes_input_set_route_list (input, routes_array, NULL); + + mm_dbg ("setting default messaging routes..."); + qmi_client_wms_set_routes (QMI_CLIENT_WMS (client), + input, + 5, + NULL, + (GAsyncReadyCallback)wms_set_routes_ready, + result); + + qmi_message_wms_set_routes_input_unref (input); + g_array_unref (routes_array); +} + +/*****************************************************************************/ +/* Load initial SMS parts */ + +typedef enum { + LOAD_INITIAL_SMS_PARTS_STEP_FIRST, + LOAD_INITIAL_SMS_PARTS_STEP_LIST_ALL, + LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_READ, + LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_NOT_READ, + LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_SENT, + LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_NOT_SENT, + LOAD_INITIAL_SMS_PARTS_STEP_LAST +} LoadInitialSmsPartsStep; + +typedef struct { + MMBroadbandModemQmi *self; + GSimpleAsyncResult *result; + QmiClientWms *client; + MMSmsStorage storage; + LoadInitialSmsPartsStep step; + + /* For each step */ + GArray *message_array; + guint i; +} LoadInitialSmsPartsContext; + +static void +load_initial_sms_parts_context_complete_and_free (LoadInitialSmsPartsContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + + if (ctx->message_array) + g_array_unref (ctx->message_array); + + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (LoadInitialSmsPartsContext, ctx); +} + +static gboolean +load_initial_sms_parts_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void read_next_sms_part (LoadInitialSmsPartsContext *ctx); + +static void +add_new_read_sms_part (MMIfaceModemMessaging *self, + QmiWmsStorageType storage, + guint32 index, + QmiWmsMessageTagType tag, + QmiWmsMessageFormat format, + GArray *data) +{ + switch (format) { + case QMI_WMS_MESSAGE_FORMAT_CDMA: + mm_dbg ("Skipping CDMA messages for now..."); + break; + case QMI_WMS_MESSAGE_FORMAT_MWI: + mm_dbg ("Don't know how to process 'message waiting indicator' messages"); + break; + case QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT: + case QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_BROADCAST: { + MMSmsPart *part; + GError *error = NULL; + + part = mm_sms_part_new_from_binary_pdu (index, + (guint8 *)data->data, + data->len, + &error); + if (part) { + mm_dbg ("Correctly parsed PDU (%d)", + index); + mm_iface_modem_messaging_take_part (self, + part, + mm_sms_state_from_qmi_message_tag (tag), + mm_sms_storage_from_qmi_storage_type (storage)); + } else { + /* Don't treat the error as critical */ + mm_dbg ("Error parsing PDU (%d): %s", + index, + error->message); + g_error_free (error); + } + + break; + } + default: + mm_dbg ("Unhandled message format '%u'", format); + break; + } +} + +static void +wms_raw_read_ready (QmiClientWms *client, + GAsyncResult *res, + LoadInitialSmsPartsContext *ctx) +{ + QmiMessageWmsRawReadOutput *output = NULL; + GError *error = NULL; + + /* Ignore errors, just keep on with the next messages */ + + output = qmi_client_wms_raw_read_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_wms_raw_read_output_get_result (output, &error)) { + mm_dbg ("Couldn't read raw message: %s", error->message); + g_error_free (error); + } else { + QmiWmsMessageTagType tag; + QmiWmsMessageFormat format; + GArray *data; + QmiMessageWmsListMessagesOutputMessageListElement *message; + + message = &g_array_index (ctx->message_array, + QmiMessageWmsListMessagesOutputMessageListElement, + ctx->i); + + qmi_message_wms_raw_read_output_get_raw_message_data ( + output, + &tag, + &format, + &data, + NULL); + add_new_read_sms_part (MM_IFACE_MODEM_MESSAGING (ctx->self), + mm_sms_storage_to_qmi_storage_type (ctx->storage), + message->memory_index, + tag, + format, + data); + } + + if (output) + qmi_message_wms_raw_read_output_unref (output); + + /* Keep on reading parts */ + ctx->i++; + read_next_sms_part (ctx); +} + +static void load_initial_sms_parts_step (LoadInitialSmsPartsContext *ctx); + +static void +read_next_sms_part (LoadInitialSmsPartsContext *ctx) +{ + QmiMessageWmsListMessagesOutputMessageListElement *message; + QmiMessageWmsRawReadInput *input; + + if (ctx->i >= ctx->message_array->len || + !ctx->message_array) { + /* If we just listed all SMS, we're done. Otherwise go to next tag. */ + if (ctx->step == LOAD_INITIAL_SMS_PARTS_STEP_LIST_ALL) + ctx->step = LOAD_INITIAL_SMS_PARTS_STEP_LAST; + else + ctx->step++; + load_initial_sms_parts_step (ctx); + return; + } + + message = &g_array_index (ctx->message_array, + QmiMessageWmsListMessagesOutputMessageListElement, + ctx->i); + + input = qmi_message_wms_raw_read_input_new (); + qmi_message_wms_raw_read_input_set_message_memory_storage_id ( + input, + mm_sms_storage_to_qmi_storage_type (ctx->storage), + message->memory_index, + NULL); + /* Only reading 3GPP SMS for now */ + qmi_message_wms_raw_read_input_set_message_mode ( + input, + QMI_WMS_MESSAGE_MODE_GSM_WCDMA, + NULL); + qmi_client_wms_raw_read (QMI_CLIENT_WMS (ctx->client), + input, + 3, + NULL, + (GAsyncReadyCallback)wms_raw_read_ready, + ctx); + qmi_message_wms_raw_read_input_unref (input); +} + +static void +wms_list_messages_ready (QmiClientWms *client, + GAsyncResult *res, + LoadInitialSmsPartsContext *ctx) +{ + QmiMessageWmsListMessagesOutput *output = NULL; + GError *error = NULL; + GArray *message_array; + + output = qmi_client_wms_list_messages_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + load_initial_sms_parts_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_wms_list_messages_output_get_result (output, &error)) { + /* Ignore error, keep on */ + g_debug ("Couldn't read SMS messages: %s", error->message); + g_error_free (error); + ctx->step++; + load_initial_sms_parts_step (ctx); + return; + } + + qmi_message_wms_list_messages_output_get_message_list ( + output, + &message_array, + NULL); + + /* Keep a reference to the array ourselves */ + ctx->message_array = g_array_ref (message_array); + + qmi_message_wms_list_messages_output_unref (output); + + /* Start reading parts */ + ctx->i = 0; + read_next_sms_part (ctx); +} + +static void +load_initial_sms_parts_step (LoadInitialSmsPartsContext *ctx) +{ + QmiMessageWmsListMessagesInput *input; + + /* Request to list messages in a given storage */ + input = qmi_message_wms_list_messages_input_new (); + qmi_message_wms_list_messages_input_set_storage_type ( + input, + mm_sms_storage_to_qmi_storage_type (ctx->storage), + NULL); + qmi_message_wms_list_messages_input_set_message_mode (input, + QMI_WMS_MESSAGE_MODE_GSM_WCDMA, + NULL); + + switch (ctx->step) { + case LOAD_INITIAL_SMS_PARTS_STEP_FIRST: + ctx->step++; + /* Fall down */ + case LOAD_INITIAL_SMS_PARTS_STEP_LIST_ALL: + mm_dbg ("loading all messages from storage '%s'...", + mm_sms_storage_get_string (ctx->storage)); + break; + case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_READ: + mm_dbg ("loading MT-read messages from storage '%s'...", + mm_sms_storage_get_string (ctx->storage)); + qmi_message_wms_list_messages_input_set_message_tag ( + input, + QMI_WMS_MESSAGE_TAG_TYPE_MT_READ, + NULL); + break; + case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MT_NOT_READ: + mm_dbg ("loading MT-not-read messages from storage '%s'...", + mm_sms_storage_get_string (ctx->storage)); + qmi_message_wms_list_messages_input_set_message_tag ( + input, + QMI_WMS_MESSAGE_TAG_TYPE_MT_NOT_READ, + NULL); + break; + case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_SENT: + mm_dbg ("loading MO-sent messages from storage '%s'...", + mm_sms_storage_get_string (ctx->storage)); + qmi_message_wms_list_messages_input_set_message_tag ( + input, + QMI_WMS_MESSAGE_TAG_TYPE_MO_SENT, + NULL); + break; + case LOAD_INITIAL_SMS_PARTS_STEP_LIST_MO_NOT_SENT: + mm_dbg ("loading MO-not-sent messages from storage '%s'...", + mm_sms_storage_get_string (ctx->storage)); + qmi_message_wms_list_messages_input_set_message_tag ( + input, + QMI_WMS_MESSAGE_TAG_TYPE_MO_NOT_SENT, + NULL); + break; + case LOAD_INITIAL_SMS_PARTS_STEP_LAST: + /* All steps done */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + load_initial_sms_parts_context_complete_and_free (ctx); + return; + } + + qmi_client_wms_list_messages (QMI_CLIENT_WMS (ctx->client), + input, + 5, + NULL, + (GAsyncReadyCallback)wms_list_messages_ready, + ctx); + qmi_message_wms_list_messages_input_unref (input); +} + +static void +load_initial_sms_parts (MMIfaceModemMessaging *self, + MMSmsStorage storage, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadInitialSmsPartsContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_WMS, &client, + callback, user_data)) + return; + + ctx = g_slice_new0 (LoadInitialSmsPartsContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->storage = storage; + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + load_initial_sms_parts); + ctx->step = LOAD_INITIAL_SMS_PARTS_STEP_FIRST; + + load_initial_sms_parts_step (ctx); +} + +/*****************************************************************************/ +/* Setup/Cleanup unsolicited event handlers (Messaging interface) */ + +typedef struct { + MMIfaceModemMessaging *self; + QmiClientWms *client; + QmiWmsStorageType storage; + guint32 memory_index; + QmiWmsMessageMode message_mode; +} IndicationRawReadContext; + +static void +indication_raw_read_context_free (IndicationRawReadContext *ctx) +{ + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (IndicationRawReadContext, ctx); +} + +static void +wms_indication_raw_read_ready (QmiClientWms *client, + GAsyncResult *res, + IndicationRawReadContext *ctx) +{ + QmiMessageWmsRawReadOutput *output = NULL; + GError *error = NULL; + + /* Ignore errors */ + + output = qmi_client_wms_raw_read_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: %s", error->message); + g_error_free (error); + } else if (!qmi_message_wms_raw_read_output_get_result (output, &error)) { + mm_dbg ("Couldn't read raw message: %s", error->message); + g_error_free (error); + } else { + QmiWmsMessageTagType tag; + QmiWmsMessageFormat format; + GArray *data; + + qmi_message_wms_raw_read_output_get_raw_message_data ( + output, + &tag, + &format, + &data, + NULL); + add_new_read_sms_part (MM_IFACE_MODEM_MESSAGING (ctx->self), + ctx->storage, + ctx->memory_index, + tag, + format, + data); + } + + if (output) + qmi_message_wms_raw_read_output_unref (output); + + indication_raw_read_context_free (ctx); +} + +static void +messaging_event_report_indication_cb (QmiClientNas *client, + QmiIndicationWmsEventReportOutput *output, + MMBroadbandModemQmi *self) +{ + QmiWmsStorageType storage; + guint32 memory_index; + + /* Currently ignoring transfer-route MT messages */ + + if (qmi_indication_wms_event_report_output_get_mt_message ( + output, + &storage, + &memory_index, + NULL)) { + IndicationRawReadContext *ctx; + QmiMessageWmsRawReadInput *input; + + ctx = g_slice_new (IndicationRawReadContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->storage = storage; + ctx->memory_index = memory_index; + + input = qmi_message_wms_raw_read_input_new (); + qmi_message_wms_raw_read_input_set_message_memory_storage_id ( + input, + storage, + memory_index, + NULL); + + /* Default to 3GPP message mode if none given */ + if (!qmi_indication_wms_event_report_output_get_message_mode ( + output, + &ctx->message_mode, + NULL)) + ctx->message_mode = QMI_WMS_MESSAGE_MODE_GSM_WCDMA; + qmi_message_wms_raw_read_input_set_message_mode ( + input, + ctx->message_mode, + NULL); + + qmi_client_wms_raw_read (QMI_CLIENT_WMS (client), + input, + 3, + NULL, + (GAsyncReadyCallback)wms_indication_raw_read_ready, + ctx); + qmi_message_wms_raw_read_input_unref (input); + } +} + +static gboolean +messaging_setup_cleanup_unsolicited_events_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +common_setup_cleanup_messaging_unsolicited_events (MMBroadbandModemQmi *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_WMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + common_setup_cleanup_messaging_unsolicited_events); + + if (enable == self->priv->messaging_unsolicited_events_setup) { + mm_dbg ("Messaging unsolicited events already %s; skipping", + enable ? "setup" : "cleanup"); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + /* Store new state */ + self->priv->messaging_unsolicited_events_setup = enable; + + /* Connect/Disconnect "Event Report" indications */ + if (enable) { + g_assert (self->priv->messaging_event_report_indication_id == 0); + self->priv->messaging_event_report_indication_id = + g_signal_connect (client, + "event-report", + G_CALLBACK (messaging_event_report_indication_cb), + self); + } else { + g_assert (self->priv->messaging_event_report_indication_id != 0); + g_signal_handler_disconnect (client, self->priv->messaging_event_report_indication_id); + self->priv->messaging_event_report_indication_id = 0; + } + + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +static void +messaging_cleanup_unsolicited_events (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_messaging_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + FALSE, + callback, + user_data); +} + +static void +messaging_setup_unsolicited_events (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_setup_cleanup_messaging_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Enable/Disable unsolicited events (Messaging interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + GSimpleAsyncResult *result; + QmiClientWms *client; + gboolean enable; +} EnableMessagingUnsolicitedEventsContext; + +static void +enable_messaging_unsolicited_events_context_complete_and_free (EnableMessagingUnsolicitedEventsContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gboolean +messaging_enable_disable_unsolicited_events_finish (MMIfaceModemMessaging *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +ser_messaging_indicator_ready (QmiClientWms *client, + GAsyncResult *res, + EnableMessagingUnsolicitedEventsContext *ctx) +{ + QmiMessageWmsSetEventReportOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_wms_set_event_report_finish (client, res, &error); + if (!output) { + mm_dbg ("QMI operation failed: '%s'", error->message); + g_error_free (error); + } else if (!qmi_message_wms_set_event_report_output_get_result (output, &error)) { + mm_dbg ("Couldn't set event report: '%s'", error->message); + g_error_free (error); + } + + if (output) + qmi_message_wms_set_event_report_output_unref (output); + + /* Just ignore errors for now */ + ctx->self->priv->messaging_unsolicited_events_enabled = ctx->enable; + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + enable_messaging_unsolicited_events_context_complete_and_free (ctx); +} + +static void +common_enable_disable_messaging_unsolicited_events (MMBroadbandModemQmi *self, + gboolean enable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnableMessagingUnsolicitedEventsContext *ctx; + GSimpleAsyncResult *result; + QmiClient *client = NULL; + QmiMessageWmsSetEventReportInput *input; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_WMS, &client, + callback, user_data)) + return; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + common_enable_disable_messaging_unsolicited_events); + + if (enable == self->priv->messaging_unsolicited_events_enabled) { + mm_dbg ("Messaging unsolicited events already %s; skipping", + enable ? "enabled" : "disabled"); + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + ctx = g_new0 (EnableMessagingUnsolicitedEventsContext, 1); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->enable = enable; + ctx->result = result; + + input = qmi_message_wms_set_event_report_input_new (); + + qmi_message_wms_set_event_report_input_set_new_mt_message_indicator ( + input, + ctx->enable, + NULL); + qmi_client_wms_set_event_report ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)ser_messaging_indicator_ready, + ctx); + qmi_message_wms_set_event_report_input_unref (input); +} + +static void +messaging_disable_unsolicited_events (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_enable_disable_messaging_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + FALSE, + callback, + user_data); +} + +static void +messaging_enable_unsolicited_events (MMIfaceModemMessaging *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + common_enable_disable_messaging_unsolicited_events (MM_BROADBAND_MODEM_QMI (self), + TRUE, + callback, + user_data); +} + +/*****************************************************************************/ +/* Create SMS (Messaging interface) */ + +static MMSms * +messaging_create_sms (MMIfaceModemMessaging *self) +{ + return mm_sms_qmi_new (MM_BASE_MODEM (self)); +} + +/*****************************************************************************/ +/* Location capabilities loading (Location interface) */ + +static MMModemLocationSource +location_load_capabilities_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return MM_MODEM_LOCATION_SOURCE_NONE; + + return (MMModemLocationSource) GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer ( + G_SIMPLE_ASYNC_RESULT (res))); +} + +static void +parent_load_capabilities_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + MMModemLocationSource sources; + GError *error = NULL; + MMQmiPort *port; + + sources = iface_modem_location_parent->load_capabilities_finish (self, res, &error); + if (error) { + g_simple_async_result_take_error (simple, error); + g_simple_async_result_complete (simple); + g_object_unref (simple); + return; + } + + port = mm_base_modem_peek_port_qmi (MM_BASE_MODEM (self)); + + /* Now our own checks */ + + /* If we have support for the PDS client, GPS location is supported */ + if (port && mm_qmi_port_peek_client (port, + QMI_SERVICE_PDS, + MM_QMI_PORT_FLAG_DEFAULT)) + sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW); + + /* If the modem is CDMA, we have support for CDMA BS location */ + if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) + sources |= MM_MODEM_LOCATION_SOURCE_CDMA_BS; + + /* So we're done, complete */ + g_simple_async_result_set_op_res_gpointer (simple, + GUINT_TO_POINTER (sources), + NULL); + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +location_load_capabilities (MMIfaceModemLocation *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + location_load_capabilities); + + /* Chain up parent's setup */ + iface_modem_location_parent->load_capabilities ( + self, + (GAsyncReadyCallback)parent_load_capabilities_ready, + result); +} + +/*****************************************************************************/ +/* Disable location gathering (Location interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientPds *client; + GSimpleAsyncResult *result; +} DisableLocationGatheringContext; + +static void +disable_location_gathering_context_complete_and_free (DisableLocationGatheringContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + if (ctx->client) + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (DisableLocationGatheringContext, ctx); +} + +static gboolean +disable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +gps_service_state_stop_ready (QmiClientPds *client, + GAsyncResult *res, + DisableLocationGatheringContext *ctx) +{ + QmiMessagePdsSetGpsServiceStateOutput *output = NULL; + 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_simple_async_result_take_error (ctx->result, error); + disable_location_gathering_context_complete_and_free (ctx); + 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_simple_async_result_take_error (ctx->result, error); + disable_location_gathering_context_complete_and_free (ctx); + 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); + + mm_dbg ("Removing location event report indication handling"); + g_assert (ctx->self->priv->location_event_report_indication_id != 0); + g_signal_handler_disconnect (client, ctx->self->priv->location_event_report_indication_id); + ctx->self->priv->location_event_report_indication_id = 0; + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + disable_location_gathering_context_complete_and_free (ctx); +} + +static void +disable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DisableLocationGatheringContext *ctx; + QmiClient *client = NULL; + gboolean stop_gps = FALSE; + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + disable_location_gathering); + + /* Nothing to be done to disable 3GPP or CDMA locations */ + if (source == MM_MODEM_LOCATION_SOURCE_3GPP_LAC_CI || + source == MM_MODEM_LOCATION_SOURCE_CDMA_BS) { + g_simple_async_result_set_op_res_gboolean (result, TRUE); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); + return; + } + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_PDS, &client, + callback, user_data)) { + g_object_unref (result); + return; + } + + ctx = g_slice_new0 (DisableLocationGatheringContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = result; + + /* Only stop GPS engine if no GPS-related sources enabled */ + if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + ctx->self->priv->enabled_sources &= ~source; + + if (!(ctx->self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW))) + stop_gps = TRUE; + } + + if (stop_gps) { + 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 ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_service_state_stop_ready, + ctx); + qmi_message_pds_set_gps_service_state_input_unref (input); + return; + } + + /* If still some GPS needed, just return */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + disable_location_gathering_context_complete_and_free (ctx); +} + +/*****************************************************************************/ +/* Enable location gathering (Location interface) */ + +static void +location_event_report_indication_cb (QmiClientPds *client, + QmiIndicationPdsEventReportOutput *output, + MMBroadbandModemQmi *self) +{ + QmiPdsPositionSessionStatus session_status; + const gchar *nmea; + + if (qmi_indication_pds_event_report_output_get_position_session_status ( + output, + &session_status, + NULL)) { + mm_dbg ("[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_dbg ("[NMEA] %s", nmea); + mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea); + } +} + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientPds *client; + GSimpleAsyncResult *result; + MMModemLocationSource source; +} EnableLocationGatheringContext; + +static void +enable_location_gathering_context_complete_and_free (EnableLocationGatheringContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + if (ctx->client) + g_object_unref (ctx->client); + g_object_unref (ctx->self); + g_slice_free (EnableLocationGatheringContext, ctx); +} + +static gboolean +enable_location_gathering_finish (MMIfaceModemLocation *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +ser_location_ready (QmiClientPds *client, + GAsyncResult *res, + EnableLocationGatheringContext *ctx) +{ + QmiMessagePdsSetEventReportOutput *output = NULL; + GError *error = NULL; + + output = qmi_client_pds_set_event_report_finish (client, res, &error); + if (!output) { + g_prefix_error (&error, "QMI operation failed: "); + g_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_pds_set_event_report_output_get_result (output, &error)) { + g_prefix_error (&error, "Couldn't set event report: "); + g_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + qmi_message_pds_set_event_report_output_unref (output); + return; + } + + qmi_message_pds_set_event_report_output_unref (output); + + mm_dbg ("Adding location event report indication handling"); + g_assert (ctx->self->priv->location_event_report_indication_id == 0); + ctx->self->priv->location_event_report_indication_id = + g_signal_connect (client, + "event-report", + G_CALLBACK (location_event_report_indication_cb), + ctx->self); + + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + enable_location_gathering_context_complete_and_free (ctx); +} + +static void +auto_tracking_state_start_ready (QmiClientPds *client, + GAsyncResult *res, + EnableLocationGatheringContext *ctx) +{ + 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_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + 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_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + 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 ( + ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)ser_location_ready, + ctx); + qmi_message_pds_set_event_report_input_unref (input); +} + +static void +gps_service_state_start_ready (QmiClientPds *client, + GAsyncResult *res, + EnableLocationGatheringContext *ctx) +{ + QmiMessagePdsSetAutoTrackingStateInput *input; + QmiMessagePdsSetGpsServiceStateOutput *output = NULL; + 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_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + 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_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + 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 ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)auto_tracking_state_start_ready, + ctx); + qmi_message_pds_set_auto_tracking_state_input_unref (input); +} + +static void +parent_enable_location_gathering_ready (MMIfaceModemLocation *self, + GAsyncResult *res, + EnableLocationGatheringContext *ctx) +{ + gboolean start_gps = FALSE; + GError *error = NULL; + + if (!iface_modem_location_parent->enable_location_gathering_finish (self, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + return; + } + + /* Now our own enabling */ + + /* CDMA modems need to re-run registration checks when enabling the CDMA BS + * location source, so that we get up to date BS location information. + * Note that we don't care for when the registration checks get finished. + */ + if (ctx->source == MM_MODEM_LOCATION_SOURCE_CDMA_BS && + mm_iface_modem_is_cdma (MM_IFACE_MODEM (self))) { + /* Reload registration to get LAC/CI */ + mm_iface_modem_cdma_run_registration_checks (MM_IFACE_MODEM_CDMA (self), NULL, NULL); + } + + /* NMEA and RAW are both enabled in the same way */ + if (ctx->source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { + /* Only start GPS engine if not done already */ + if (!(ctx->self->priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | + MM_MODEM_LOCATION_SOURCE_GPS_RAW))) + start_gps = TRUE; + ctx->self->priv->enabled_sources |= ctx->source; + } + + if (start_gps) { + QmiMessagePdsSetGpsServiceStateInput *input; + QmiClient *client; + + client = peek_qmi_client (ctx->self, QMI_SERVICE_PDS, &error); + if (!client) { + g_simple_async_result_take_error (ctx->result, error); + enable_location_gathering_context_complete_and_free (ctx); + return; + } + + /* Keep a ref around */ + ctx->client = g_object_ref (client); + + 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 ( + ctx->client, + input, + 10, + NULL, /* cancellable */ + (GAsyncReadyCallback)gps_service_state_start_ready, + ctx); + qmi_message_pds_set_gps_service_state_input_unref (input); + return; + } + + /* For any other location (e.g. 3GPP), or if GPS already running just return */ + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + enable_location_gathering_context_complete_and_free (ctx); +} + +static void +enable_location_gathering (MMIfaceModemLocation *self, + MMModemLocationSource source, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnableLocationGatheringContext *ctx; + + ctx = g_slice_new0 (EnableLocationGatheringContext); + ctx->self = g_object_ref (self); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + enable_location_gathering); + ctx->source = source; + + /* Chain up parent's gathering enable */ + iface_modem_location_parent->enable_location_gathering ( + self, + source, + (GAsyncReadyCallback)parent_enable_location_gathering_ready, + ctx); +} + +/*****************************************************************************/ +/* Check firmware support (Firmware interface) */ + +typedef struct { + gchar *build_id; + GArray *modem_unique_id; + GArray *pri_unique_id; + gboolean current; +} FirmwarePair; + +static void +firmware_pair_free (FirmwarePair *pair) +{ + g_free (pair->build_id); + g_array_unref (pair->modem_unique_id); + g_array_unref (pair->pri_unique_id); + g_slice_free (FirmwarePair, pair); +} + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientDms *client; + GSimpleAsyncResult *result; + GList *pairs; + GList *l; +} FirmwareCheckSupportContext; + +static void +firmware_check_support_context_complete_and_free (FirmwareCheckSupportContext *ctx) +{ + g_simple_async_result_complete (ctx->result); + g_object_unref (ctx->result); + g_list_free_full (ctx->pairs, (GDestroyNotify)firmware_pair_free); + g_object_unref (ctx->self); + g_object_unref (ctx->client); + g_slice_free (FirmwareCheckSupportContext, ctx); +} + +static gboolean +firmware_check_support_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error) +{ + /* Never fails, just says TRUE or FALSE */ + return g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res)); +} + +static void get_next_image_info (FirmwareCheckSupportContext *ctx); + +static void +get_pri_image_info_ready (QmiClientDms *client, + GAsyncResult *res, + FirmwareCheckSupportContext *ctx) +{ + QmiMessageDmsGetStoredImageInfoOutput *output; + GError *error = NULL; + FirmwarePair *current; + + current = (FirmwarePair *)ctx->l->data; + + output = qmi_client_dms_get_stored_image_info_finish (client, res, &error); + if (!output || + !qmi_message_dms_get_stored_image_info_output_get_result (output, &error)) { + mm_warn ("Couldn't get detailed info for PRI image with build ID '%s': %s", + current->build_id, + error->message); + g_error_free (error); + } else { + gchar *unique_id_str; + MMFirmwareProperties *firmware; + + firmware = mm_firmware_properties_new (MM_FIRMWARE_IMAGE_TYPE_GOBI, + current->build_id); + + unique_id_str = mm_utils_bin2hexstr ((const guint8 *)current->pri_unique_id->data, + current->pri_unique_id->len); + mm_firmware_properties_set_gobi_pri_unique_id (firmware, unique_id_str); + g_free (unique_id_str); + + unique_id_str = mm_utils_bin2hexstr ((const guint8 *)current->modem_unique_id->data, + current->modem_unique_id->len); + mm_firmware_properties_set_gobi_modem_unique_id (firmware, unique_id_str); + g_free (unique_id_str); + + /* Boot version (optional) */ + { + guint16 boot_major_version; + guint16 boot_minor_version; + + if (qmi_message_dms_get_stored_image_info_output_get_boot_version ( + output, + &boot_major_version, + &boot_minor_version, + NULL)) { + gchar *aux; + + aux = g_strdup_printf ("%u.%u", boot_major_version, boot_minor_version); + mm_firmware_properties_set_gobi_boot_version (firmware, aux); + g_free (aux); + } + } + + /* PRI version (optional) */ + { + guint32 pri_version; + const gchar *pri_info; + + if (qmi_message_dms_get_stored_image_info_output_get_pri_version ( + output, + &pri_version, + &pri_info, + NULL)) { + gchar *aux; + + aux = g_strdup_printf ("%u", pri_version); + mm_firmware_properties_set_gobi_pri_version (firmware, aux); + g_free (aux); + + mm_firmware_properties_set_gobi_pri_info (firmware, pri_info); + } + } + + /* Add firmware image to our internal list */ + ctx->self->priv->firmware_list = g_list_append (ctx->self->priv->firmware_list, + firmware); + + /* If this is is also the current image running, keep it */ + if (current->current) { + if (ctx->self->priv->current_firmware) + mm_warn ("A current firmware is already set (%s), not setting '%s' as current", + mm_firmware_properties_get_unique_id (ctx->self->priv->current_firmware), + current->build_id); + else + ctx->self->priv->current_firmware = g_object_ref (firmware); + + } + + qmi_message_dms_get_stored_image_info_output_unref (output); + } + + /* Go on to the next one */ + ctx->l = g_list_next (ctx->l); + get_next_image_info (ctx); +} + +static void +get_next_image_info (FirmwareCheckSupportContext *ctx) +{ + QmiMessageDmsGetStoredImageInfoInputImage image_id; + QmiMessageDmsGetStoredImageInfoInput *input; + FirmwarePair *current; + + if (!ctx->l) { + /* We're done */ + + if (!ctx->self->priv->firmware_list) { + mm_warn ("No valid firmware images listed. " + "Assuming firmware unsupported."); + g_simple_async_result_set_op_res_gboolean (ctx->result, FALSE); + } else + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + firmware_check_support_context_complete_and_free (ctx); + return; + } + + current = (FirmwarePair *)ctx->l->data; + + /* Query PRI image info */ + image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI; + image_id.unique_id = current->pri_unique_id; + image_id.build_id = current->build_id; + input = qmi_message_dms_get_stored_image_info_input_new (); + qmi_message_dms_get_stored_image_info_input_set_image (input, &image_id, NULL); + qmi_client_dms_get_stored_image_info (ctx->client, + input, + 10, + NULL, + (GAsyncReadyCallback)get_pri_image_info_ready, + ctx); + qmi_message_dms_get_stored_image_info_input_unref (input); +} + +static void +list_stored_images_ready (QmiClientDms *client, + GAsyncResult *res, + FirmwareCheckSupportContext *ctx) +{ + GArray *array; + gint pri_id; + gint modem_id; + guint i; + guint j; + QmiMessageDmsListStoredImagesOutputListImage *image_pri; + QmiMessageDmsListStoredImagesOutputListImage *image_modem; + QmiMessageDmsListStoredImagesOutput *output; + + output = qmi_client_dms_list_stored_images_finish (client, res, NULL); + if (!output || + !qmi_message_dms_list_stored_images_output_get_result (output, NULL)) { + /* Assume firmware unsupported */ + g_simple_async_result_set_op_res_gboolean (ctx->result, FALSE); + firmware_check_support_context_complete_and_free (ctx); + if (output) + qmi_message_dms_list_stored_images_output_unref (output); + return; + } + + qmi_message_dms_list_stored_images_output_get_list ( + output, + &array, + NULL); + + /* Find which index corresponds to each image type */ + pri_id = -1; + modem_id = -1; + for (i = 0; i < array->len; i++) { + QmiMessageDmsListStoredImagesOutputListImage *image; + + image = &g_array_index (array, + QmiMessageDmsListStoredImagesOutputListImage, + i); + + switch (image->type) { + case QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI: + if (pri_id != -1) + mm_warn ("Multiple array elements found with PRI type"); + else + pri_id = (gint)i; + break; + case QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM: + if (modem_id != -1) + mm_warn ("Multiple array elements found with MODEM type"); + else + modem_id = (gint)i; + break; + default: + break; + } + } + + if (pri_id < 0 || modem_id < 0) { + mm_warn ("We need both PRI (%s) and MODEM (%s) images. " + "Assuming firmware unsupported.", + pri_id < 0 ? "not found" : "found", + modem_id < 0 ? "not found" : "found"); + g_simple_async_result_set_op_res_gboolean (ctx->result, FALSE); + firmware_check_support_context_complete_and_free (ctx); + qmi_message_dms_list_stored_images_output_unref (output); + return; + } + + /* Loop PRI images and try to find a pairing MODEM image with same boot ID */ + image_pri = &g_array_index (array, + QmiMessageDmsListStoredImagesOutputListImage, + pri_id); + image_modem = &g_array_index (array, + QmiMessageDmsListStoredImagesOutputListImage, + modem_id); + + for (i = 0; i < image_pri->sublist->len; i++) { + QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement *subimage_pri; + + subimage_pri = &g_array_index (image_pri->sublist, + QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement, + i); + for (j = 0; j < image_modem->sublist->len; j++) { + QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement *subimage_modem; + + subimage_modem = &g_array_index (image_modem->sublist, + QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement, + j); + + if (g_str_equal (subimage_pri->build_id, subimage_modem->build_id)) { + FirmwarePair *pair; + + mm_dbg ("Found pairing PRI+MODEM images with build ID '%s'", subimage_pri->build_id); + pair = g_slice_new (FirmwarePair); + pair->build_id = g_strdup (subimage_pri->build_id); + pair->modem_unique_id = g_array_ref (subimage_modem->unique_id); + pair->pri_unique_id = g_array_ref (subimage_pri->unique_id); + pair->current = (image_pri->index_of_running_image == i ? TRUE : FALSE); + ctx->pairs = g_list_append (ctx->pairs, pair); + break; + } + } + + if (j == image_modem->sublist->len) + mm_dbg ("Pairing for PRI image with build ID '%s' not found", subimage_pri->build_id); + } + + if (!ctx->pairs) { + mm_warn ("No valid PRI+MODEM pairs found. " + "Assuming firmware unsupported."); + g_simple_async_result_set_op_res_gboolean (ctx->result, FALSE); + firmware_check_support_context_complete_and_free (ctx); + qmi_message_dms_list_stored_images_output_unref (output); + return; + } + + /* Firmware is supported; now keep on loading info for each image and cache it */ + qmi_message_dms_list_stored_images_output_unref (output); + + ctx->l = ctx->pairs; + get_next_image_info (ctx); +} + +static void +firmware_check_support (MMIfaceModemFirmware *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + FirmwareCheckSupportContext *ctx; + QmiClient *client = NULL; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + ctx = g_slice_new0 (FirmwareCheckSupportContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + firmware_check_support); + + mm_dbg ("loading firmware images..."); + qmi_client_dms_list_stored_images (QMI_CLIENT_DMS (client), + NULL, + 10, + NULL, + (GAsyncReadyCallback)list_stored_images_ready, + ctx); +} + +/*****************************************************************************/ +/* Load firmware list (Firmware interface) */ + +static GList * +firmware_load_list_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error) +{ + return (GList *)g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); +} + +static void +firmware_load_list (MMIfaceModemFirmware *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + GSimpleAsyncResult *result; + GList *dup; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + firmware_load_list); + + /* We'll return the new list of new references we create here */ + dup = g_list_copy (self->priv->firmware_list); + g_list_foreach (dup, (GFunc)g_object_ref, NULL); + + g_simple_async_result_set_op_res_gpointer (result, dup, NULL); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Load current firmware (Firmware interface) */ + +static MMFirmwareProperties * +firmware_load_current_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error) +{ + return (MMFirmwareProperties *)g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); +} + +static void +firmware_load_current (MMIfaceModemFirmware *_self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self); + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + firmware_load_current); + + /* We'll return the reference we create here */ + g_simple_async_result_set_op_res_gpointer ( + result, + self->priv->current_firmware ? g_object_ref (self->priv->current_firmware) : NULL, + NULL); + g_simple_async_result_complete_in_idle (result); + g_object_unref (result); +} + +/*****************************************************************************/ +/* Change current firmware (Firmware interface) */ + +typedef struct { + MMBroadbandModemQmi *self; + QmiClientDms *client; + GSimpleAsyncResult *result; + MMFirmwareProperties *firmware; +} FirmwareChangeCurrentContext; + +static void +firmware_change_current_context_complete_and_free (FirmwareChangeCurrentContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_object_unref (ctx->client); + if (ctx->firmware) + g_object_unref (ctx->firmware); + g_slice_free (FirmwareChangeCurrentContext, ctx); +} + +static gboolean +firmware_change_current_finish (MMIfaceModemFirmware *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +firmware_power_cycle_ready (MMBroadbandModemQmi *self, + GAsyncResult *res, + FirmwareChangeCurrentContext *ctx) +{ + GError *error = NULL; + + if (!power_cycle_finish (self, res, &error)) + g_simple_async_result_take_error (ctx->result, error); + else + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + firmware_change_current_context_complete_and_free (ctx); +} + +static void +firmware_select_stored_image_ready (QmiClientDms *client, + GAsyncResult *res, + FirmwareChangeCurrentContext *ctx) +{ + QmiMessageDmsSetFirmwarePreferenceOutput *output; + GError *error = NULL; + + output = qmi_client_dms_set_firmware_preference_finish (client, res, &error); + if (!output) { + g_simple_async_result_take_error (ctx->result, error); + firmware_change_current_context_complete_and_free (ctx); + return; + } + + if (!qmi_message_dms_set_firmware_preference_output_get_result (output, &error)) { + g_simple_async_result_take_error (ctx->result, error); + firmware_change_current_context_complete_and_free (ctx); + qmi_message_dms_set_firmware_preference_output_unref (output); + return; + } + + qmi_message_dms_set_firmware_preference_output_unref (output); + + /* Now, go into offline mode */ + power_cycle (ctx->self, + (GAsyncReadyCallback)firmware_power_cycle_ready, + ctx); +} + +static MMFirmwareProperties * +find_firmware_properties_by_unique_id (MMBroadbandModemQmi *self, + const gchar *unique_id) +{ + GList *l; + + for (l = self->priv->firmware_list; l; l = g_list_next (l)) { + if (g_str_equal (mm_firmware_properties_get_unique_id (MM_FIRMWARE_PROPERTIES (l->data)), + unique_id)) + return g_object_ref (l->data); + } + + return NULL; +} + +static MMFirmwareProperties * +find_firmware_properties_by_gobi_pri_info_substring (MMBroadbandModemQmi *self, + const gchar *str, + guint *n_found) +{ + MMFirmwareProperties *first = NULL; + GList *l; + + *n_found = 0; + + for (l = self->priv->firmware_list; l; l = g_list_next (l)) { + const gchar *pri_info; + + pri_info = mm_firmware_properties_get_gobi_pri_info (MM_FIRMWARE_PROPERTIES (l->data)); + if (pri_info && strstr (pri_info, str)) { + if (!first && *n_found == 0) + first = g_object_ref (l->data); + else + g_clear_object (&first); + (*n_found)++; + } + } + + return first; +} + +static void +firmware_change_current (MMIfaceModemFirmware *self, + const gchar *unique_id, + GAsyncReadyCallback callback, + gpointer user_data) +{ + QmiMessageDmsSetFirmwarePreferenceInput *input; + FirmwareChangeCurrentContext *ctx; + QmiClient *client = NULL; + GArray *array; + QmiMessageDmsSetFirmwarePreferenceInputListImage modem_image_id; + QmiMessageDmsSetFirmwarePreferenceInputListImage pri_image_id; + guint8 *tmp; + gsize tmp_len; + + if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self), + QMI_SERVICE_DMS, &client, + callback, user_data)) + return; + + ctx = g_slice_new0 (FirmwareChangeCurrentContext); + ctx->self = g_object_ref (self); + ctx->client = g_object_ref (client); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + firmware_change_current); + + /* Look for the firmware image with the requested unique ID */ + ctx->firmware = find_firmware_properties_by_unique_id (ctx->self, unique_id); + if (!ctx->firmware) { + guint n = 0; + + /* Ok, let's look at the PRI info */ + ctx->firmware = find_firmware_properties_by_gobi_pri_info_substring (ctx->self, unique_id, &n); + if (n > 1) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Multiple firmware images (%u) found matching '%s' as PRI info substring", + n, unique_id); + firmware_change_current_context_complete_and_free (ctx); + return; + } + + if (n == 0) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "Firmware with unique ID '%s' wasn't found", + unique_id); + firmware_change_current_context_complete_and_free (ctx); + return; + } + + g_assert (n == 1 && MM_IS_FIRMWARE_PROPERTIES (ctx->firmware)); + } + + /* If we're already in the requested firmware, we're done */ + if (ctx->self->priv->current_firmware && + g_str_equal (mm_firmware_properties_get_unique_id (ctx->self->priv->current_firmware), + mm_firmware_properties_get_unique_id (ctx->firmware))) { + mm_dbg ("Modem is already running firmware image '%s'", + mm_firmware_properties_get_unique_id (ctx->self->priv->current_firmware)); + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + firmware_change_current_context_complete_and_free (ctx); + return; + } + + /* Modem image ID */ + tmp_len = 0; + tmp = (guint8 *)mm_utils_hexstr2bin (mm_firmware_properties_get_gobi_modem_unique_id (ctx->firmware), &tmp_len); + modem_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM; + modem_image_id.build_id = (gchar *)mm_firmware_properties_get_unique_id (ctx->firmware); + modem_image_id.unique_id = g_array_sized_new (FALSE, FALSE, sizeof (guint8), tmp_len); + g_array_insert_vals (modem_image_id.unique_id, 0, tmp, tmp_len); + g_free (tmp); + + /* PRI image ID */ + tmp_len = 0; + tmp = (guint8 *)mm_utils_hexstr2bin (mm_firmware_properties_get_gobi_pri_unique_id (ctx->firmware), &tmp_len); + pri_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI; + pri_image_id.build_id = (gchar *)mm_firmware_properties_get_unique_id (ctx->firmware); + pri_image_id.unique_id = g_array_sized_new (FALSE, FALSE, sizeof (guint8), tmp_len); + g_array_insert_vals (pri_image_id.unique_id, 0, tmp, tmp_len); + g_free (tmp); + + mm_dbg ("Changing Gobi firmware to MODEM '%s' and PRI '%s' with Build ID '%s'...", + mm_firmware_properties_get_gobi_modem_unique_id (ctx->firmware), + mm_firmware_properties_get_gobi_pri_unique_id (ctx->firmware), + unique_id); + + /* Build array of image IDs */ + array = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageDmsSetFirmwarePreferenceInputListImage), 2); + g_array_append_val (array, modem_image_id); + g_array_append_val (array, pri_image_id); + + input = qmi_message_dms_set_firmware_preference_input_new (); + qmi_message_dms_set_firmware_preference_input_set_list (input, array, NULL); + qmi_client_dms_set_firmware_preference ( + ctx->client, + input, + 10, + NULL, + (GAsyncReadyCallback)firmware_select_stored_image_ready, + ctx); + g_array_unref (modem_image_id.unique_id); + g_array_unref (pri_image_id.unique_id); + qmi_message_dms_set_firmware_preference_input_unref (input); +} + +/*****************************************************************************/ +/* First enabling step */ + +static gboolean +enabling_started_finish (MMBroadbandModem *self, + GAsyncResult *res, + GError **error) +{ + return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); +} + +static void +parent_enabling_started_ready (MMBroadbandModem *self, + GAsyncResult *res, + GSimpleAsyncResult *simple) +{ + GError *error = NULL; + + if (!MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_qmi_parent_class)->enabling_started_finish ( + self, + res, + &error)) { + /* Don't treat this as fatal. Parent enabling may fail if it cannot grab a primary + * AT port, which isn't really an issue in QMI-based modems */ + mm_dbg ("Couldn't start parent enabling: %s", error->message); + g_error_free (error); + } + + g_simple_async_result_set_op_res_gboolean (simple, TRUE); + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +static void +enabling_started (MMBroadbandModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *result; + + result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + enabling_started); + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_qmi_parent_class)->enabling_started ( + self, + (GAsyncReadyCallback)parent_enabling_started_ready, + result); +} + +/*****************************************************************************/ +/* First initialization step */ + +typedef struct { + MMBroadbandModem *self; + GSimpleAsyncResult *result; + MMQmiPort *qmi; + QmiService services[32]; + guint service_index; +} InitializationStartedContext; + +static void +initialization_started_context_complete_and_free (InitializationStartedContext *ctx) +{ + g_simple_async_result_complete_in_idle (ctx->result); + if (ctx->qmi) + g_object_unref (ctx->qmi); + g_object_unref (ctx->result); + g_object_unref (ctx->self); + g_free (ctx); +} + +static gpointer +initialization_started_finish (MMBroadbandModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + /* Just parent's pointer passed here */ + return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); +} + +static void +parent_initialization_started_ready (MMBroadbandModem *self, + GAsyncResult *res, + InitializationStartedContext *ctx) +{ + gpointer parent_ctx; + GError *error = NULL; + + parent_ctx = MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_qmi_parent_class)->initialization_started_finish ( + self, + res, + &error); + if (error) { + /* Don't treat this as fatal. Parent initialization may fail if it cannot grab a primary + * AT port, which isn't really an issue in QMI-based modems */ + mm_dbg ("Couldn't start parent initialization: %s", error->message); + g_error_free (error); + } + + g_simple_async_result_set_op_res_gpointer (ctx->result, parent_ctx, NULL); + initialization_started_context_complete_and_free (ctx); +} + +static void +parent_initialization_started (InitializationStartedContext *ctx) +{ + MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_qmi_parent_class)->initialization_started ( + ctx->self, + (GAsyncReadyCallback)parent_initialization_started_ready, + ctx); +} + +static void allocate_next_client (InitializationStartedContext *ctx); + +static void +qmi_port_allocate_client_ready (MMQmiPort *qmi, + GAsyncResult *res, + InitializationStartedContext *ctx) +{ + GError *error = NULL; + + if (!mm_qmi_port_allocate_client_finish (qmi, res, &error)) { + mm_dbg ("Couldn't allocate client for service '%s': %s", + qmi_service_get_string (ctx->services[ctx->service_index]), + error->message); + g_error_free (error); + } + + ctx->service_index++; + allocate_next_client (ctx); +} + +static void +allocate_next_client (InitializationStartedContext *ctx) +{ + if (ctx->services[ctx->service_index] == QMI_SERVICE_UNKNOWN) { + /* Done we are, launch parent's callback */ + parent_initialization_started (ctx); + return; + } + + /* Otherwise, allocate next client */ + mm_qmi_port_allocate_client (ctx->qmi, + ctx->services[ctx->service_index], + MM_QMI_PORT_FLAG_DEFAULT, + NULL, + (GAsyncReadyCallback)qmi_port_allocate_client_ready, + ctx); +} + + +static void +qmi_port_open_ready_no_data_format (MMQmiPort *qmi, + GAsyncResult *res, + InitializationStartedContext *ctx) +{ + GError *error = NULL; + + if (!mm_qmi_port_open_finish (qmi, res, &error)) { + g_simple_async_result_take_error (ctx->result, error); + initialization_started_context_complete_and_free (ctx); + return; + } + + allocate_next_client (ctx); +} + +static void +qmi_port_open_ready (MMQmiPort *qmi, + GAsyncResult *res, + InitializationStartedContext *ctx) +{ + GError *error = NULL; + + if (!mm_qmi_port_open_finish (qmi, res, &error)) { + /* Really, really old devices (Gobi 1K, 2008-era firmware) may not + * support SetDataFormat, so if we get an error opening the port + * try without it. The qmi_wwan driver will fix up any issues that + * the device might have between raw-ip and 802.3 mode anyway. + */ + mm_qmi_port_open (ctx->qmi, + FALSE, + NULL, + (GAsyncReadyCallback)qmi_port_open_ready_no_data_format, + ctx); + return; + } + + allocate_next_client (ctx); +} + +static void +initialization_started (MMBroadbandModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + InitializationStartedContext *ctx; + + ctx = g_new0 (InitializationStartedContext, 1); + ctx->self = g_object_ref (self); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + initialization_started); + ctx->qmi = mm_base_modem_get_port_qmi (MM_BASE_MODEM (self)); + + /* This may happen if we unplug the modem unexpectedly */ + if (!ctx->qmi) { + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot initialize: QMI port went missing"); + initialization_started_context_complete_and_free (ctx); + return; + } + + if (mm_qmi_port_is_open (ctx->qmi)) { + /* Nothing to be done, just launch parent's callback */ + parent_initialization_started (ctx); + return; + } + + /* Setup services to open */ + ctx->services[0] = QMI_SERVICE_DMS; + ctx->services[1] = QMI_SERVICE_NAS; + ctx->services[2] = QMI_SERVICE_WMS; + ctx->services[3] = QMI_SERVICE_PDS; + ctx->services[4] = QMI_SERVICE_UNKNOWN; + + /* Now open our QMI port */ + mm_qmi_port_open (ctx->qmi, + TRUE, + NULL, + (GAsyncReadyCallback)qmi_port_open_ready, + ctx); +} + +/*****************************************************************************/ + +MMBroadbandModemQmi * +mm_broadband_modem_qmi_new (const gchar *device, + const gchar **drivers, + const gchar *plugin, + guint16 vendor_id, + guint16 product_id) +{ + return g_object_new (MM_TYPE_BROADBAND_MODEM_QMI, + MM_BASE_MODEM_DEVICE, device, + MM_BASE_MODEM_DRIVERS, drivers, + MM_BASE_MODEM_PLUGIN, plugin, + MM_BASE_MODEM_VENDOR_ID, vendor_id, + MM_BASE_MODEM_PRODUCT_ID, product_id, + NULL); +} + +static void +mm_broadband_modem_qmi_init (MMBroadbandModemQmi *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), + MM_TYPE_BROADBAND_MODEM_QMI, + MMBroadbandModemQmiPrivate); +} + +static void +finalize (GObject *object) +{ + MMQmiPort *qmi; + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (object); + + qmi = mm_base_modem_peek_port_qmi (MM_BASE_MODEM (self)); + /* If we did open the QMI port during initialization, close it now */ + if (qmi && + mm_qmi_port_is_open (qmi)) { + mm_qmi_port_close (qmi); + } + + g_free (self->priv->imei); + g_free (self->priv->meid); + g_free (self->priv->esn); + g_free (self->priv->current_operator_id); + g_free (self->priv->current_operator_description); + if (self->priv->supported_bands) + g_array_unref (self->priv->supported_bands); + if (self->priv->supported_radio_interfaces) + g_array_unref (self->priv->supported_radio_interfaces); + + G_OBJECT_CLASS (mm_broadband_modem_qmi_parent_class)->finalize (object); +} + +static void +dispose (GObject *object) +{ + MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (object); + + g_list_free_full (self->priv->firmware_list, (GDestroyNotify)g_object_unref); + self->priv->firmware_list = NULL; + + g_clear_object (&self->priv->current_firmware); + + G_OBJECT_CLASS (mm_broadband_modem_qmi_parent_class)->dispose (object); +} + +static void +iface_modem_init (MMIfaceModem *iface) +{ + /* Initialization steps */ + iface->load_current_capabilities = modem_load_current_capabilities; + iface->load_current_capabilities_finish = modem_load_current_capabilities_finish; + iface->load_supported_capabilities = modem_load_supported_capabilities; + iface->load_supported_capabilities_finish = modem_load_supported_capabilities_finish; + iface->set_current_capabilities = set_current_capabilities; + iface->set_current_capabilities_finish = set_current_capabilities_finish; + iface->load_manufacturer = modem_load_manufacturer; + iface->load_manufacturer_finish = modem_load_manufacturer_finish; + iface->load_model = modem_load_model; + iface->load_model_finish = modem_load_model_finish; + iface->load_revision = modem_load_revision; + iface->load_revision_finish = modem_load_revision_finish; + iface->load_equipment_identifier = modem_load_equipment_identifier; + iface->load_equipment_identifier_finish = modem_load_equipment_identifier_finish; + iface->load_device_identifier = modem_load_device_identifier; + iface->load_device_identifier_finish = modem_load_device_identifier_finish; + iface->load_own_numbers = modem_load_own_numbers; + iface->load_own_numbers_finish = modem_load_own_numbers_finish; + iface->load_unlock_required = modem_load_unlock_required; + iface->load_unlock_required_finish = modem_load_unlock_required_finish; + iface->load_unlock_retries = modem_load_unlock_retries; + iface->load_unlock_retries_finish = modem_load_unlock_retries_finish; + iface->load_supported_bands = modem_load_supported_bands; + iface->load_supported_bands_finish = modem_load_supported_bands_finish; + iface->load_supported_modes = modem_load_supported_modes; + iface->load_supported_modes_finish = modem_load_supported_modes_finish; + iface->load_power_state = load_power_state; + iface->load_power_state_finish = load_power_state_finish; + iface->load_supported_ip_families = modem_load_supported_ip_families; + iface->load_supported_ip_families_finish = modem_load_supported_ip_families_finish; + + /* Enabling/disabling */ + iface->modem_power_up = modem_power_up; + iface->modem_power_up_finish = modem_power_up_down_finish; + iface->modem_after_power_up = NULL; + iface->modem_after_power_up_finish = NULL; + iface->modem_power_down = modem_power_down; + iface->modem_power_down_finish = modem_power_up_down_finish; + iface->setup_flow_control = NULL; + iface->setup_flow_control_finish = NULL; + iface->load_supported_charsets = NULL; + iface->load_supported_charsets_finish = NULL; + iface->setup_charset = NULL; + iface->setup_charset_finish = NULL; + iface->load_current_modes = load_current_modes; + iface->load_current_modes_finish = load_current_modes_finish; + iface->set_current_modes = set_current_modes; + iface->set_current_modes_finish = set_current_modes_finish; + iface->load_signal_quality = load_signal_quality; + iface->load_signal_quality_finish = load_signal_quality_finish; + iface->load_current_bands = modem_load_current_bands; + iface->load_current_bands_finish = modem_load_current_bands_finish; + iface->set_current_bands = set_current_bands; + iface->set_current_bands_finish = set_current_bands_finish; + + /* Don't try to load access technologies, as we would be using parent's + * generic method (QCDM based). Access technologies are already reported via + * QMI when we load signal quality. */ + iface->load_access_technologies = NULL; + iface->load_access_technologies_finish = NULL; + + /* Create QMI-specific SIM */ + iface->create_sim = create_sim; + iface->create_sim_finish = create_sim_finish; + + /* Create QMI-specific bearer */ + iface->create_bearer = modem_create_bearer; + iface->create_bearer_finish = modem_create_bearer_finish; + + /* Other actions */ + iface->factory_reset = modem_factory_reset; + iface->factory_reset_finish = modem_factory_reset_finish; +} + +static void +iface_modem_3gpp_init (MMIfaceModem3gpp *iface) +{ + /* Initialization steps */ + iface->load_imei = modem_3gpp_load_imei; + iface->load_imei_finish = modem_3gpp_load_imei_finish; + iface->load_enabled_facility_locks = modem_3gpp_load_enabled_facility_locks; + iface->load_enabled_facility_locks_finish = modem_3gpp_load_enabled_facility_locks_finish; + + /* Enabling/Disabling steps */ + iface->setup_unsolicited_events = modem_3gpp_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_3gpp_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_3gpp_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_3gpp_enable_disable_unsolicited_events_finish; + iface->setup_unsolicited_registration_events = modem_3gpp_setup_unsolicited_registration_events; + iface->setup_unsolicited_registration_events_finish = modem_3gpp_setup_cleanup_unsolicited_registration_events_finish; + iface->cleanup_unsolicited_registration_events = modem_3gpp_cleanup_unsolicited_registration_events; + iface->cleanup_unsolicited_registration_events_finish = modem_3gpp_setup_cleanup_unsolicited_registration_events_finish; + iface->enable_unsolicited_registration_events = modem_3gpp_enable_unsolicited_registration_events; + iface->enable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish; + iface->disable_unsolicited_registration_events = modem_3gpp_disable_unsolicited_registration_events; + iface->disable_unsolicited_registration_events_finish = modem_3gpp_enable_disable_unsolicited_registration_events_finish; + + /* Other actions */ + iface->scan_networks = modem_3gpp_scan_networks; + iface->scan_networks_finish = modem_3gpp_scan_networks_finish; + iface->register_in_network = modem_3gpp_register_in_network; + iface->register_in_network_finish = modem_3gpp_register_in_network_finish; + iface->run_registration_checks = modem_3gpp_run_registration_checks; + iface->run_registration_checks_finish = modem_3gpp_run_registration_checks_finish; + iface->load_operator_code = modem_3gpp_load_operator_code; + iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish; + iface->load_operator_name = modem_3gpp_load_operator_name; + iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish; +} + +static void +iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface) +{ + /* Assume we don't have USSD support */ + iface->check_support = NULL; + iface->check_support_finish = NULL; +} + +static void +iface_modem_cdma_init (MMIfaceModemCdma *iface) +{ + iface->load_meid = modem_cdma_load_meid; + iface->load_meid_finish = modem_cdma_load_meid_finish; + iface->load_esn = modem_cdma_load_esn; + iface->load_esn_finish = modem_cdma_load_esn_finish; + + /* Enabling/Disabling steps */ + iface->setup_unsolicited_events = modem_cdma_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = modem_cdma_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = modem_cdma_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = modem_cdma_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = modem_cdma_enable_disable_unsolicited_events_finish; + iface->disable_unsolicited_events = modem_cdma_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = modem_cdma_enable_disable_unsolicited_events_finish; + + /* Other actions */ + iface->run_registration_checks = modem_cdma_run_registration_checks; + iface->run_registration_checks_finish = modem_cdma_run_registration_checks_finish; + iface->load_activation_state = modem_cdma_load_activation_state; + iface->load_activation_state_finish = modem_cdma_load_activation_state_finish; + iface->activate = modem_cdma_activate; + iface->activate_finish = modem_cdma_activate_finish; +} + +static void +iface_modem_messaging_init (MMIfaceModemMessaging *iface) +{ + iface->check_support = messaging_check_support; + iface->check_support_finish = messaging_check_support_finish; + iface->load_supported_storages = messaging_load_supported_storages; + iface->load_supported_storages_finish = messaging_load_supported_storages_finish; + iface->setup_sms_format = NULL; + iface->setup_sms_format_finish = NULL; + iface->set_default_storage = messaging_set_default_storage; + iface->set_default_storage_finish = messaging_set_default_storage_finish; + iface->load_initial_sms_parts = load_initial_sms_parts; + iface->load_initial_sms_parts_finish = load_initial_sms_parts_finish; + iface->setup_unsolicited_events = messaging_setup_unsolicited_events; + iface->setup_unsolicited_events_finish = messaging_setup_cleanup_unsolicited_events_finish; + iface->cleanup_unsolicited_events = messaging_cleanup_unsolicited_events; + iface->cleanup_unsolicited_events_finish = messaging_setup_cleanup_unsolicited_events_finish; + iface->enable_unsolicited_events = messaging_enable_unsolicited_events; + iface->enable_unsolicited_events_finish = messaging_enable_disable_unsolicited_events_finish; + iface->disable_unsolicited_events = messaging_disable_unsolicited_events; + iface->disable_unsolicited_events_finish = messaging_enable_disable_unsolicited_events_finish; + iface->create_sms = messaging_create_sms; +} + +static void +iface_modem_location_init (MMIfaceModemLocation *iface) +{ + iface_modem_location_parent = g_type_interface_peek_parent (iface); + + iface->load_capabilities = location_load_capabilities; + iface->load_capabilities_finish = location_load_capabilities_finish; + iface->enable_location_gathering = enable_location_gathering; + iface->enable_location_gathering_finish = enable_location_gathering_finish; + iface->disable_location_gathering = disable_location_gathering; + iface->disable_location_gathering_finish = disable_location_gathering_finish; +} + +static void +iface_modem_firmware_init (MMIfaceModemFirmware *iface) +{ + iface->check_support = firmware_check_support; + iface->check_support_finish = firmware_check_support_finish; + iface->load_list = firmware_load_list; + iface->load_list_finish = firmware_load_list_finish; + iface->load_current = firmware_load_current; + iface->load_current_finish = firmware_load_current_finish; + iface->change_current = firmware_change_current; + iface->change_current_finish = firmware_change_current_finish; +} + +static void +mm_broadband_modem_qmi_class_init (MMBroadbandModemQmiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBroadbandModemQmiPrivate)); + + object_class->finalize = finalize; + object_class->dispose = dispose; + + broadband_modem_class->initialization_started = initialization_started; + broadband_modem_class->initialization_started_finish = initialization_started_finish; + broadband_modem_class->enabling_started = enabling_started; + broadband_modem_class->enabling_started_finish = enabling_started_finish; + /* Do not initialize the QMI modem through AT commands */ + broadband_modem_class->enabling_modem_init = NULL; + broadband_modem_class->enabling_modem_init_finish = NULL; +} |