aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2018-12-18 14:53:05 +0100
committerAleksander Morgado <aleksander@aleksander.es>2019-04-02 12:14:03 +0200
commitb4b6dc9e650ddb869a4577f3afa269d2aeb231cc (patch)
treeb1810db2fd972ecac8a43e4042a30f8c5c51cb2f /src
parent94cf7f0ceb8c00a413d279a1fbdb491c01240d81 (diff)
shared-qmi: implement QMI PDC based carrier config support
We implement both loading the current configuration in use, as well as automatically switching to a different one if a mapping file is available.
Diffstat (limited to 'src')
-rw-r--r--src/mm-broadband-modem-mbim.c5
-rw-r--r--src/mm-broadband-modem-qmi.c5
-rw-r--r--src/mm-broadband-modem.c14
-rw-r--r--src/mm-shared-qmi.c918
-rw-r--r--src/mm-shared-qmi.h14
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, &current_config->total_size, &error) ||
+ !qmi_indication_pdc_get_config_info_output_get_version (output, &current_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 */