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.
This commit is contained in:
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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",
|
||||
|
@@ -47,6 +47,22 @@ typedef enum {
|
||||
FEATURE_SUPPORTED,
|
||||
} 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;
|
||||
@@ -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);
|
||||
@@ -1933,6 +1956,901 @@ mm_shared_qmi_factory_reset (MMIfaceModem *self,
|
||||
task);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* 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 */
|
||||
|
||||
|
@@ -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 */
|
||||
|
||||
|
Reference in New Issue
Block a user