diff options
-rw-r--r-- | src/mm-broadband-modem-mbim.c | 5 | ||||
-rw-r--r-- | src/mm-broadband-modem-qmi.c | 5 | ||||
-rw-r--r-- | src/mm-broadband-modem.c | 14 | ||||
-rw-r--r-- | src/mm-shared-qmi.c | 918 | ||||
-rw-r--r-- | src/mm-shared-qmi.h | 14 |
5 files changed, 956 insertions, 0 deletions
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c index e66397dd..62b2fcd7 100644 --- a/src/mm-broadband-modem-mbim.c +++ b/src/mm-broadband-modem-mbim.c @@ -1937,6 +1937,7 @@ static const QmiService qmi_services[] = { QMI_SERVICE_NAS, QMI_SERVICE_PDS, QMI_SERVICE_LOC, + QMI_SERVICE_PDC, }; static void allocate_next_qmi_client (GTask *task); @@ -5231,6 +5232,10 @@ iface_modem_init (MMIfaceModem *iface) iface->load_supported_ip_families_finish = modem_load_supported_ip_families_finish; #if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED + iface->load_carrier_config = mm_shared_qmi_load_carrier_config; + iface->load_carrier_config_finish = mm_shared_qmi_load_carrier_config_finish; + iface->setup_carrier_config = mm_shared_qmi_setup_carrier_config; + iface->setup_carrier_config_finish = mm_shared_qmi_setup_carrier_config_finish; iface->load_supported_bands = mm_shared_qmi_load_supported_bands; iface->load_supported_bands_finish = mm_shared_qmi_load_supported_bands_finish; iface->load_current_bands = mm_shared_qmi_load_current_bands; diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c index 41c07c23..7356a5e8 100644 --- a/src/mm-broadband-modem-qmi.c +++ b/src/mm-broadband-modem-qmi.c @@ -8243,6 +8243,7 @@ static const QmiService qmi_services[] = { QMI_SERVICE_OMA, QMI_SERVICE_UIM, QMI_SERVICE_LOC, + QMI_SERVICE_PDC, }; typedef struct { @@ -8586,6 +8587,10 @@ iface_modem_init (MMIfaceModem *iface) iface->load_power_state_finish = load_power_state_finish; iface->load_supported_ip_families = modem_load_supported_ip_families; iface->load_supported_ip_families_finish = modem_load_supported_ip_families_finish; + iface->load_carrier_config = mm_shared_qmi_load_carrier_config; + iface->load_carrier_config_finish = mm_shared_qmi_load_carrier_config_finish; + iface->setup_carrier_config = mm_shared_qmi_setup_carrier_config; + iface->setup_carrier_config_finish = mm_shared_qmi_setup_carrier_config_finish; /* Enabling/disabling */ iface->modem_power_up = modem_power_up; diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c index bc26c206..0a51b4b5 100644 --- a/src/mm-broadband-modem.c +++ b/src/mm-broadband-modem.c @@ -120,6 +120,7 @@ enum { PROP_MODEM_SIM_HOT_SWAP_SUPPORTED, PROP_MODEM_SIM_HOT_SWAP_CONFIGURED, PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, + PROP_MODEM_CARRIER_CONFIG_MAPPING, PROP_FLOW_CONTROL, PROP_LAST }; @@ -147,6 +148,7 @@ struct _MMBroadbandModemPrivate { MMBaseSim *modem_sim; MMBearerList *modem_bearer_list; MMModemState modem_state; + gchar *carrier_config_mapping; /* Implementation helpers */ MMModemCharset modem_current_charset; gboolean modem_cind_support_checked; @@ -11189,6 +11191,9 @@ set_property (GObject *object, case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED: self->priv->periodic_signal_check_disabled = g_value_get_boolean (value); break; + case PROP_MODEM_CARRIER_CONFIG_MAPPING: + self->priv->carrier_config_mapping = g_value_dup_string (value); + break; case PROP_FLOW_CONTROL: self->priv->flow_control = g_value_get_flags (value); break; @@ -11309,6 +11314,9 @@ get_property (GObject *object, case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED: g_value_set_boolean (value, self->priv->periodic_signal_check_disabled); break; + case PROP_MODEM_CARRIER_CONFIG_MAPPING: + g_value_set_string (value, self->priv->carrier_config_mapping); + break; case PROP_FLOW_CONTROL: g_value_set_flags (value, self->priv->flow_control); break; @@ -11362,6 +11370,8 @@ finalize (GObject *object) if (self->priv->modem_3gpp_registration_regex) mm_3gpp_creg_regex_destroy (self->priv->modem_3gpp_registration_regex); + g_free (self->priv->carrier_config_mapping); + G_OBJECT_CLASS (mm_broadband_modem_parent_class)->finalize (object); } @@ -11841,6 +11851,10 @@ mm_broadband_modem_class_init (MMBroadbandModemClass *klass) PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED); + g_object_class_override_property (object_class, + PROP_MODEM_CARRIER_CONFIG_MAPPING, + MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING); + properties[PROP_FLOW_CONTROL] = g_param_spec_flags (MM_BROADBAND_MODEM_FLOW_CONTROL, "Flow control", diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c index f3a1a47c..c48b543a 100644 --- a/src/mm-shared-qmi.c +++ b/src/mm-shared-qmi.c @@ -48,6 +48,22 @@ typedef enum { } Feature; typedef struct { + GArray *id; + QmiPdcConfigurationType config_type; + guint32 token; + guint32 version; + gchar *description; + guint32 total_size; +} ConfigInfo; + +static void +config_info_clear (ConfigInfo *config_info) +{ + g_array_unref (config_info->id); + g_free (config_info->description); +} + +typedef struct { /* Capabilities & modes helpers */ MMModemCapability current_capabilities; GArray *supported_radio_interfaces; @@ -67,11 +83,17 @@ typedef struct { gchar **loc_assistance_data_servers; guint32 loc_assistance_data_max_file_size; guint32 loc_assistance_data_max_part_size; + + /* Carrier config helpers */ + GArray *config_list; + gint config_active_i; } Private; static void private_free (Private *priv) { + if (priv->config_list) + g_array_unref (priv->config_list); if (priv->supported_bands) g_array_unref (priv->supported_bands); if (priv->supported_radio_interfaces) @@ -102,6 +124,7 @@ get_private (MMSharedQmi *self) priv->feature_nas_technology_preference = FEATURE_UNKNOWN; priv->feature_nas_system_selection_preference = FEATURE_UNKNOWN; + priv->config_active_i = -1; /* Setup parent class' MMIfaceModemLocation */ g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface); @@ -1934,6 +1957,901 @@ mm_shared_qmi_factory_reset (MMIfaceModem *self, } /*****************************************************************************/ +/* Setup carrier config (Modem interface) */ + +#define SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS 10 +#define GENERIC_CONFIG_FALLBACK "generic" + +typedef enum { + SETUP_CARRIER_CONFIG_STEP_FIRST, + SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED, + SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED, + SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT, + SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT, + SETUP_CARRIER_CONFIG_STEP_LAST, +} SetupCarrierConfigStep; + + +typedef struct { + SetupCarrierConfigStep step; + QmiClientPdc *client; + GKeyFile *keyfile; + gchar *imsi; + + gint config_requested_i; + gchar *config_requested; + + guint token; + guint timeout_id; + gulong set_selected_config_indication_id; + gulong activate_config_indication_id; +} SetupCarrierConfigContext; + +/* Allow to cleanup action setup right away, without being tied + * to the lifecycle of the GTask */ +static void +setup_carrier_config_context_cleanup_action (SetupCarrierConfigContext *ctx) +{ + if (ctx->activate_config_indication_id) { + g_signal_handler_disconnect (ctx->client, ctx->activate_config_indication_id); + ctx->activate_config_indication_id = 0; + } + if (ctx->set_selected_config_indication_id) { + g_signal_handler_disconnect (ctx->client, ctx->set_selected_config_indication_id); + ctx->set_selected_config_indication_id = 0; + } + if (ctx->timeout_id) { + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + } +} + +static void +setup_carrier_config_context_free (SetupCarrierConfigContext *ctx) +{ + setup_carrier_config_context_cleanup_action (ctx); + + g_free (ctx->config_requested); + g_free (ctx->imsi); + g_key_file_unref (ctx->keyfile); + g_clear_object (&ctx->client); + g_slice_free (SetupCarrierConfigContext, ctx); +} + +gboolean +mm_shared_qmi_setup_carrier_config_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void setup_carrier_config_step (GTask *task); + +static void +setup_carrier_config_abort (GTask *task, + GError *error) +{ + SetupCarrierConfigContext *ctx; + + ctx = g_task_get_task_data (task); + setup_carrier_config_context_cleanup_action (ctx); + g_task_return_error (task, error); + g_object_unref (task); +} + +static gboolean +setup_carrier_config_timeout_no_error (GTask *task) +{ + SetupCarrierConfigContext *ctx; + + ctx = g_task_get_task_data (task); + g_assert (ctx->timeout_id); + ctx->timeout_id = 0; + + setup_carrier_config_context_cleanup_action (ctx); + ctx->step++; + setup_carrier_config_step (task); + + return G_SOURCE_REMOVE; +} + +static gboolean +setup_carrier_config_timeout (GTask *task) +{ + SetupCarrierConfigContext *ctx; + + ctx = g_task_get_task_data (task); + g_assert (ctx->timeout_id); + ctx->timeout_id = 0; + + setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, + "Operation timed out")); + + return G_SOURCE_REMOVE; +} + +static void +activate_config_indication (QmiClientPdc *client, + QmiIndicationPdcActivateConfigOutput *output, + GTask *task) +{ + SetupCarrierConfigContext *ctx; + GError *error = NULL; + guint16 error_code = 0; + + ctx = g_task_get_task_data (task); + + if (!qmi_indication_pdc_activate_config_output_get_indication_result (output, &error_code, &error)) { + setup_carrier_config_abort (task, error); + return; + } + + if (error_code != 0) { + setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't activate config: %s", + qmi_protocol_error_get_string ((QmiProtocolError) error_code))); + return; + } + + /* Go on */ + setup_carrier_config_context_cleanup_action (ctx); + ctx->step++; + setup_carrier_config_step (task); +} + +static void +activate_config_ready (QmiClientPdc *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdcActivateConfigOutput *output; + SetupCarrierConfigContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_pdc_activate_config_finish (client, res, &error); + if (!output || !qmi_message_pdc_activate_config_output_get_result (output, &error)) { + setup_carrier_config_abort (task, error); + goto out; + } + + /* When we activate the config, if the operation is successful, we'll just + * see the modem going away completely. So, do not consider an error the timeout + * waiting for the Activate Config indication, as that is actually a good + * thing. + */ + ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS, + (GSourceFunc) setup_carrier_config_timeout_no_error, + task); + ctx->activate_config_indication_id = g_signal_connect (ctx->client, + "activate-config", + G_CALLBACK (activate_config_indication), + task); +out: + if (output) + qmi_message_pdc_activate_config_output_unref (output); +} + +static void +set_selected_config_indication (QmiClientPdc *client, + QmiIndicationPdcSetSelectedConfigOutput *output, + GTask *task) +{ + SetupCarrierConfigContext *ctx; + GError *error = NULL; + guint16 error_code = 0; + + ctx = g_task_get_task_data (task); + + if (!qmi_indication_pdc_set_selected_config_output_get_indication_result (output, &error_code, &error)) { + setup_carrier_config_abort (task, error); + return; + } + + if (error_code != 0) { + setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't set selected config: %s", + qmi_protocol_error_get_string ((QmiProtocolError) error_code))); + return; + } + + /* Go on */ + setup_carrier_config_context_cleanup_action (ctx); + ctx->step++; + setup_carrier_config_step (task); +} + +static void +set_selected_config_ready (QmiClientPdc *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdcSetSelectedConfigOutput *output; + SetupCarrierConfigContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_pdc_set_selected_config_finish (client, res, &error); + if (!output || !qmi_message_pdc_set_selected_config_output_get_result (output, &error)) { + setup_carrier_config_abort (task, error); + goto out; + } + + ctx->timeout_id = g_timeout_add_seconds (SETUP_CARRIER_CONFIG_STEP_TIMEOUT_SECS, + (GSourceFunc) setup_carrier_config_timeout, + task); + ctx->set_selected_config_indication_id = g_signal_connect (ctx->client, + "set-selected-config", + G_CALLBACK (set_selected_config_indication), + task); +out: + if (output) + qmi_message_pdc_set_selected_config_output_unref (output); +} + +static void +find_requested_carrier_config (GTask *task) +{ + SetupCarrierConfigContext *ctx; + Private *priv; + gchar mccmnc[7]; + gchar *group; + + ctx = g_task_get_task_data (task); + priv = get_private (g_task_get_source_object (task)); + + /* Only one group expected per file, so get the start one */ + group = g_key_file_get_start_group (ctx->keyfile); + + /* First, try to match 6 MCCMNC digits (3-digit MNCs) */ + strncpy (mccmnc, ctx->imsi, 6); + mccmnc[6] = '\0'; + ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL); + if (ctx->config_requested) { + mm_dbg ("Requested carrier configuration found for '%s' in group '%s': %s", mccmnc, group, ctx->config_requested); + goto out_config; + } + + /* If not found, try to match 5 MCCMNC digits (2-digit MNCs) */ + mccmnc[5] = '\0'; + ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, mccmnc, NULL); + if (ctx->config_requested) { + mm_dbg ("Requested carrier configuration found for '%s' in group '%s': %s", mccmnc, group, ctx->config_requested); + goto out_config; + } + + /* If not found, try to match the generic configuration */ + ctx->config_requested = g_key_file_get_string (ctx->keyfile, group, GENERIC_CONFIG_FALLBACK, NULL); + if (ctx->config_requested) { + mm_dbg ("Fallback carrier configuration found for '%s' in group '%s'", mccmnc, group); + goto out_config; + } + +out_config: + if (!ctx->config_requested) { + setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "no requested configuration found in group '%s'", group)); + goto out; + } + + /* Now, look for the configuration among the ones available in the device */ + if (priv->config_list) { + guint i; + + for (i = 0; i < priv->config_list->len; i++) { + ConfigInfo *config; + + config = &g_array_index (priv->config_list, ConfigInfo, i); + if (!g_strcmp0 (ctx->config_requested, config->description)) { + mm_dbg ("Requested carrier configuration '%s' is available", ctx->config_requested); + ctx->config_requested_i = i; + } + } + } + + /* Fail operation if we didn't find the one we want */ + if (ctx->config_requested_i < 0) { + setup_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "requested carrier configuration '%s' is not available", + ctx->config_requested)); + } else { + ctx->step++; + setup_carrier_config_step (task); + } +out: + + g_free (group); +} + +static void +setup_carrier_config_step (GTask *task) +{ + SetupCarrierConfigContext *ctx; + Private *priv; + + ctx = g_task_get_task_data (task); + priv = get_private (g_task_get_source_object (task)); + + switch (ctx->step) { + case SETUP_CARRIER_CONFIG_STEP_FIRST: + ctx->step++; + /* fall-through */ + + case SETUP_CARRIER_CONFIG_STEP_FIND_REQUESTED: + find_requested_carrier_config (task); + return; + + case SETUP_CARRIER_CONFIG_STEP_CHECK_CHANGE_NEEDED: + g_assert (ctx->config_requested_i >= 0); + g_assert (priv->config_active_i >= 0); + if (ctx->config_requested_i == priv->config_active_i) { + mm_info ("Carrier config switching not needed: already using '%s'", ctx->config_requested); + ctx->step = SETUP_CARRIER_CONFIG_STEP_LAST; + setup_carrier_config_step (task); + return; + } + + ctx->step++; + /* fall-through */ + + case SETUP_CARRIER_CONFIG_STEP_UPDATE_CURRENT: { + QmiMessagePdcSetSelectedConfigInput *input; + ConfigInfo *requested_config; + ConfigInfo *active_config; + QmiConfigTypeAndId type_and_id; + + requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); + active_config = &g_array_index (priv->config_list, ConfigInfo, priv->config_active_i); + mm_warn ("Carrier config switching needed: '%s' -> '%s'", + active_config->description, requested_config->description); + + type_and_id.config_type = requested_config->config_type;; + type_and_id.id = requested_config->id; + + input = qmi_message_pdc_set_selected_config_input_new (); + qmi_message_pdc_set_selected_config_input_set_type_with_id (input, &type_and_id, NULL); + qmi_message_pdc_set_selected_config_input_set_token (input, ctx->token++, NULL); + qmi_client_pdc_set_selected_config (ctx->client, + input, + 10, + NULL, + (GAsyncReadyCallback)set_selected_config_ready, + task); + qmi_message_pdc_set_selected_config_input_unref (input); + return; + } + + case SETUP_CARRIER_CONFIG_STEP_ACTIVATE_CURRENT: { + QmiMessagePdcActivateConfigInput *input; + ConfigInfo *requested_config; + + requested_config = &g_array_index (priv->config_list, ConfigInfo, ctx->config_requested_i); + + input = qmi_message_pdc_activate_config_input_new (); + qmi_message_pdc_activate_config_input_set_config_type (input, requested_config->config_type, NULL); + qmi_message_pdc_activate_config_input_set_token (input, ctx->token++, NULL); + qmi_client_pdc_activate_config (ctx->client, + input, + 10, + NULL, + (GAsyncReadyCallback) activate_config_ready, + task); + qmi_message_pdc_activate_config_input_unref (input); + return; + } + + case SETUP_CARRIER_CONFIG_STEP_LAST: + g_task_return_boolean (task, TRUE); + g_object_unref (task); + break; + } +} + +void +mm_shared_qmi_setup_carrier_config (MMIfaceModem *self, + const gchar *imsi, + const gchar *carrier_config_mapping, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SetupCarrierConfigContext *ctx; + GTask *task; + QmiClient *client = NULL; + GError *error = NULL; + + g_assert (imsi); + g_assert (carrier_config_mapping); + + task = g_task_new (self, NULL, callback, user_data); + ctx = g_slice_new0 (SetupCarrierConfigContext); + ctx->step = SETUP_CARRIER_CONFIG_STEP_FIRST; + ctx->imsi = g_strdup (imsi); + ctx->keyfile = g_key_file_new (); + ctx->config_requested_i = -1; + g_task_set_task_data (task, ctx, (GDestroyNotify)setup_carrier_config_context_free); + + /* Load mapping keyfile */ + if (!g_key_file_load_from_file (ctx->keyfile, + carrier_config_mapping, + G_KEY_FILE_NONE, + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Load PDC client */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDC, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (!client) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "QMI PDC not supported"); + g_object_unref (task); + return; + } + ctx->client = g_object_ref (client); + + setup_carrier_config_step (task); +} + +/*****************************************************************************/ +/* Load carrier config (Modem interface) */ + +#define LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS 5 + +typedef enum { + LOAD_CARRIER_CONFIG_STEP_FIRST, + LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS, + LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT, + LOAD_CARRIER_CONFIG_STEP_LAST, +} LoadCarrierConfigStep; + +typedef struct { + LoadCarrierConfigStep step; + + QmiClientPdc *client; + + GArray *config_list; + guint configs_loaded; + gint config_active_i; + gchar *config_active; + + guint token; + guint timeout_id; + gulong list_configs_indication_id; + gulong get_selected_config_indication_id; + gulong get_config_info_indication_id; +} LoadCarrierConfigContext; + +/* Allow to cleanup action load right away, without being tied + * to the lifecycle of the GTask */ +static void +load_carrier_config_context_cleanup_action (LoadCarrierConfigContext *ctx) +{ + if (ctx->get_selected_config_indication_id) { + g_signal_handler_disconnect (ctx->client, ctx->get_selected_config_indication_id); + ctx->get_selected_config_indication_id = 0; + } + if (ctx->get_config_info_indication_id) { + g_signal_handler_disconnect (ctx->client, ctx->get_config_info_indication_id); + ctx->get_config_info_indication_id = 0; + } + if (ctx->list_configs_indication_id) { + g_signal_handler_disconnect (ctx->client, ctx->list_configs_indication_id); + ctx->list_configs_indication_id = 0; + } + if (ctx->timeout_id) { + g_source_remove (ctx->timeout_id); + ctx->timeout_id = 0; + } +} + +static void +load_carrier_config_context_free (LoadCarrierConfigContext *ctx) +{ + load_carrier_config_context_cleanup_action (ctx); + + if (ctx->config_list) + g_array_unref (ctx->config_list); + g_free (ctx->config_active); + g_clear_object (&ctx->client); + g_slice_free (LoadCarrierConfigContext, ctx); +} + +gchar * +mm_shared_qmi_load_carrier_config_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (res), error); +} + +static void load_carrier_config_step (GTask *task); + +static void +load_carrier_config_abort (GTask *task, + GError *error) +{ + LoadCarrierConfigContext *ctx; + + ctx = g_task_get_task_data (task); + load_carrier_config_context_cleanup_action (ctx); + g_task_return_error (task, error); + g_object_unref (task); +} + +static gboolean +load_carrier_config_timeout (GTask *task) +{ + LoadCarrierConfigContext *ctx; + + ctx = g_task_get_task_data (task); + g_assert (ctx->timeout_id); + ctx->timeout_id = 0; + + load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, + "Operation timed out")); + + return G_SOURCE_REMOVE; +} + +static void +get_selected_config_indication (QmiClientPdc *client, + QmiIndicationPdcGetSelectedConfigOutput *output, + GTask *task) +{ + LoadCarrierConfigContext *ctx; + GArray *active_id = NULL; + GError *error = NULL; + guint16 error_code = 0; + guint i; + + ctx = g_task_get_task_data (task); + + if (!qmi_indication_pdc_get_selected_config_output_get_indication_result (output, &error_code, &error)) { + load_carrier_config_abort (task, error); + return; + } + + if (error_code != 0 && + error_code != QMI_PROTOCOL_ERROR_NOT_PROVISIONED) { /* No configs active */ + load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't get selected config: %s", + qmi_protocol_error_get_string ((QmiProtocolError) error_code))); + return; + } + + qmi_indication_pdc_get_selected_config_output_get_active_id (output, &active_id, NULL); + if (!active_id) { + load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't get selected config: no active id reported")); + return; + } + + g_assert (ctx->config_list); + g_assert (ctx->config_list->len); + + for (i = 0; i < ctx->config_list->len; i++) { + ConfigInfo *config; + + config = &g_array_index (ctx->config_list, ConfigInfo, i); + if ((config->id->len == active_id->len) && + !memcmp (config->id->data, active_id->data, active_id->len)) { + ctx->config_active_i = i; + ctx->config_active = g_strdup (config->description); + break; + } + } + + if (i == ctx->config_list->len) { + load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't find currently selected config")); + return; + } + + /* Go on */ + load_carrier_config_context_cleanup_action (ctx); + ctx->step++; + load_carrier_config_step (task); +} + +static void +get_selected_config_ready (QmiClientPdc *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdcGetSelectedConfigOutput *output; + LoadCarrierConfigContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_pdc_get_selected_config_finish (client, res, &error); + if (!output || !qmi_message_pdc_get_selected_config_output_get_result (output, &error)) { + load_carrier_config_abort (task, error); + goto out; + } + + ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS, + (GSourceFunc) load_carrier_config_timeout, + task); + ctx->get_selected_config_indication_id = g_signal_connect (ctx->client, + "get-selected-config", + G_CALLBACK (get_selected_config_indication), + task); + +out: + if (output) + qmi_message_pdc_get_selected_config_output_unref (output); +} + +static void +get_config_info_indication (QmiClientPdc *client, + QmiIndicationPdcGetConfigInfoOutput *output, + GTask *task) +{ + LoadCarrierConfigContext *ctx; + GError *error = NULL; + ConfigInfo *current_config = NULL; + guint32 token; + const gchar *description; + int i; + guint16 error_code = 0; + + ctx = g_task_get_task_data (task); + + if (!qmi_indication_pdc_get_config_info_output_get_indication_result (output, &error_code, &error)) { + load_carrier_config_abort (task, error); + return; + } + + if (error_code != 0) { + load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't get config info: %s", + qmi_protocol_error_get_string ((QmiProtocolError) error_code))); + return; + } + + if (!qmi_indication_pdc_get_config_info_output_get_token (output, &token, &error)) { + load_carrier_config_abort (task, error); + return; + } + + /* Look for the current config in the list, match by token */ + for (i = 0; i < ctx->config_list->len; i++) { + current_config = &g_array_index (ctx->config_list, ConfigInfo, i); + if (current_config->token == token) + break; + } + + /* Ignore if not found in the list */ + if (i == ctx->config_list->len) + return; + + /* Ignore if already set */ + if (current_config->description) + return; + + /* Store total size, version and description of the current config */ + if (!qmi_indication_pdc_get_config_info_output_get_total_size (output, ¤t_config->total_size, &error) || + !qmi_indication_pdc_get_config_info_output_get_version (output, ¤t_config->version, &error) || + !qmi_indication_pdc_get_config_info_output_get_description (output, &description, &error)) { + load_carrier_config_abort (task, error); + return; + } + + current_config->description = g_strdup (description); + ctx->configs_loaded++; + + /* If not all loaded, wait for more */ + if (ctx->configs_loaded < ctx->config_list->len) + return; + + /* Go on */ + load_carrier_config_context_cleanup_action (ctx); + ctx->step++; + load_carrier_config_step (task); +} + +static void +list_configs_indication (QmiClientPdc *client, + QmiIndicationPdcListConfigsOutput *output, + GTask *task) +{ + LoadCarrierConfigContext *ctx; + GError *error = NULL; + GArray *configs = NULL; + int i; + guint16 error_code = 0; + + ctx = g_task_get_task_data (task); + + if (!qmi_indication_pdc_list_configs_output_get_indication_result (output, &error_code, &error)) { + load_carrier_config_abort (task, error); + return; + } + + if (error_code != 0) { + load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "couldn't list configs: %s", + qmi_protocol_error_get_string ((QmiProtocolError) error_code))); + return; + } + + if (!qmi_indication_pdc_list_configs_output_get_configs (output, &configs, &error)) { + load_carrier_config_abort (task, error); + return; + } + + if (!configs || !configs->len) { + load_carrier_config_abort (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "no configurations found")); + return; + } + + /* Preallocate config list and request details for each */ + mm_dbg ("found %u carrier configurations...", configs->len); + ctx->config_list = g_array_sized_new (FALSE, TRUE, sizeof (ConfigInfo), configs->len); + g_array_set_size (ctx->config_list, configs->len); + g_array_set_clear_func (ctx->config_list, (GDestroyNotify) config_info_clear); + + ctx->get_config_info_indication_id = g_signal_connect (ctx->client, + "get-config-info", + G_CALLBACK (get_config_info_indication), + task); + + for (i = 0; i < configs->len; i++) { + ConfigInfo *current_info; + QmiIndicationPdcListConfigsOutputConfigsElement *element; + QmiConfigTypeAndId type_with_id; + QmiMessagePdcGetConfigInfoInput *input; + + element = &g_array_index (configs, QmiIndicationPdcListConfigsOutputConfigsElement, i); + + current_info = &g_array_index (ctx->config_list, ConfigInfo, i); + current_info->token = ctx->token++; + current_info->id = g_array_ref (element->id); + current_info->config_type = element->config_type; + + input = qmi_message_pdc_get_config_info_input_new (); + type_with_id.config_type = element->config_type; + type_with_id.id = current_info->id; + qmi_message_pdc_get_config_info_input_set_type_with_id (input, &type_with_id, NULL); + qmi_message_pdc_get_config_info_input_set_token (input, current_info->token, NULL); + qmi_client_pdc_get_config_info (ctx->client, input, 10, NULL, NULL, NULL); /* ignore response! */ + qmi_message_pdc_get_config_info_input_unref (input); + } +} + +static void +list_configs_ready (QmiClientPdc *client, + GAsyncResult *res, + GTask *task) +{ + QmiMessagePdcListConfigsOutput *output; + LoadCarrierConfigContext *ctx; + GError *error = NULL; + + ctx = g_task_get_task_data (task); + + output = qmi_client_pdc_list_configs_finish (client, res, &error); + if (!output || !qmi_message_pdc_list_configs_output_get_result (output, &error)) { + load_carrier_config_abort (task, error); + goto out; + } + + ctx->timeout_id = g_timeout_add_seconds (LOAD_CARRIER_CONFIG_STEP_TIMEOUT_SECS, + (GSourceFunc) load_carrier_config_timeout, + task); + ctx->list_configs_indication_id = g_signal_connect (ctx->client, + "list-configs", + G_CALLBACK (list_configs_indication), + task); +out: + if (output) + qmi_message_pdc_list_configs_output_unref (output); +} + +static void +load_carrier_config_step (GTask *task) +{ + LoadCarrierConfigContext *ctx; + Private *priv; + + ctx = g_task_get_task_data (task); + priv = get_private (g_task_get_source_object (task)); + + switch (ctx->step) { + case LOAD_CARRIER_CONFIG_STEP_FIRST: + ctx->step++; + /* fall-through */ + + case LOAD_CARRIER_CONFIG_STEP_LIST_CONFIGS: { + QmiMessagePdcListConfigsInput *input; + + input = qmi_message_pdc_list_configs_input_new (); + qmi_message_pdc_list_configs_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); + qmi_message_pdc_list_configs_input_set_token (input, ctx->token++, NULL); + qmi_client_pdc_list_configs (ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)list_configs_ready, + task); + qmi_message_pdc_list_configs_input_unref (input); + return; + } + + case LOAD_CARRIER_CONFIG_STEP_QUERY_CURRENT: { + QmiMessagePdcGetSelectedConfigInput *input; + + input = qmi_message_pdc_get_selected_config_input_new (); + qmi_message_pdc_get_selected_config_input_set_config_type (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, NULL); + qmi_message_pdc_get_selected_config_input_set_token (input, ctx->token++, NULL); + qmi_client_pdc_get_selected_config (ctx->client, + input, + 5, + NULL, + (GAsyncReadyCallback)get_selected_config_ready, + task); + qmi_message_pdc_get_selected_config_input_unref (input); + return; + } + + case LOAD_CARRIER_CONFIG_STEP_LAST: + /* We will now store the loaded information so that we can later on use it + * if needed during the automatic carrier config switching operation */ + g_assert (!priv->config_list); + g_assert (priv->config_active_i < 0); + g_assert (ctx->config_active_i >= 0); + priv->config_list = g_array_ref (ctx->config_list); + priv->config_active_i = ctx->config_active_i; + + g_assert (ctx->config_active); + g_task_return_pointer (task, g_strdup (ctx->config_active), g_free); + g_object_unref (task); + break; + } +} + +void +mm_shared_qmi_load_carrier_config (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ + LoadCarrierConfigContext *ctx; + GTask *task; + QmiClient *client = NULL; + + + task = g_task_new (self, NULL, callback, user_data); + ctx = g_slice_new0 (LoadCarrierConfigContext); + ctx->step = LOAD_CARRIER_CONFIG_STEP_FIRST; + ctx->config_active_i = -1; + g_task_set_task_data (task, ctx, (GDestroyNotify)load_carrier_config_context_free); + + /* Load PDC client */ + client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self), + QMI_SERVICE_PDC, + MM_PORT_QMI_FLAG_DEFAULT, + NULL); + if (!client) { + g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "QMI PDC not supported"); + g_object_unref (task); + return; + } + ctx->client = g_object_ref (client); + + load_carrier_config_step (task); +} + +/*****************************************************************************/ /* Location: Set SUPL server */ typedef struct { diff --git a/src/mm-shared-qmi.h b/src/mm-shared-qmi.h index e728e609..43564b15 100644 --- a/src/mm-shared-qmi.h +++ b/src/mm-shared-qmi.h @@ -147,6 +147,20 @@ void mm_shared_qmi_factory_reset (MMIfaceMode gboolean mm_shared_qmi_factory_reset_finish (MMIfaceModem *self, GAsyncResult *res, GError **error); +void mm_shared_qmi_load_carrier_config (MMIfaceModem *self, + GAsyncReadyCallback callback, + gpointer user_data); +gchar *mm_shared_qmi_load_carrier_config_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); +void mm_shared_qmi_setup_carrier_config (MMIfaceModem *self, + const gchar *imsi, + const gchar *carrier_config_mapping, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_shared_qmi_setup_carrier_config_finish (MMIfaceModem *self, + GAsyncResult *res, + GError **error); /* Shared QMI location support */ |