aboutsummaryrefslogtreecommitdiff
path: root/src/mm-shared-qmi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-shared-qmi.c')
-rw-r--r--src/mm-shared-qmi.c1267
1 files changed, 1267 insertions, 0 deletions
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index dba063cf..58645320 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