summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2018-07-15 22:54:18 +0200
committerDan Williams <dcbw@redhat.com>2018-09-12 17:25:19 +0000
commit692d076ba68e09bcad146d78109d4a59e1563918 (patch)
treeb736525a272432333658ad054c94857264f2b1b1
parent6026c99f2ec99e2721356ee645472d8403676bf6 (diff)
downloadModemManager-692d076ba68e09bcad146d78109d4a59e1563918.tar.gz
shared-qmi: implement reworked mode and capabilities management
This commit introduces several improvements and changes in the way modes and capabilities are managed in QMI capable devices. It is organized into a single commit, as all changes in all 6 operations (load current capabilities, load supported capabilities, set current capabilities, load supported modes, load current modes, set current modes) are related one to the other (given that the same QMI commands are used for both capabilities and mode management). The primary change is related to which capabilities are reported as supported for a given device. In the previous implementation we allowed switching between every combination possible for GSM/UMTS+LTE, CDMA/EVDO+LTE or GSM/UMTS+CDMA/EVDO+LTE devices. E.g. we would allow "LTE only" and "GSM/UMTS only" capabilities for GSM/UMTS+LTE devices, even if they would both be managed in exactly the same way. That setup wasn't ideal, because it meant that switching to a "LTE only" configuration would require a power cycle, as capability switching requires a power cycle, even if no change was expected in the exposed DBus interfaces (which is why we require the power cycle). So, instead of allowing every possible capability combination, we use capability switching logic exclusively for configuring GSM/UMTS+CDMA/EVDO devices (regardless of whether it has LTE or not) to add or remove the GSM/UMTS and CDMA/EVDO capabilities. E.g. for a GSM/UMTS+CDMA/EVDO+LTE device we would allow 3 combinatios: "GSM/UMTS+LTE", "CDMA/EVDO+LTE" and "GSM/UMTS+CDMA/EVDO+LTE". The "GSM/UMTS+CDMA/EVDO+LTE" is a special case because for this one we allow switching to "LTE only" capabilities while we forbid switching to "4G only" mode. As the same commands are used for mode and capability switching, if we didn't have "LTE only" and we allowed "4G only" mode instead and rebooted the device, we would end up not being able to know which other capabilities (GSM/UMTS or CDMA/EVDO or both) were also enabled. Now that we have capability switching confined to a very subset of combinations, we can use the mode switching logic to e.g. allow "4G only" configurations in all non multimode devices, as well as masks of allowed modes with one being preferred, which we didn't allow before. In the previous implementation all mode switching logic was disabled for LTE capable QMI devices. In the new implementation, we use the "Acquisition Order Preference" TLV in NAS Set System Selection Preference to define the full list of mode preferences for all supported modes. We also no longer just assume that NAS Technology Preference is always available and NAS System Selection Preference only after NAS >= 1.1. This logic is flawed, instead we're going to probe for those features once when loading current capabilities, and we then just implement the capabilities/mode switching logic based on that.
-rw-r--r--configure.ac2
-rw-r--r--src/mm-broadband-modem-qmi.c1232
-rw-r--r--src/mm-modem-helpers-qmi.c131
-rw-r--r--src/mm-modem-helpers-qmi.h8
-rw-r--r--src/mm-shared-qmi.c1267
-rw-r--r--src/mm-shared-qmi.h67
6 files changed, 1467 insertions, 1240 deletions
diff --git a/configure.ac b/configure.ac
index dd06b3af7..f6623eab6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -341,7 +341,7 @@ dnl-----------------------------------------------------------------------------
dnl QMI support (enabled by default)
dnl
-LIBQMI_VERSION=1.21.3
+LIBQMI_VERSION=1.21.4
AC_ARG_WITH(qmi, AS_HELP_STRING([--without-qmi], [Build without QMI support]), [], [with_qmi=yes])
AM_CONDITIONAL(WITH_QMI, test "x$with_qmi" = "xyes")
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index c729d2286..54082ae07 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -79,9 +79,6 @@ struct _MMBroadbandModemQmiPrivate {
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;
@@ -193,597 +190,6 @@ modem_create_bearer (MMIfaceModem *self,
}
/*****************************************************************************/
-/* Current Capabilities loading (Modem interface) */
-
-typedef struct {
- QmiClientNas *nas_client;
- QmiClientDms *dms_client;
- 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)
-{
- GError *inner_error = NULL;
- gssize value;
-
- value = g_task_propagate_int (G_TASK (res), &inner_error);
- if (inner_error) {
- g_propagate_error (error, inner_error);
- return MM_MODEM_CAPABILITY_NONE;
- }
- return (MMModemCapability)value;
-}
-
-static void
-load_current_capabilities_context_free (LoadCurrentCapabilitiesContext *ctx)
-{
- g_object_unref (ctx->nas_client);
- g_object_unref (ctx->dms_client);
- g_slice_free (LoadCurrentCapabilitiesContext, ctx);
-}
-
-static void load_current_capabilities_context_step (GTask *task);
-
-static void
-load_current_capabilities_get_capabilities_ready (QmiClientDms *client,
- GAsyncResult *res,
- GTask *task)
-{
- LoadCurrentCapabilitiesContext *ctx;
- QmiMessageDmsGetCapabilitiesOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
- output = qmi_client_dms_get_capabilities_finish (client, res, &error);
- if (!output) {
- g_prefix_error (&error, "QMI operation failed: ");
- g_task_return_error (task, error);
- } else if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) {
- g_prefix_error (&error, "Couldn't get Capabilities: ");
- g_task_return_error (task, 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));
- }
-
- g_task_return_int (task,
- mm_modem_capability_from_qmi_capabilities_context (&ctx->capabilities_context));
- }
-
- if (output)
- qmi_message_dms_get_capabilities_output_unref (output);
- g_object_unref (task);
-}
-
-static void
-load_current_capabilities_get_technology_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- LoadCurrentCapabilitiesContext *ctx;
- QmiMessageNasGetTechnologyPreferenceOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
- 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 (task);
-}
-
-static void
-load_current_capabilities_get_system_selection_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- LoadCurrentCapabilitiesContext *ctx;
- QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
- 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 (task);
-}
-
-static void
-load_current_capabilities_context_step (GTask *task)
-{
- LoadCurrentCapabilitiesContext *ctx;
-
- ctx = g_task_get_task_data (task);
- 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,
- task);
- 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,
- task);
- return;
- }
-
- qmi_client_dms_get_capabilities (
- ctx->dms_client,
- NULL, /* no input */
- 5,
- NULL, /* cancellable */
- (GAsyncReadyCallback)load_current_capabilities_get_capabilities_ready,
- task);
-}
-
-static void
-modem_load_current_capabilities (MMIfaceModem *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- LoadCurrentCapabilitiesContext *ctx;
- GTask *task;
- 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 (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
- QMI_SERVICE_NAS, &nas_client,
- callback, user_data))
- return;
-
- if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
- QMI_SERVICE_DMS, &dms_client,
- callback, user_data))
- return;
-
- ctx = g_slice_new0 (LoadCurrentCapabilitiesContext);
- ctx->nas_client = g_object_ref (nas_client);
- ctx->dms_client = g_object_ref (dms_client);
-
- /* 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;
-
- task = g_task_new (self, NULL, callback, user_data);
- g_task_set_task_data (task,
- ctx,
- (GDestroyNotify)load_current_capabilities_context_free);
-
- load_current_capabilities_context_step (task);
-}
-
-/*****************************************************************************/
-/* Supported capabilities loading (Modem interface) */
-
-static GArray *
-modem_load_supported_capabilities_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error)
-{
- return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-static void
-dms_get_capabilities_ready (QmiClientDms *client,
- GAsyncResult *res,
- GTask *task)
-{
- 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_task_return_error (task, error);
- } else if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) {
- g_prefix_error (&error, "Couldn't get supported capabilities: ");
- g_task_return_error (task, error);
- } else {
- MMBroadbandModemQmi *self;
- guint i;
- MMModemCapability mask = MM_MODEM_CAPABILITY_NONE;
- MMModemCapability single;
- GArray *radio_interface_list;
- GArray *supported_combinations;
- GArray *filtered_combinations;
-
- self = g_task_get_source_object (task);
-
- 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 (self->priv->supported_radio_interfaces)
- g_array_unref (self->priv->supported_radio_interfaces);
- 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_task_return_pointer (task,
- filtered_combinations,
- (GDestroyNotify) g_array_unref);
- }
-
- if (output)
- qmi_message_dms_get_capabilities_output_unref (output);
-
- g_object_unref (task);
-}
-
-static void
-modem_load_supported_capabilities (MMIfaceModem *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- QmiClient *client = NULL;
-
- if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
- QMI_SERVICE_DMS, &client,
- callback, user_data))
- return;
-
- mm_dbg ("loading supported capabilities...");
- qmi_client_dms_get_capabilities (QMI_CLIENT_DMS (client),
- NULL,
- 5,
- NULL,
- (GAsyncReadyCallback)dms_get_capabilities_ready,
- g_task_new (self, NULL, callback, user_data));
-}
-
-/*****************************************************************************/
-/* Current capabilities setting (Modem interface) */
-
-typedef struct {
- QmiClientNas *client;
- MMModemCapability capabilities;
- gboolean run_set_system_selection_preference;
- gboolean run_set_technology_preference;
-} SetCurrentCapabilitiesContext;
-
-static void
-set_current_capabilities_context_free (SetCurrentCapabilitiesContext *ctx)
-{
- g_object_unref (ctx->client);
- g_slice_free (SetCurrentCapabilitiesContext, ctx);
-}
-
-static gboolean
-set_current_capabilities_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error)
-{
- return g_task_propagate_boolean (G_TASK (res), error);
-}
-
-static void
-capabilities_reset_ready (MMIfaceModem *self,
- GAsyncResult *res,
- GTask *task)
-{
- GError *error = NULL;
-
- if (!mm_shared_qmi_reset_finish (self, res, &error))
- g_task_return_error (task, error);
- else
- g_task_return_boolean (task, TRUE);
-
- g_object_unref (task);
-}
-
-static void
-capabilities_reset (GTask *task)
-{
- MMBroadbandModemQmi *self;
-
- self = g_task_get_source_object (task);
-
- /* Power cycle the modem */
- mm_shared_qmi_reset (MM_IFACE_MODEM (self),
- (GAsyncReadyCallback)capabilities_reset_ready,
- task);
-}
-
-static void set_current_capabilities_context_step (GTask *task);
-
-static void
-capabilities_set_technology_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- SetCurrentCapabilitiesContext *ctx;
- QmiMessageNasSetTechnologyPreferenceOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
- output = qmi_client_nas_set_technology_preference_finish (client, res, &error);
- if (!output) {
- 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_reset (task);
- qmi_message_nas_set_technology_preference_output_unref (output);
- return;
- }
-
- ctx->run_set_technology_preference = FALSE;
- set_current_capabilities_context_step (task);
-}
-
-static void
-capabilities_set_system_selection_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- SetCurrentCapabilitiesContext *ctx;
- QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
- output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
- if (!output) {
- 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_reset (task);
- 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 (task);
-}
-
-static void
-set_current_capabilities_context_step (GTask *task)
-{
- SetCurrentCapabilitiesContext *ctx;
-
- ctx = g_task_get_task_data (task);
- 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_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
- "Unhandled capabilities setting: '%s'",
- str);
- g_object_unref (task);
- g_free (str);
- return;
- }
-
- input = qmi_message_nas_set_system_selection_preference_input_new ();
- qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL);
- qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL);
-
- qmi_client_nas_set_system_selection_preference (
- ctx->client,
- input,
- 5,
- NULL, /* cancellable */
- (GAsyncReadyCallback)capabilities_set_system_selection_preference_ready,
- task);
- 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_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
- "Unhandled capabilities setting: '%s'",
- str);
- g_object_unref (task);
- g_free (str);
- return;
- }
-
- input = qmi_message_nas_set_technology_preference_input_new ();
- qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL);
-
- qmi_client_nas_set_technology_preference (
- ctx->client,
- input,
- 5,
- NULL, /* cancellable */
- (GAsyncReadyCallback)capabilities_set_technology_preference_ready,
- task);
- qmi_message_nas_set_technology_preference_input_unref (input);
- return;
- }
-
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_UNSUPPORTED,
- "Setting capabilities is not supported by this device");
- g_object_unref (task);
-}
-
-static void
-set_current_capabilities (MMIfaceModem *self,
- MMModemCapability capabilities,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- SetCurrentCapabilitiesContext *ctx;
- GTask *task;
- QmiClient *client = NULL;
-
- if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
- QMI_SERVICE_NAS, &client,
- callback, user_data))
- return;
-
- ctx = g_slice_new0 (SetCurrentCapabilitiesContext);
- ctx->client = g_object_ref (client);
- 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;
-
- task = g_task_new (self, NULL, callback, user_data);
- g_task_set_task_data (task,
- ctx,
- (GDestroyNotify)set_current_capabilities_context_free);
-
- set_current_capabilities_context_step (task);
-}
-
-/*****************************************************************************/
/* Manufacturer loading (Modem interface) */
static gchar *
@@ -2235,139 +1641,6 @@ set_current_bands (MMIfaceModem *_self,
}
/*****************************************************************************/
-/* Load supported modes (Modem interface) */
-
-static GArray *
-modem_load_supported_modes_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error)
-{
- return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-static void
-modem_load_supported_modes (MMIfaceModem *_self,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- MMBroadbandModemQmi *self = MM_BROADBAND_MODEM_QMI (_self);
- GTask *task;
- GArray *combinations;
- MMModemModeCombination mode;
-
- task = g_task_new (self, NULL, callback, user_data);
-
- if (!self->priv->supported_radio_interfaces) {
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
- "Cannot load supported modes, no radio interface list");
- g_object_unref (task);
- 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_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
- g_object_unref (task);
-}
-
-/*****************************************************************************/
/* Load supported IP families (Modem interface) */
static MMBearerIpFamily
@@ -3088,485 +2361,6 @@ create_sim (MMIfaceModem *self,
}
/*****************************************************************************/
-/* Load current modes (Modem interface) */
-
-typedef struct {
- QmiClientNas *client;
- 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_free (LoadCurrentModesContext *ctx)
-{
- g_object_unref (ctx->client);
- g_free (ctx);
-}
-
-static gboolean
-load_current_modes_finish (MMIfaceModem *self,
- GAsyncResult *res,
- MMModemMode *allowed,
- MMModemMode *preferred,
- GError **error)
-{
- LoadCurrentModesResult *result;
-
- result = g_task_propagate_pointer (G_TASK (res), error);
- if (!result)
- return FALSE;
-
- *allowed = result->allowed;
- *preferred = result->preferred;
- g_free (result);
- return TRUE;
-}
-
-static void load_current_modes_context_step (GTask *task);
-
-static void
-get_technology_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- LoadCurrentModesContext *ctx;
- LoadCurrentModesResult *result = NULL;
- QmiMessageNasGetTechnologyPreferenceOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
-
- 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 (task);
- return;
- }
-
- g_task_return_pointer (task, result, g_free);
- g_object_unref (task);
-}
-
-static void
-current_modes_get_system_selection_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- LoadCurrentModesContext *ctx;
- LoadCurrentModesResult *result = NULL;
- QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL;
- GError *error = NULL;
- QmiNasRatModePreference mode_preference_mask = 0;
-
- ctx = g_task_get_task_data (task);
-
- 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 (task);
- return;
- }
-
- g_task_return_pointer (task, result, g_free);
- g_object_unref (task);
-}
-
-static void
-load_current_modes_context_step (GTask *task)
-{
- LoadCurrentModesContext *ctx;
-
- ctx = g_task_get_task_data (task);
-
- 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,
- task);
- 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,
- task);
- return;
- }
-
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_UNSUPPORTED,
- "Loading current modes is not supported by this device");
- g_object_unref (task);
-}
-
-static void
-load_current_modes (MMIfaceModem *self,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- LoadCurrentModesContext *ctx;
- GTask *task;
- QmiClient *client = NULL;
-
- if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
- QMI_SERVICE_NAS, &client,
- callback, user_data))
- return;
-
- ctx = g_new0 (LoadCurrentModesContext, 1);
- ctx->client = g_object_ref (client);
-
- /* 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;
-
- task = g_task_new (self, NULL, callback, user_data);
- g_task_set_task_data (task,
- ctx,
- (GDestroyNotify)load_current_modes_context_free);
-
- load_current_modes_context_step (task);
-}
-
-/*****************************************************************************/
-/* Set allowed modes (Modem interface) */
-
-typedef struct {
- QmiClientNas *client;
- MMModemMode allowed;
- MMModemMode preferred;
- gboolean run_set_system_selection_preference;
- gboolean run_set_technology_preference;
-} SetCurrentModesContext;
-
-static void
-set_current_modes_context_free (SetCurrentModesContext *ctx)
-{
- g_object_unref (ctx->client);
- g_slice_free (SetCurrentModesContext, ctx);
-}
-
-static gboolean
-set_current_modes_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error)
-{
- return g_task_propagate_boolean (G_TASK (res), error);
-}
-
-static void set_current_modes_context_step (GTask *task);
-
-static void
-set_technology_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- SetCurrentModesContext *ctx;
- QmiMessageNasSetTechnologyPreferenceOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
-
- output = qmi_client_nas_set_technology_preference_finish (client, res, &error);
- if (!output) {
- 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_task_return_boolean (task, TRUE);
- g_object_unref (task);
- qmi_message_nas_set_technology_preference_output_unref (output);
- return;
- }
-
- ctx->run_set_technology_preference = FALSE;
- set_current_modes_context_step (task);
-}
-
-static void
-allowed_modes_set_system_selection_preference_ready (QmiClientNas *client,
- GAsyncResult *res,
- GTask *task)
-{
- SetCurrentModesContext *ctx;
- QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL;
- GError *error = NULL;
-
- ctx = g_task_get_task_data (task);
-
- output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
- if (!output) {
- 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_task_return_boolean (task, TRUE);
- g_object_unref (task);
- 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 (task);
-}
-
-static void
-set_current_modes_context_step (GTask *task)
-{
- MMIfaceModem *self;
- SetCurrentModesContext *ctx;
-
- self = g_task_get_source_object (task);
- ctx = g_task_get_task_data (task);
-
- 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 (self),
- mm_iface_modem_is_3gpp (self));
- if (!pref) {
- gchar *str;
-
- str = mm_modem_mode_build_string_from_mask (ctx->allowed);
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
- "Unhandled allowed mode setting: '%s'",
- str);
- g_object_unref (task);
- g_free (str);
- return;
- }
-
- input = qmi_message_nas_set_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 (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,
- task);
- 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_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
- "Cannot set specific preferred mode");
- g_object_unref (task);
- return;
- }
-
- pref = mm_modem_mode_to_qmi_radio_technology_preference (ctx->allowed,
- mm_iface_modem_is_cdma (self));
- if (!pref) {
- gchar *str;
-
- str = mm_modem_mode_build_string_from_mask (ctx->allowed);
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_FAILED,
- "Unhandled allowed mode setting: '%s'",
- str);
- g_object_unref (task);
- g_free (str);
- return;
- }
-
- input = qmi_message_nas_set_technology_preference_input_new ();
- qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL);
-
- qmi_client_nas_set_technology_preference (
- ctx->client,
- input,
- 5,
- NULL, /* cancellable */
- (GAsyncReadyCallback)set_technology_preference_ready,
- task);
- qmi_message_nas_set_technology_preference_input_unref (input);
- return;
- }
-
- g_task_return_new_error (task,
- MM_CORE_ERROR,
- MM_CORE_ERROR_UNSUPPORTED,
- "Setting allowed modes is not supported by this device");
- g_object_unref (task);
-}
-
-static void
-set_current_modes (MMIfaceModem *self,
- MMModemMode allowed,
- MMModemMode preferred,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- SetCurrentModesContext *ctx;
- GTask *task;
- QmiClient *client = NULL;
-
- if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
- QMI_SERVICE_NAS, &client,
- callback, user_data))
- return;
-
- ctx = g_slice_new0 (SetCurrentModesContext);
- ctx->client = g_object_ref (client);
-
- if (allowed == MM_MODEM_MODE_ANY && ctx->preferred == MM_MODEM_MODE_NONE) {
- ctx->allowed = MM_MODEM_MODE_NONE;
- if (mm_iface_modem_is_2g (self))
- ctx->allowed |= MM_MODEM_MODE_2G;
- if (mm_iface_modem_is_3g (self))
- ctx->allowed |= MM_MODEM_MODE_3G;
- if (mm_iface_modem_is_4g (self))
- ctx->allowed |= MM_MODEM_MODE_4G;
- ctx->preferred = MM_MODEM_MODE_NONE;
- } else {
- ctx->allowed = allowed;
- ctx->preferred = preferred;
- }
-
- /* 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;
-
- task = g_task_new (self, NULL, callback, user_data);
- g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_modes_context_free);
-
- set_current_modes_context_step (task);
-}
-
-/*****************************************************************************/
/* IMEI loading (3GPP interface) */
static gchar *
@@ -10148,8 +8942,6 @@ finalize (GObject *object)
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);
}
@@ -10171,12 +8963,12 @@ 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_current_capabilities = mm_shared_qmi_load_current_capabilities;
+ iface->load_current_capabilities_finish = mm_shared_qmi_load_current_capabilities_finish;
+ iface->load_supported_capabilities = mm_shared_qmi_load_supported_capabilities;
+ iface->load_supported_capabilities_finish = mm_shared_qmi_load_supported_capabilities_finish;
+ iface->set_current_capabilities = mm_shared_qmi_set_current_capabilities;
+ iface->set_current_capabilities_finish = mm_shared_qmi_set_current_capabilities_finish;
iface->load_manufacturer = modem_load_manufacturer;
iface->load_manufacturer_finish = modem_load_manufacturer_finish;
iface->load_model = modem_load_model;
@@ -10197,8 +8989,8 @@ iface_modem_init (MMIfaceModem *iface)
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_supported_modes = mm_shared_qmi_load_supported_modes;
+ iface->load_supported_modes_finish = mm_shared_qmi_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;
@@ -10219,10 +9011,10 @@ iface_modem_init (MMIfaceModem *iface)
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_current_modes = mm_shared_qmi_load_current_modes;
+ iface->load_current_modes_finish = mm_shared_qmi_load_current_modes_finish;
+ iface->set_current_modes = mm_shared_qmi_set_current_modes;
+ iface->set_current_modes_finish = mm_shared_qmi_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;
diff --git a/src/mm-modem-helpers-qmi.c b/src/mm-modem-helpers-qmi.c
index ccc2a9122..5df344786 100644
--- a/src/mm-modem-helpers-qmi.c
+++ b/src/mm-modem-helpers-qmi.c
@@ -754,6 +754,25 @@ mm_modem_access_technologies_from_qmi_data_capability_array (GArray *data_capabi
/*****************************************************************************/
MMModemMode
+mm_modem_mode_from_qmi_nas_radio_interface (QmiNasRadioInterface iface)
+{
+ switch (iface) {
+ case QMI_NAS_RADIO_INTERFACE_CDMA_1X:
+ case QMI_NAS_RADIO_INTERFACE_GSM:
+ return MM_MODEM_MODE_2G;
+ case QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO:
+ case QMI_NAS_RADIO_INTERFACE_UMTS:
+ return MM_MODEM_MODE_3G;
+ case QMI_NAS_RADIO_INTERFACE_LTE:
+ return MM_MODEM_MODE_4G;
+ default:
+ return MM_MODEM_MODE_NONE;
+ }
+}
+
+/*****************************************************************************/
+
+MMModemMode
mm_modem_mode_from_qmi_radio_technology_preference (QmiNasRadioTechnologyPreference qmi)
{
MMModemMode mode = MM_MODEM_MODE_NONE;
@@ -909,6 +928,94 @@ mm_modem_capability_to_qmi_rat_mode_preference (MMModemCapability caps)
/*****************************************************************************/
+GArray *
+mm_modem_capability_to_qmi_acquisition_order_preference (MMModemCapability caps)
+{
+ GArray *array;
+ QmiNasRadioInterface value;
+
+ array = g_array_new (FALSE, FALSE, sizeof (QmiNasRadioInterface));
+
+ if (caps & MM_MODEM_CAPABILITY_LTE) {
+ value = QMI_NAS_RADIO_INTERFACE_LTE;
+ g_array_append_val (array, value);
+ }
+
+ if (caps & MM_MODEM_CAPABILITY_GSM_UMTS) {
+ value = QMI_NAS_RADIO_INTERFACE_UMTS;
+ g_array_append_val (array, value);
+ value = QMI_NAS_RADIO_INTERFACE_GSM;
+ g_array_append_val (array, value);
+ }
+
+ if (caps & MM_MODEM_CAPABILITY_CDMA_EVDO) {
+ value = QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO;
+ g_array_append_val (array, value);
+ value = QMI_NAS_RADIO_INTERFACE_CDMA_1X;
+ g_array_append_val (array, value);
+ }
+
+ return array;
+}
+
+GArray *
+mm_modem_mode_to_qmi_acquisition_order_preference (MMModemMode allowed,
+ MMModemMode preferred,
+ gboolean is_cdma,
+ gboolean is_3gpp)
+{
+ GArray *array;
+ QmiNasRadioInterface value;
+
+ array = g_array_new (FALSE, FALSE, sizeof (QmiNasRadioInterface));
+
+ if (allowed & MM_MODEM_MODE_4G) {
+ value = QMI_NAS_RADIO_INTERFACE_LTE;
+ if (preferred == MM_MODEM_MODE_4G)
+ g_array_prepend_val (array, value);
+ else
+ g_array_append_val (array, value);
+ }
+
+ if (allowed & MM_MODEM_MODE_3G) {
+ if (is_cdma) {
+ value = QMI_NAS_RADIO_INTERFACE_CDMA_1XEVDO;
+ if (preferred == MM_MODEM_MODE_3G)
+ g_array_prepend_val (array, value);
+ else
+ g_array_append_val (array, value);
+ }
+ if (is_3gpp) {
+ value = QMI_NAS_RADIO_INTERFACE_UMTS;
+ if (preferred == MM_MODEM_MODE_3G)
+ g_array_prepend_val (array, value);
+ else
+ g_array_append_val (array, value);
+ }
+ }
+
+ if (allowed & MM_MODEM_MODE_2G) {
+ if (is_cdma) {
+ value = QMI_NAS_RADIO_INTERFACE_CDMA_1X;
+ if (preferred == MM_MODEM_MODE_2G)
+ g_array_prepend_val (array, value);
+ else
+ g_array_append_val (array, value);
+ }
+ if (is_3gpp) {
+ value = QMI_NAS_RADIO_INTERFACE_GSM;
+ if (preferred == MM_MODEM_MODE_2G)
+ g_array_prepend_val (array, value);
+ else
+ g_array_append_val (array, value);
+ }
+ }
+
+ return array;
+}
+
+/*****************************************************************************/
+
MMModemCapability
mm_modem_capability_from_qmi_radio_technology_preference (QmiNasRadioTechnologyPreference qmi)
{
@@ -1200,6 +1307,15 @@ mm_bearer_allowed_auth_to_qmi_authentication (MMBearerAllowedAuth auth)
/*****************************************************************************/
+/**
+ * The only case where we need to apply some logic to decide what the current
+ * capabilities are is when we have a multimode CDMA/EVDO+GSM/UMTS device, in
+ * which case we'll check the SSP and TP current values to decide which
+ * capabilities are present and which have been disabled.
+ *
+ * For all the other cases, the DMS capabilities are exactly the current ones,
+ * as there would be no capability switching support.
+ */
MMModemCapability
mm_modem_capability_from_qmi_capabilities_context (MMQmiCapabilitiesContext *ctx)
{
@@ -1209,16 +1325,19 @@ mm_modem_capability_from_qmi_capabilities_context (MMQmiCapabilitiesContext *ctx
gchar *dms_capabilities_str;
gchar *tmp_str;
- /* SSP logic to gather capabilities uses the Mode Preference TLV if available,
- * and if not available it falls back to using Band Preference TLVs */
+ /* If not a multimode device, we're done */
+#define MULTIMODE (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)
+ if ((ctx->dms_capabilities & MULTIMODE) != MULTIMODE)
+ return ctx->dms_capabilities;
+
+ /* We have a multimode CDMA/EVDO+GSM/UMTS device, check SSP and TP */
+
+ /* SSP logic to gather capabilities uses the Mode Preference TLV if available */
if (ctx->nas_ssp_mode_preference_mask)
tmp = mm_modem_capability_from_qmi_rat_mode_preference (ctx->nas_ssp_mode_preference_mask);
-
/* If no value retrieved from SSP, check TP. We only process TP
* values if not 'auto'. */
- if ( tmp == MM_MODEM_CAPABILITY_NONE
- && ctx->nas_tp_mask
- && ctx->nas_tp_mask != QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO)
+ else if (ctx->nas_tp_mask && (ctx->nas_tp_mask != QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO))
tmp = mm_modem_capability_from_qmi_radio_technology_preference (ctx->nas_tp_mask);
/* Final capabilities are the intersection between the Technology
diff --git a/src/mm-modem-helpers-qmi.h b/src/mm-modem-helpers-qmi.h
index 8f02b95b5..1deea7c36 100644
--- a/src/mm-modem-helpers-qmi.h
+++ b/src/mm-modem-helpers-qmi.h
@@ -46,6 +46,8 @@ MMModemAccessTechnology mm_modem_access_technologies_from_qmi_radio_interface_ar
MMModemAccessTechnology mm_modem_access_technology_from_qmi_data_capability (QmiNasDataCapability cap);
MMModemAccessTechnology mm_modem_access_technologies_from_qmi_data_capability_array (GArray *data_capabilities);
+MMModemMode mm_modem_mode_from_qmi_nas_radio_interface (QmiNasRadioInterface iface);
+
MMModemMode mm_modem_mode_from_qmi_radio_technology_preference (QmiNasRadioTechnologyPreference qmi);
QmiNasRadioTechnologyPreference mm_modem_mode_to_qmi_radio_technology_preference (MMModemMode mode,
gboolean is_cdma);
@@ -58,6 +60,12 @@ QmiNasRatModePreference mm_modem_mode_to_qmi_rat_mode_preference (MMModemMode mo
MMModemCapability mm_modem_capability_from_qmi_rat_mode_preference (QmiNasRatModePreference qmi);
QmiNasRatModePreference mm_modem_capability_to_qmi_rat_mode_preference (MMModemCapability caps);
+GArray *mm_modem_capability_to_qmi_acquisition_order_preference (MMModemCapability caps);
+GArray *mm_modem_mode_to_qmi_acquisition_order_preference (MMModemMode allowed,
+ MMModemMode preferred,
+ gboolean is_cdma,
+ gboolean is_3gpp);
+
MMModemCapability mm_modem_capability_from_qmi_radio_technology_preference (QmiNasRadioTechnologyPreference qmi);
QmiNasRadioTechnologyPreference mm_modem_capability_to_qmi_radio_technology_preference (MMModemCapability caps);
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index dba063cfc..58645320a 100644
--- a/src/mm-shared-qmi.c
+++ b/src/mm-shared-qmi.c
@@ -40,7 +40,20 @@
#define PRIVATE_TAG "shared-qmi-private-tag"
static GQuark private_quark;
+typedef enum {
+ FEATURE_UNKNOWN,
+ FEATURE_UNSUPPORTED,
+ FEATURE_SUPPORTED,
+} Feature;
+
typedef struct {
+ /* Capabilities & modes helpers */
+ MMModemCapability current_capabilities;
+ GArray *supported_radio_interfaces;
+ Feature feature_nas_technology_preference;
+ Feature feature_nas_system_selection_preference;
+ gboolean disable_4g_only_mode;
+
/* Location helpers */
MMIfaceModemLocation *iface_modem_location_parent;
MMModemLocationSource enabled_sources;
@@ -56,6 +69,8 @@ typedef struct {
static void
private_free (Private *priv)
{
+ if (priv->supported_radio_interfaces)
+ g_array_unref (priv->supported_radio_interfaces);
if (priv->pds_location_event_report_indication_id)
g_signal_handler_disconnect (priv->pds_client, priv->pds_location_event_report_indication_id);
if (priv->pds_client)
@@ -80,6 +95,9 @@ get_private (MMSharedQmi *self)
if (!priv) {
priv = g_slice_new0 (Private);
+ priv->feature_nas_technology_preference = FEATURE_UNKNOWN;
+ priv->feature_nas_system_selection_preference = FEATURE_UNKNOWN;
+
/* Setup parent class' MMIfaceModemLocation */
g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface);
priv->iface_modem_location_parent = MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface (self);
@@ -91,6 +109,1255 @@ get_private (MMSharedQmi *self)
}
/*****************************************************************************/
+/* Current capabilities setting (Modem interface) */
+
+typedef enum {
+ SET_CURRENT_CAPABILITIES_STEP_FIRST,
+ SET_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE,
+ SET_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE,
+ SET_CURRENT_CAPABILITIES_STEP_RESET,
+ SET_CURRENT_CAPABILITIES_STEP_LAST,
+} SetCurrentCapabilitiesStep;
+
+typedef struct {
+ QmiClientNas *client;
+ MMModemCapability capabilities;
+ gboolean capabilities_updated;
+ SetCurrentCapabilitiesStep step;
+} SetCurrentCapabilitiesContext;
+
+static void
+set_current_capabilities_context_free (SetCurrentCapabilitiesContext *ctx)
+{
+ g_object_unref (ctx->client);
+ g_slice_free (SetCurrentCapabilitiesContext, ctx);
+}
+
+gboolean
+mm_shared_qmi_set_current_capabilities_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void set_current_capabilities_step (GTask *task);
+
+static void
+set_current_capabilities_reset_ready (MMIfaceModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentCapabilitiesContext *ctx;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ if (!mm_shared_qmi_reset_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ set_current_capabilities_step (task);
+}
+
+static void
+set_current_capabilities_set_technology_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentCapabilitiesContext *ctx;
+ QmiMessageNasSetTechnologyPreferenceOutput *output = NULL;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_nas_set_technology_preference_finish (client, res, &error);
+ if (!output || !qmi_message_nas_set_technology_preference_output_get_result (output, &error)) {
+ /* A no-effect error here is not a real error */
+ if (!g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+ /* no effect, just end operation without reset */
+ g_clear_error (&error);
+ ctx->step = SET_CURRENT_CAPABILITIES_STEP_LAST;
+ set_current_capabilities_step (task);
+ goto out;
+ }
+
+ /* success! */
+ ctx->step = SET_CURRENT_CAPABILITIES_STEP_RESET;
+ set_current_capabilities_step (task);
+
+out:
+ if (output)
+ qmi_message_nas_set_technology_preference_output_unref (output);
+}
+
+static void
+set_current_capabilities_technology_preference (GTask *task)
+{
+ SetCurrentCapabilitiesContext *ctx;
+ QmiMessageNasSetTechnologyPreferenceInput *input;
+ QmiNasRadioTechnologyPreference pref;
+
+ ctx = g_task_get_task_data (task);
+
+ pref = mm_modem_capability_to_qmi_radio_technology_preference (ctx->capabilities);
+ if (!pref) {
+ gchar *str;
+
+ str = mm_modem_capability_build_string_from_mask (ctx->capabilities);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unhandled capabilities setting: '%s'",
+ str);
+ g_object_unref (task);
+ g_free (str);
+ return;
+ }
+
+ input = qmi_message_nas_set_technology_preference_input_new ();
+ qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL);
+
+ qmi_client_nas_set_technology_preference (
+ ctx->client,
+ input,
+ 5,
+ NULL,
+ (GAsyncReadyCallback)set_current_capabilities_set_technology_preference_ready,
+ task);
+ qmi_message_nas_set_technology_preference_input_unref (input);
+}
+
+static void
+set_current_capabilities_set_system_selection_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentCapabilitiesContext *ctx;
+ QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
+ if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ goto out;
+ }
+
+ /* success! */
+ ctx->step = SET_CURRENT_CAPABILITIES_STEP_RESET;
+ set_current_capabilities_step (task);
+
+out:
+ if (output)
+ qmi_message_nas_set_system_selection_preference_output_unref (output);
+}
+
+static void
+set_current_capabilities_system_selection_preference (GTask *task)
+{
+ MMSharedQmi *self;
+ Private *priv;
+ SetCurrentCapabilitiesContext *ctx;
+ QmiMessageNasSetSystemSelectionPreferenceInput *input;
+ QmiNasRatModePreference pref;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (MM_SHARED_QMI (self));
+ ctx = g_task_get_task_data (task);
+
+ pref = mm_modem_capability_to_qmi_rat_mode_preference (ctx->capabilities);
+ if (!pref) {
+ gchar *str;
+
+ str = mm_modem_capability_build_string_from_mask (ctx->capabilities);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unhandled capabilities setting: '%s'",
+ str);
+ g_object_unref (task);
+ g_free (str);
+ return;
+ }
+
+ input = qmi_message_nas_set_system_selection_preference_input_new ();
+ qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL);
+ qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL);
+
+ qmi_client_nas_set_system_selection_preference (
+ ctx->client,
+ input,
+ 5,
+ NULL,
+ (GAsyncReadyCallback)set_current_capabilities_set_system_selection_preference_ready,
+ task);
+ qmi_message_nas_set_system_selection_preference_input_unref (input);
+}
+
+static void
+set_current_capabilities_step (GTask *task)
+{
+ MMSharedQmi *self;
+ Private *priv;
+ SetCurrentCapabilitiesContext *ctx;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (MM_SHARED_QMI (self));
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case SET_CURRENT_CAPABILITIES_STEP_FIRST:
+ /* Error out early if both unsupported */
+ if ((priv->feature_nas_system_selection_preference != FEATURE_SUPPORTED) &&
+ (priv->feature_nas_technology_preference != FEATURE_SUPPORTED)) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Setting capabilities is not supported by this device");
+ g_object_unref (task);
+ return;
+ }
+ ctx->step++;
+ /* fall-through */
+
+ case SET_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE:
+ if (priv->feature_nas_system_selection_preference == FEATURE_SUPPORTED) {
+ set_current_capabilities_system_selection_preference (task);
+ return;
+ }
+ ctx->step++;
+ /* fall-through */
+
+ case SET_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE:
+ if (priv->feature_nas_technology_preference == FEATURE_SUPPORTED) {
+ set_current_capabilities_technology_preference (task);
+ return;
+ }
+ ctx->step++;
+ /* fall-through */
+
+ case SET_CURRENT_CAPABILITIES_STEP_RESET:
+ mm_shared_qmi_reset (MM_IFACE_MODEM (self),
+ (GAsyncReadyCallback)set_current_capabilities_reset_ready,
+ task);
+ return;
+
+ case SET_CURRENT_CAPABILITIES_STEP_LAST:
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+}
+
+void
+mm_shared_qmi_set_current_capabilities (MMIfaceModem *self,
+ MMModemCapability capabilities,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ SetCurrentCapabilitiesContext *ctx;
+ GTask *task;
+ QmiClient *client = NULL;
+
+ if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
+ QMI_SERVICE_NAS, &client,
+ callback, user_data))
+ return;
+
+ priv = get_private (MM_SHARED_QMI (self));
+ g_assert (priv->feature_nas_technology_preference != FEATURE_UNKNOWN);
+ g_assert (priv->feature_nas_system_selection_preference != FEATURE_UNKNOWN);
+
+ ctx = g_slice_new0 (SetCurrentCapabilitiesContext);
+ ctx->client = g_object_ref (client);
+ ctx->capabilities = capabilities;
+ ctx->step = SET_CURRENT_CAPABILITIES_STEP_FIRST;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_capabilities_context_free);
+
+ set_current_capabilities_step (task);
+}
+
+/*****************************************************************************/
+/* Current capabilities (Modem interface) */
+
+typedef enum {
+ LOAD_CURRENT_CAPABILITIES_STEP_FIRST,
+ LOAD_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE,
+ LOAD_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE,
+ LOAD_CURRENT_CAPABILITIES_STEP_DMS_GET_CAPABILITIES,
+ LOAD_CURRENT_CAPABILITIES_STEP_LAST,
+} LoadCurrentCapabilitiesStep;
+
+typedef struct {
+ QmiClientNas *nas_client;
+ QmiClientDms *dms_client;
+ LoadCurrentCapabilitiesStep step;
+ MMQmiCapabilitiesContext capabilities_context;
+} LoadCurrentCapabilitiesContext;
+
+MMModemCapability
+mm_shared_qmi_load_current_capabilities_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize value;
+
+ value = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return MM_MODEM_CAPABILITY_NONE;
+ }
+ return (MMModemCapability)value;
+}
+
+static void
+load_current_capabilities_context_free (LoadCurrentCapabilitiesContext *ctx)
+{
+ g_object_unref (ctx->nas_client);
+ g_object_unref (ctx->dms_client);
+ g_slice_free (LoadCurrentCapabilitiesContext, ctx);
+}
+
+static void load_current_capabilities_step (GTask *task);
+
+static void
+load_current_capabilities_get_capabilities_ready (QmiClientDms *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMSharedQmi *self;
+ Private *priv;
+ LoadCurrentCapabilitiesContext *ctx;
+ QmiMessageDmsGetCapabilitiesOutput *output = NULL;
+ GError *error = NULL;
+ guint i;
+ GArray *radio_interface_list;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_dms_get_capabilities_finish (client, res, &error);
+ if (!output) {
+ g_prefix_error (&error, "QMI operation failed: ");
+ goto out;
+ }
+
+ if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) {
+ g_prefix_error (&error, "Couldn't get Capabilities: ");
+ goto out;
+ }
+
+ qmi_message_dms_get_capabilities_output_get_info (
+ output,
+ NULL, /* info_max_tx_channel_rate */
+ NULL, /* info_max_rx_channel_rate */
+ NULL, /* info_data_service_capability */
+ NULL, /* info_sim_capability */
+ &radio_interface_list,
+ NULL);
+
+ /* Cache supported radio interfaces */
+ g_assert (!priv->supported_radio_interfaces);
+ priv->supported_radio_interfaces = g_array_ref (radio_interface_list);
+
+ for (i = 0; i < radio_interface_list->len; i++)
+ ctx->capabilities_context.dms_capabilities |=
+ mm_modem_capability_from_qmi_radio_interface (g_array_index (radio_interface_list, QmiDmsRadioInterface, i));
+
+out:
+ if (output)
+ qmi_message_dms_get_capabilities_output_unref (output);
+
+ /* Failure in DMS Get Capabilities is fatal */
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx->step++;
+ load_current_capabilities_step (task);
+}
+
+static void
+load_current_capabilities_get_technology_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMSharedQmi *self;
+ Private *priv;
+ LoadCurrentCapabilitiesContext *ctx;
+ QmiMessageNasGetTechnologyPreferenceOutput *output = NULL;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (MM_SHARED_QMI (self));
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_nas_get_technology_preference_finish (client, res, &error);
+ if (!output) {
+ mm_dbg ("QMI operation failed: %s", error->message);
+ g_error_free (error);
+ priv->feature_nas_technology_preference = FEATURE_UNSUPPORTED;
+ } 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);
+ priv->feature_nas_technology_preference = FEATURE_SUPPORTED;
+ } else {
+ qmi_message_nas_get_technology_preference_output_get_active (
+ output,
+ &ctx->capabilities_context.nas_tp_mask,
+ NULL, /* duration */
+ NULL);
+ priv->feature_nas_technology_preference = FEATURE_SUPPORTED;
+ }
+
+ if (output)
+ qmi_message_nas_get_technology_preference_output_unref (output);
+
+ ctx->step++;
+ load_current_capabilities_step (task);
+}
+
+static void
+load_current_capabilities_get_system_selection_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMSharedQmi *self;
+ Private *priv;
+ LoadCurrentCapabilitiesContext *ctx;
+ QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+ priv = get_private (MM_SHARED_QMI (self));
+
+ 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);
+ priv->feature_nas_system_selection_preference = FEATURE_UNSUPPORTED;
+ } 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);
+ priv->feature_nas_system_selection_preference = FEATURE_SUPPORTED;
+ } else {
+ qmi_message_nas_get_system_selection_preference_output_get_mode_preference (
+ output,
+ &ctx->capabilities_context.nas_ssp_mode_preference_mask,
+ NULL);
+ priv->feature_nas_system_selection_preference = FEATURE_SUPPORTED;
+ }
+
+ if (output)
+ qmi_message_nas_get_system_selection_preference_output_unref (output);
+
+ ctx->step++;
+ load_current_capabilities_step (task);
+}
+
+static void
+load_current_capabilities_step (GTask *task)
+{
+ MMSharedQmi *self;
+ Private *priv;
+ LoadCurrentCapabilitiesContext *ctx;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (MM_SHARED_QMI (self));
+ ctx = g_task_get_task_data (task);
+
+ switch (ctx->step) {
+ case LOAD_CURRENT_CAPABILITIES_STEP_FIRST:
+ ctx->step++;
+ /* fall-through */
+
+ case LOAD_CURRENT_CAPABILITIES_STEP_NAS_SYSTEM_SELECTION_PREFERENCE:
+ qmi_client_nas_get_system_selection_preference (
+ ctx->nas_client, NULL, 5, NULL,
+ (GAsyncReadyCallback)load_current_capabilities_get_system_selection_preference_ready,
+ task);
+ return;
+
+ case LOAD_CURRENT_CAPABILITIES_STEP_NAS_TECHNOLOGY_PREFERENCE:
+ qmi_client_nas_get_technology_preference (
+ ctx->nas_client, NULL, 5, NULL,
+ (GAsyncReadyCallback)load_current_capabilities_get_technology_preference_ready,
+ task);
+ return;
+
+ case LOAD_CURRENT_CAPABILITIES_STEP_DMS_GET_CAPABILITIES:
+ qmi_client_dms_get_capabilities (
+ ctx->dms_client, NULL, 5, NULL,
+ (GAsyncReadyCallback)load_current_capabilities_get_capabilities_ready,
+ task);
+ return;
+
+ case LOAD_CURRENT_CAPABILITIES_STEP_LAST:
+ g_assert (priv->feature_nas_technology_preference != FEATURE_UNKNOWN);
+ g_assert (priv->feature_nas_system_selection_preference != FEATURE_UNKNOWN);
+ priv->current_capabilities = mm_modem_capability_from_qmi_capabilities_context (&ctx->capabilities_context);
+ g_task_return_int (task, priv->current_capabilities);
+ g_object_unref (task);
+ return;
+ }
+}
+
+void
+mm_shared_qmi_load_current_capabilities (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ LoadCurrentCapabilitiesContext *ctx;
+ GTask *task;
+ QmiClient *nas_client = NULL;
+ QmiClient *dms_client = NULL;
+ Private *priv;
+
+ /*
+ * We assume that DMS Get Capabilities reports always the same result,
+ * that will include all capabilities supported by the device regardless
+ * of which ones are configured at the moment. E.g. for the Load Supported
+ * Capabilities we base the logic exclusively on this method's output.
+ *
+ * We then consider 3 different cases:
+ * a) If the device supports NAS System Selection Preference, we use the
+ * "mode preference" TLV to select currently enabled capabilities.
+ * b) If the device supports NAS Technology Preference (older devices),
+ * we use this method to select currently enabled capabilities.
+ * c) If none of those messages is supported we don't allow swiching
+ * capabilities.
+ */
+
+ if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
+ QMI_SERVICE_NAS, &nas_client,
+ callback, user_data))
+ return;
+
+ if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
+ QMI_SERVICE_DMS, &dms_client,
+ callback, user_data))
+ return;
+
+ /* Current capabilities is the first thing run, and will only be run once per modem,
+ * so we should here check support for the optional features. */
+ priv = get_private (MM_SHARED_QMI (self));
+ g_assert (priv->feature_nas_technology_preference == FEATURE_UNKNOWN);
+ g_assert (priv->feature_nas_system_selection_preference == FEATURE_UNKNOWN);
+
+ ctx = g_slice_new0 (LoadCurrentCapabilitiesContext);
+ ctx->nas_client = g_object_ref (nas_client);
+ ctx->dms_client = g_object_ref (dms_client);
+ ctx->step = LOAD_CURRENT_CAPABILITIES_STEP_FIRST;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)load_current_capabilities_context_free);
+
+ load_current_capabilities_step (task);
+}
+
+/*****************************************************************************/
+/* Supported capabilities (Modem interface) */
+
+GArray *
+mm_shared_qmi_load_supported_capabilities_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+void
+mm_shared_qmi_load_supported_capabilities (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+ MMModemCapability mask;
+ MMModemCapability single;
+ GArray *supported_combinations;
+ guint i;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* List of radio interfaces preloaded in current capabilities */
+ priv = get_private (MM_SHARED_QMI (self));
+ if (!priv->supported_radio_interfaces) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "cannot load current capabilities without radio interface information");
+ g_object_unref (task);
+ return;
+ }
+
+ /* Build mask with all supported capabilities */
+ mask = MM_MODEM_CAPABILITY_NONE;
+ for (i = 0; i < priv->supported_radio_interfaces->len; i++)
+ mask |= mm_modem_capability_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i));
+
+ supported_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), 3);
+
+ /* Add all possible supported capability combinations.
+ * In order to avoid unnecessary modem reboots, we will only implement capabilities
+ * switching only when switching GSM/UMTS+CDMA/EVDO multimode devices, and only if
+ * we have support for the commands doing it.
+ */
+ if (priv->feature_nas_technology_preference == FEATURE_SUPPORTED || priv->feature_nas_system_selection_preference == FEATURE_UNKNOWN) {
+ if (mask == (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)) {
+ /* Multimode GSM/UMTS+CDMA/EVDO device switched to GSM/UMTS only */
+ single = MM_MODEM_CAPABILITY_GSM_UMTS;
+ g_array_append_val (supported_combinations, single);
+ /* Multimode GSM/UMTS+CDMA/EVDO device switched to CDMA/EVDO only */
+ single = MM_MODEM_CAPABILITY_CDMA_EVDO;
+ g_array_append_val (supported_combinations, single);
+ } else if (mask == (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE)) {
+ /* Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to GSM/UMTS+LTE only */
+ single = MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE;
+ g_array_append_val (supported_combinations, single);
+ /* Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to CDMA/EVDO+LTE only */
+ single = MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE;
+ g_array_append_val (supported_combinations, single);
+ /*
+ * Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to LTE only.
+ *
+ * This case is required because we use the same methods and operations to
+ * switch capabilities and modes. For the LTE capability there is a direct
+ * related 4G mode, and so we cannot select a '4G only' mode in this device
+ * because we wouldn't be able to know the full list of current capabilities
+ * if the device was rebooted, as we would only see LTE capability. So,
+ * handle this special case so that the LTE/4G-only mode can exclusively be
+ * selected as capability switching in this kind of devices.
+ */
+ priv->disable_4g_only_mode = TRUE;
+ single = MM_MODEM_CAPABILITY_LTE;
+ g_array_append_val (supported_combinations, single);
+ }
+ }
+
+ /* Add the full mask itself */
+ single = mask;
+ g_array_append_val (supported_combinations, single);
+
+ g_task_return_pointer (task, supported_combinations, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Allowed modes setting (Modem interface) */
+
+typedef struct {
+ QmiClientNas *client;
+ MMModemMode allowed;
+ MMModemMode preferred;
+} SetCurrentModesContext;
+
+static void
+set_current_modes_context_free (SetCurrentModesContext *ctx)
+{
+ g_object_unref (ctx->client);
+ g_slice_free (SetCurrentModesContext, ctx);
+}
+
+gboolean
+mm_shared_qmi_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+set_current_modes_technology_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesContext *ctx;
+ QmiMessageNasSetTechnologyPreferenceOutput *output = NULL;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_nas_set_technology_preference_finish (client, res, &error);
+ if (!output ||
+ (!qmi_message_nas_set_technology_preference_output_get_result (output, &error) &&
+ !g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_NO_EFFECT))) {
+ g_task_return_error (task, error);
+ } else {
+ g_clear_error (&error);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+
+ if (output)
+ qmi_message_nas_set_technology_preference_output_unref (output);
+}
+
+static void
+set_current_modes_technology_preference (GTask *task)
+{
+ MMIfaceModem *self;
+ SetCurrentModesContext *ctx;
+ QmiMessageNasSetTechnologyPreferenceInput *input;
+ QmiNasRadioTechnologyPreference pref;
+
+ self = g_task_get_source_object (task);
+ ctx = g_task_get_task_data (task);
+
+ if (ctx->preferred != MM_MODEM_MODE_NONE) {
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot set specific preferred mode");
+ g_object_unref (task);
+ return;
+ }
+
+ pref = mm_modem_mode_to_qmi_radio_technology_preference (ctx->allowed, mm_iface_modem_is_cdma (self));
+ if (!pref) {
+ gchar *str;
+
+ str = mm_modem_mode_build_string_from_mask (ctx->allowed);
+ g_task_return_new_error (task,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unhandled allowed mode setting: '%s'",
+ str);
+ g_object_unref (task);
+ g_free (str);
+ return;
+ }
+
+ input = qmi_message_nas_set_technology_preference_input_new ();
+ qmi_message_nas_set_technology_preference_input_set_current (input, pref, QMI_NAS_PREFERENCE_DURATION_PERMANENT, NULL);
+
+ qmi_client_nas_set_technology_preference (
+ ctx->client,
+ input,
+ 5,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)set_current_modes_technology_preference_ready,
+ task);
+ qmi_message_nas_set_technology_preference_input_unref (input);
+}
+
+static void
+set_current_modes_system_selection_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ SetCurrentModesContext *ctx;
+ QmiMessageNasSetSystemSelectionPreferenceOutput *output = NULL;
+ GError *error = NULL;
+
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_nas_set_system_selection_preference_finish (client, res, &error);
+ if (!output || !qmi_message_nas_set_system_selection_preference_output_get_result (output, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ if (output)
+ qmi_message_nas_set_system_selection_preference_output_unref (output);
+}
+
+static void
+set_current_modes_system_selection_preference (GTask *task)
+{
+ MMIfaceModem *self;
+ SetCurrentModesContext *ctx;
+ QmiMessageNasSetSystemSelectionPreferenceInput *input;
+ Private *priv;
+ QmiNasRatModePreference pref;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (MM_SHARED_QMI (self));
+ ctx = g_task_get_task_data (task);
+
+ input = qmi_message_nas_set_system_selection_preference_input_new ();
+ qmi_message_nas_set_system_selection_preference_input_set_change_duration (input, QMI_NAS_CHANGE_DURATION_PERMANENT, NULL);
+
+ /* Preferred modes */
+
+ if (ctx->preferred != MM_MODEM_MODE_NONE) {
+ GArray *array;
+
+ /* Acquisition order array */
+ array = mm_modem_mode_to_qmi_acquisition_order_preference (ctx->allowed,
+ ctx->preferred,
+ mm_iface_modem_is_cdma (self),
+ mm_iface_modem_is_3gpp (self));
+ g_assert (array);
+ qmi_message_nas_set_system_selection_preference_input_set_acquisition_order_preference (input, array, NULL);
+ g_array_unref (array);
+
+ /* Only set GSM/WCDMA acquisition order preference if both 2G and 3G given as allowed */
+ if (mm_iface_modem_is_3gpp (self) && ((ctx->allowed & (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G)) == (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G))) {
+ QmiNasGsmWcdmaAcquisitionOrderPreference order;
+
+ order = mm_modem_mode_to_qmi_gsm_wcdma_acquisition_order_preference (ctx->preferred);
+ qmi_message_nas_set_system_selection_preference_input_set_gsm_wcdma_acquisition_order_preference (input, order, NULL);
+ }
+ }
+
+ /* Allowed modes */
+ pref = mm_modem_mode_to_qmi_rat_mode_preference (ctx->allowed,
+ mm_iface_modem_is_cdma (self),
+ mm_iface_modem_is_3gpp (self));
+ qmi_message_nas_set_system_selection_preference_input_set_mode_preference (input, pref, NULL);
+
+ qmi_client_nas_set_system_selection_preference (
+ ctx->client,
+ input,
+ 5,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)set_current_modes_system_selection_preference_ready,
+ task);
+ qmi_message_nas_set_system_selection_preference_input_unref (input);
+}
+
+void
+mm_shared_qmi_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SetCurrentModesContext *ctx;
+ GTask *task;
+ QmiClient *client = NULL;
+ Private *priv;
+
+ if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
+ QMI_SERVICE_NAS, &client,
+ callback, user_data))
+ return;
+
+ ctx = g_slice_new0 (SetCurrentModesContext);
+ ctx->client = g_object_ref (client);
+
+ if (allowed == MM_MODEM_MODE_ANY && ctx->preferred == MM_MODEM_MODE_NONE) {
+ ctx->allowed = MM_MODEM_MODE_NONE;
+ if (mm_iface_modem_is_2g (self))
+ ctx->allowed |= MM_MODEM_MODE_2G;
+ if (mm_iface_modem_is_3g (self))
+ ctx->allowed |= MM_MODEM_MODE_3G;
+ if (mm_iface_modem_is_4g (self))
+ ctx->allowed |= MM_MODEM_MODE_4G;
+ ctx->preferred = MM_MODEM_MODE_NONE;
+ } else {
+ ctx->allowed = allowed;
+ ctx->preferred = preferred;
+ }
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)set_current_modes_context_free);
+
+ priv = get_private (MM_SHARED_QMI (self));
+
+ if (priv->feature_nas_system_selection_preference == FEATURE_SUPPORTED) {
+ set_current_modes_system_selection_preference (task);
+ return;
+ }
+
+ if (priv->feature_nas_technology_preference == FEATURE_SUPPORTED) {
+ set_current_modes_technology_preference (task);
+ return;
+ }
+
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Setting allowed modes is not supported by this device");
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Load current modes (Modem interface) */
+
+typedef struct {
+ QmiClientNas *client;
+} LoadCurrentModesContext;
+
+typedef struct {
+ MMModemMode allowed;
+ MMModemMode preferred;
+} LoadCurrentModesResult;
+
+static void
+load_current_modes_context_free (LoadCurrentModesContext *ctx)
+{
+ g_object_unref (ctx->client);
+ g_free (ctx);
+}
+
+gboolean
+mm_shared_qmi_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error)
+{
+ LoadCurrentModesResult *result;
+
+ result = g_task_propagate_pointer (G_TASK (res), error);
+ if (!result)
+ return FALSE;
+
+ *allowed = result->allowed;
+ *preferred = result->preferred;
+ g_free (result);
+ return TRUE;
+}
+
+static void
+get_technology_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadCurrentModesContext *ctx;
+ LoadCurrentModesResult *result = NULL;
+ QmiMessageNasGetTechnologyPreferenceOutput *output = NULL;
+ GError *error = NULL;
+ MMModemMode allowed;
+ QmiNasRadioTechnologyPreference preference_mask;
+
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_nas_get_technology_preference_finish (client, res, &error);
+ if (!output || !qmi_message_nas_get_technology_preference_output_get_result (output, &error)) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ qmi_message_nas_get_technology_preference_output_get_active (
+ output,
+ &preference_mask,
+ NULL, /* duration */
+ NULL);
+ allowed = mm_modem_mode_from_qmi_radio_technology_preference (preference_mask);
+ if (allowed == MM_MODEM_MODE_NONE) {
+ gchar *str;
+
+ str = qmi_nas_radio_technology_preference_build_string_from_mask (preference_mask);
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unsupported modes reported: '%s'", str);
+ g_free (str);
+ goto out;
+ }
+
+ /* We got a valid value from here */
+ result = g_new (LoadCurrentModesResult, 1);
+ result->allowed = allowed;
+ result->preferred = MM_MODEM_MODE_NONE;
+ g_task_return_pointer (task, result, g_free);
+
+out:
+ if (output)
+ qmi_message_nas_get_technology_preference_output_unref (output);
+ g_object_unref (task);
+}
+
+static void
+load_current_modes_technology_preference (GTask *task)
+{
+ LoadCurrentModesContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+
+ qmi_client_nas_get_technology_preference (
+ ctx->client,
+ NULL, /* no input */
+ 5,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)get_technology_preference_ready,
+ task);
+}
+
+static void
+load_current_modes_system_selection_preference_ready (QmiClientNas *client,
+ GAsyncResult *res,
+ GTask *task)
+{
+ LoadCurrentModesContext *ctx;
+ LoadCurrentModesResult *result = NULL;
+ QmiMessageNasGetSystemSelectionPreferenceOutput *output = NULL;
+ GError *error = NULL;
+ QmiNasRatModePreference mode_preference_mask = 0;
+ MMModemMode allowed;
+
+ ctx = g_task_get_task_data (task);
+
+ output = qmi_client_nas_get_system_selection_preference_finish (client, res, &error);
+ if (!output || !qmi_message_nas_get_system_selection_preference_output_get_result (output, &error)) {
+ g_task_return_error (task, error);
+ goto out;
+ }
+
+ if (!qmi_message_nas_get_system_selection_preference_output_get_mode_preference (
+ output,
+ &mode_preference_mask,
+ NULL)) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Mode preference not reported in system selection preference");
+ goto out;
+ }
+
+ allowed = mm_modem_mode_from_qmi_rat_mode_preference (mode_preference_mask);
+ if (allowed == MM_MODEM_MODE_NONE) {
+ gchar *str;
+
+ str = qmi_nas_rat_mode_preference_build_string_from_mask (mode_preference_mask);
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unsupported modes reported: '%s'", str);
+ g_free (str);
+ goto out;
+ }
+
+ /* We got a valid value from here */
+ result = g_new (LoadCurrentModesResult, 1);
+ result->allowed = allowed;
+ result->preferred = MM_MODEM_MODE_NONE;
+
+ /* For 2G+3G only rely on the GSM/WCDMA acquisition order preference TLV */
+ if (mode_preference_mask == (QMI_NAS_RAT_MODE_PREFERENCE_GSM | QMI_NAS_RAT_MODE_PREFERENCE_UMTS)) {
+ QmiNasGsmWcdmaAcquisitionOrderPreference gsm_or_wcdma;
+
+ if (qmi_message_nas_get_system_selection_preference_output_get_gsm_wcdma_acquisition_order_preference (
+ output,
+ &gsm_or_wcdma,
+ NULL))
+ result->preferred = mm_modem_mode_from_qmi_gsm_wcdma_acquisition_order_preference (gsm_or_wcdma);
+ }
+ /* Otherwise, rely on the acquisition order array TLV */
+ else {
+ GArray *array;
+
+ if (qmi_message_nas_get_system_selection_preference_output_get_acquisition_order_preference (
+ output,
+ &array,
+ NULL) &&
+ array->len > 0) {
+ guint i;
+
+ /* The array of preference contains the preference of the full list of supported
+ * access technologies, regardless of whether they're enabled or not. So, look for
+ * the first one that is flagged as enabled, not just the first one in the array.
+ */
+ for (i = 0; i < array->len; i++) {
+ MMModemMode mode;
+
+ mode = mm_modem_mode_from_qmi_nas_radio_interface (g_array_index (array, QmiNasRadioInterface, i));
+ if (allowed == mode)
+ break;
+ if (allowed & mode) {
+ result->preferred = mode;
+ break;
+ }
+ }
+ }
+ }
+
+ g_task_return_pointer (task, result, g_free);
+
+out:
+ if (output)
+ qmi_message_nas_get_system_selection_preference_output_unref (output);
+ g_object_unref (task);
+}
+
+static void
+load_current_modes_system_selection_preference (GTask *task)
+{
+ LoadCurrentModesContext *ctx;
+
+ ctx = g_task_get_task_data (task);
+ qmi_client_nas_get_system_selection_preference (
+ ctx->client,
+ NULL, /* no input */
+ 5,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)load_current_modes_system_selection_preference_ready,
+ task);
+}
+
+void
+mm_shared_qmi_load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ LoadCurrentModesContext *ctx;
+ GTask *task;
+ QmiClient *client = NULL;
+
+ if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
+ QMI_SERVICE_NAS, &client,
+ callback, user_data))
+ return;
+
+ ctx = g_new0 (LoadCurrentModesContext, 1);
+ ctx->client = g_object_ref (client);
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, ctx, (GDestroyNotify)load_current_modes_context_free);
+
+ priv = get_private (MM_SHARED_QMI (self));
+
+ if (priv->feature_nas_system_selection_preference != FEATURE_UNSUPPORTED) {
+ load_current_modes_system_selection_preference (task);
+ return;
+ }
+
+ if (priv->feature_nas_technology_preference != FEATURE_UNSUPPORTED) {
+ load_current_modes_technology_preference (task);
+ return;
+ }
+
+ /* Default to supported */
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Loading current modes is not supported by this device");
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* Supported modes (Modem interface) */
+
+GArray *
+mm_shared_qmi_load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+void
+mm_shared_qmi_load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ GArray *combinations;
+ MMModemModeCombination mode;
+ Private *priv;
+ MMModemMode mask_all;
+ guint i;
+ GArray *all;
+ GArray *filtered;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ priv = get_private (MM_SHARED_QMI (self));
+ g_assert (priv->supported_radio_interfaces);
+
+ /* Build all, based on the supported radio interfaces */
+ mask_all = MM_MODEM_MODE_NONE;
+ for (i = 0; i < priv->supported_radio_interfaces->len; i++)
+ mask_all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i));
+ mode.allowed = mask_all;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+ g_array_append_val (all, mode);
+
+ /* If SSP and TP are not supported, ignore supported mode management */
+ if (priv->feature_nas_system_selection_preference == FEATURE_UNSUPPORTED && priv->feature_nas_technology_preference == FEATURE_UNSUPPORTED) {
+ g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+ return;
+ }
+
+ combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 5);
+
+ /* 2G-only, 3G-only */
+ mode.allowed = MM_MODEM_MODE_2G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ mode.allowed = MM_MODEM_MODE_3G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+
+ /* 4G-only mode is not possible in multimode GSM/UMTS+CDMA/EVDO+LTE
+ * devices. This configuration may be selected as "LTE only" capability
+ * instead. */
+ if (!priv->disable_4g_only_mode) {
+ mode.allowed = MM_MODEM_MODE_4G;
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 2G+3G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ if (priv->feature_nas_system_selection_preference != FEATURE_UNSUPPORTED) {
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ } else {
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 2G+4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
+ if (priv->feature_nas_system_selection_preference != FEATURE_UNSUPPORTED) {
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ } else {
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 3G+4G */
+ mode.allowed = (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ if (priv->feature_nas_system_selection_preference != FEATURE_UNSUPPORTED) {
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ } else {
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* 2G+3G+4G */
+ mode.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
+ if (priv->feature_nas_system_selection_preference != FEATURE_UNSUPPORTED) {
+ mode.preferred = MM_MODEM_MODE_4G;
+ g_array_append_val (combinations, mode);
+ mode.preferred = MM_MODEM_MODE_3G;
+ g_array_append_val (combinations, mode);
+ mode.preferred = MM_MODEM_MODE_2G;
+ g_array_append_val (combinations, mode);
+ } else {
+ mode.preferred = MM_MODEM_MODE_NONE;
+ g_array_append_val (combinations, mode);
+ }
+
+ /* Filter out unsupported modes */
+ filtered = mm_filter_supported_modes (all, combinations);
+ g_array_unref (all);
+ g_array_unref (combinations);
+
+ g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
/* Reset (Modem interface) */
gboolean
diff --git a/src/mm-shared-qmi.h b/src/mm-shared-qmi.h
index af660caac..9b3e10c53 100644
--- a/src/mm-shared-qmi.h
+++ b/src/mm-shared-qmi.h
@@ -62,19 +62,60 @@ gboolean mm_shared_qmi_ensure_client (MMSharedQmi *self,
/* Shared QMI device management support */
-void mm_shared_qmi_reset (MMIfaceModem *self,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean mm_shared_qmi_reset_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error);
-void mm_shared_qmi_factory_reset (MMIfaceModem *self,
- const gchar *code,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean mm_shared_qmi_factory_reset_finish (MMIfaceModem *self,
- GAsyncResult *res,
- GError **error);
+void mm_shared_qmi_load_supported_capabilities (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_qmi_load_supported_capabilities_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_qmi_load_current_capabilities (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemCapability mm_shared_qmi_load_current_capabilities_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_qmi_set_current_capabilities (MMIfaceModem *self,
+ MMModemCapability capabilities,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_qmi_set_current_capabilities_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_qmi_load_supported_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GArray *mm_shared_qmi_load_supported_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_qmi_load_current_modes (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_qmi_load_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ MMModemMode *allowed,
+ MMModemMode *preferred,
+ GError **error);
+void mm_shared_qmi_set_current_modes (MMIfaceModem *self,
+ MMModemMode allowed,
+ MMModemMode preferred,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_qmi_set_current_modes_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_qmi_reset (MMIfaceModem *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_qmi_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_qmi_factory_reset (MMIfaceModem *self,
+ const gchar *code,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_qmi_factory_reset_finish (MMIfaceModem *self,
+ GAsyncResult *res,
+ GError **error);
/* Shared QMI location support */