/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * qmicli -- Command line interface to control QMI devices * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Copyright (C) 2013-2022 Aleksander Morgado */ #include "config.h" #include #include #include #include #include #include #include #include #include "qmicli.h" #include "qmicli-helpers.h" #if defined HAVE_QMI_SERVICE_PDC #define LIST_CONFIGS_TIMEOUT_SECS 2 #define LOAD_CONFIG_CHUNK_SIZE 0x400 /* Info about config */ typedef struct { GArray *id; QmiPdcConfigurationType config_type; guint32 token; guint32 version; gchar *description; guint32 total_size; } ConfigInfo; /* Info about loading config */ typedef struct { GMappedFile *mapped_file; GArray *checksum; gsize offset; } LoadConfigFileData; /* Context */ typedef struct { QmiDevice *device; QmiClientPdc *client; GCancellable *cancellable; gboolean skip_cid_release; /* local data */ guint timeout_id; GArray *config_list; guint configs_loaded; GArray *active_config_id; GArray *pending_config_id; gboolean ids_loaded; guint list_configs_indication_id; guint get_selected_config_indication_id; LoadConfigFileData *load_config_file_data; guint load_config_indication_id; guint get_config_info_indication_id; guint set_selected_config_indication_id; guint activate_config_indication_id; guint device_removed_indication_id; guint deactivate_config_indication_id; guint refresh_indication_id; guint token; } Context; static Context *ctx; /* Options */ static gchar *list_configs_str; static gchar *delete_config_str; static gchar *activate_config_str; static gchar *deactivate_config_str; static gchar *load_config_str; static gboolean monitor_refresh_flag; static gboolean noop_flag; #if defined HAVE_QMI_MESSAGE_PDC_LIST_CONFIGS && \ defined HAVE_QMI_INDICATION_PDC_LIST_CONFIGS && \ defined HAVE_QMI_MESSAGE_PDC_GET_SELECTED_CONFIG && \ defined HAVE_QMI_INDICATION_PDC_GET_SELECTED_CONFIG && \ defined HAVE_QMI_MESSAGE_PDC_GET_CONFIG_INFO && \ defined HAVE_QMI_INDICATION_PDC_GET_CONFIG_INFO # define HAVE_QMI_ACTION_PDC_LIST_CONFIGS #endif #if defined HAVE_QMI_MESSAGE_PDC_ACTIVATE_CONFIG && \ defined HAVE_QMI_INDICATION_PDC_ACTIVATE_CONFIG && \ defined HAVE_QMI_MESSAGE_PDC_SET_SELECTED_CONFIG && \ defined HAVE_QMI_INDICATION_PDC_SET_SELECTED_CONFIG # define HAVE_QMI_ACTION_PDC_ACTIVATE_CONFIG #endif #if defined HAVE_QMI_MESSAGE_PDC_DEACTIVATE_CONFIG && \ defined HAVE_QMI_INDICATION_PDC_DEACTIVATE_CONFIG # define HAVE_QMI_ACTION_PDC_DEACTIVATE_CONFIG #endif #if defined HAVE_QMI_MESSAGE_PDC_LOAD_CONFIG && \ defined HAVE_QMI_INDICATION_PDC_LOAD_CONFIG # define HAVE_QMI_ACTION_PDC_LOAD_CONFIG #endif #if defined HAVE_QMI_INDICATION_PDC_REFRESH && \ defined HAVE_QMI_MESSAGE_PDC_REGISTER # define HAVE_QMI_ACTION_PDC_REFRESH #endif static GOptionEntry entries[] = { #if defined HAVE_QMI_ACTION_PDC_LIST_CONFIGS { "pdc-list-configs", 0, 0, G_OPTION_ARG_STRING, &list_configs_str, "List all configs", "[(platform|software)]" }, #endif #if defined HAVE_QMI_MESSAGE_PDC_DELETE_CONFIG { "pdc-delete-config", 0, 0, G_OPTION_ARG_STRING, &delete_config_str, "Delete config", "[(platform|software),ConfigId]" }, #endif #if defined HAVE_QMI_ACTION_PDC_ACTIVATE_CONFIG { "pdc-activate-config", 0, 0, G_OPTION_ARG_STRING, &activate_config_str, "Activate config", "[(platform|software),ConfigId]" }, #endif #if defined HAVE_QMI_ACTION_PDC_DEACTIVATE_CONFIG { "pdc-deactivate-config", 0, 0, G_OPTION_ARG_STRING, &deactivate_config_str, "Deactivate config", "[(platform|software),ConfigId]" }, #endif #if defined HAVE_QMI_ACTION_PDC_LOAD_CONFIG { "pdc-load-config", 0, 0, G_OPTION_ARG_STRING, &load_config_str, "Load config to device", "[Path to config]" }, #endif #if defined HAVE_QMI_ACTION_PDC_REFRESH { "pdc-monitor-refresh", 0, 0, G_OPTION_ARG_NONE, &monitor_refresh_flag, "Watch for refresh indications", NULL }, #endif { "pdc-noop", 0, 0, G_OPTION_ARG_NONE, &noop_flag, "Just allocate or release a PDC client. Use with `--client-no-release-cid' and/or `--client-cid'", NULL }, { NULL, 0, 0, 0, NULL, NULL, NULL } }; GOptionGroup * qmicli_pdc_get_option_group (void) { GOptionGroup *group; group = g_option_group_new ("pdc", "PDC options:", "Show platform device configurations options", NULL, NULL); g_option_group_add_entries (group, entries); return group; } gboolean qmicli_pdc_options_enabled (void) { static guint n_actions = 0; static gboolean checked = FALSE; if (checked) return !!n_actions; n_actions = (!!list_configs_str + !!delete_config_str + !!activate_config_str + !!deactivate_config_str + !!load_config_str + monitor_refresh_flag + noop_flag); if (n_actions > 1) { g_printerr ("error: too many PDC actions requested\n"); exit (EXIT_FAILURE); } /* Actions that require receiving QMI indication messages must specify that * indications are expected. */ if (list_configs_str || activate_config_str || deactivate_config_str || load_config_str || monitor_refresh_flag) qmicli_expect_indications (); checked = TRUE; return !!n_actions; } static Context * context_new (QmiDevice *device, QmiClientPdc *client, GCancellable *cancellable) { Context *context; context = g_slice_new0 (Context); context->device = g_object_ref (device); context->client = g_object_ref (client); context->cancellable = g_object_ref (cancellable); return context; } static void context_free (Context *context) { guint i; if (!context) return; if (context->config_list) { for (i = 0; i < context->config_list->len; i++) { ConfigInfo *current_config; current_config = &g_array_index (context->config_list, ConfigInfo, i); g_free (current_config->description); if (current_config->id) g_array_unref (current_config->id); } g_array_unref (context->config_list); g_signal_handler_disconnect (context->client, context->list_configs_indication_id); g_signal_handler_disconnect (context->client, context->get_config_info_indication_id); g_signal_handler_disconnect (context->client, context->get_selected_config_indication_id); } if (context->load_config_file_data) { g_array_unref (context->load_config_file_data->checksum); g_mapped_file_unref (context->load_config_file_data->mapped_file); g_slice_free (LoadConfigFileData, context->load_config_file_data); g_signal_handler_disconnect (context->client, context->load_config_indication_id); } if (context->set_selected_config_indication_id) g_signal_handler_disconnect (context->client, context->set_selected_config_indication_id); if (context->activate_config_indication_id) g_signal_handler_disconnect (context->client, context->activate_config_indication_id); if (context->device_removed_indication_id) g_signal_handler_disconnect (context->device, context->device_removed_indication_id); if (context->deactivate_config_indication_id) g_signal_handler_disconnect (context->client, context->deactivate_config_indication_id); g_object_unref (context->cancellable); g_object_unref (context->client); g_object_unref (context->device); g_slice_free (Context, context); } static void operation_shutdown (gboolean operation_status) { /* Cleanup context and finish async operation */ qmicli_async_operation_done (operation_status, ctx->skip_cid_release); context_free (ctx); ctx = NULL; } /******************************************************************************/ /* Common */ #if defined HAVE_QMI_ACTION_PDC_ACTIVATE_CONFIG || \ defined HAVE_QMI_ACTION_PDC_DEACTIVATE_CONFIG || \ defined HAVE_QMI_MESSAGE_PDC_DELETE_CONFIG static gboolean parse_type_and_id (const gchar *str, QmiPdcConfigurationType *config_type, GArray **id) { guint num_parts; g_auto(GStrv) substrings = NULL; substrings = g_strsplit (str, ",", -1); num_parts = g_strv_length (substrings); if (num_parts != 2) { g_printerr ("Expected 2 parameters, but found %u\n", num_parts); return FALSE; } if (!qmicli_read_pdc_configuration_type_from_string (substrings[0], config_type)) { g_printerr ("Incorrect id specified: %s\n", substrings[0]); return FALSE; } if (!qmicli_read_binary_array_from_string (substrings[1], id)) { g_printerr ("Incorrect config type specified: %s\n", substrings[1]); return FALSE; } return TRUE; } #endif /* HAVE_QMI_ACTION_PDC_ACTIVATE_CONFIG * HAVE_QMI_ACTION_PDC_DEACTIVATE_CONFIG * HAVE_QMI_MESSAGE_PDC_DELETE_CONFIG */ /******************************************************************************/ /* List configs and get selected config */ #if defined HAVE_QMI_ACTION_PDC_LIST_CONFIGS static const char * status_string (GArray *id) { if (!id) return "Unknown"; if (ctx->active_config_id && id->len == ctx->active_config_id->len && memcmp (id->data, ctx->active_config_id->data, id->len) == 0) return "Active"; if (ctx->pending_config_id && id->len == ctx->pending_config_id->len && memcmp (id->data, ctx->pending_config_id->data, id->len) == 0) return "Pending"; return "Inactive"; } static void print_configs (GArray *configs) { guint i; g_printf ("Total configurations: %u\n", ctx->config_list->len); for (i = 0; i < ctx->config_list->len; i++) { ConfigInfo *current_config; char *id_str; current_config = &g_array_index (ctx->config_list, ConfigInfo, i); g_printf ("Configuration %u:\n", i + 1); g_printf ("\tDescription: %s\n", current_config->description); g_printf ("\tType: %s\n", qmi_pdc_configuration_type_get_string (current_config->config_type)); g_printf ("\tSize: %u\n", current_config->total_size); g_printf ("\tStatus: %s\n", status_string (current_config->id)); g_printf ("\tVersion: 0x%X\n", current_config->version); id_str = qmicli_get_raw_data_printable (current_config->id, 80, ""); g_printf ("\tID: %s\n", id_str ? id_str : "none"); g_free (id_str); } } static void check_list_config_completed (void) { if (ctx->configs_loaded == ctx->config_list->len && ctx->ids_loaded) { print_configs (ctx->config_list); operation_shutdown (TRUE); } } static void get_config_info_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcGetConfigInfoOutput) output = NULL; output = qmi_client_pdc_get_config_info_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } if (!qmi_message_pdc_get_config_info_output_get_result (output, &error)) { g_printerr ("error: couldn't get config info: %s\n", error->message); operation_shutdown (FALSE); return; } } static void get_config_info_ready_indication (QmiClientPdc *client, QmiIndicationPdcGetConfigInfoOutput *output) { g_autoptr(GError) error = NULL; ConfigInfo *current_config = NULL; guint32 token; const gchar *description; guint i; guint16 error_code = 0; if (!qmi_indication_pdc_get_config_info_output_get_indication_result (output, &error_code, &error)) { g_printerr ("error: couldn't get config info: %s\n", error->message); operation_shutdown (FALSE); return; } if (error_code != 0) { g_printerr ("error: couldn't get config info: %s\n", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); operation_shutdown (FALSE); return; } if (!qmi_indication_pdc_get_config_info_output_get_token (output, &token, &error)) { g_printerr ("error: couldn't get config info token: %s\n", error->message); operation_shutdown (FALSE); return; } /* Look for the current config in the list */ 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; } /* 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)) { g_printerr ("error: couldn't get config info details: %s\n", error->message); operation_shutdown (FALSE); return; } current_config->description = g_strdup (description); ctx->configs_loaded++; check_list_config_completed (); } static gboolean list_configs_timeout (void) { /* No indication yet, cancelling */ if (!ctx->config_list) { g_printf ("Total configurations: 0\n"); operation_shutdown (TRUE); } return FALSE; } static void list_configs_ready_indication (QmiClientPdc *client, QmiIndicationPdcListConfigsOutput *output) { g_autoptr(GError) error = NULL; GArray *configs = NULL; guint i; guint16 error_code = 0; /* Remove timeout as soon as we get the indication */ if (ctx->timeout_id) { g_source_remove (ctx->timeout_id); ctx->timeout_id = 0; } if (!qmi_indication_pdc_list_configs_output_get_indication_result (output, &error_code, &error)) { g_printerr ("error: couldn't list configs: %s\n", error->message); operation_shutdown (FALSE); return; } if (error_code != 0) { g_printerr ("error: couldn't list config: %s\n", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); operation_shutdown (FALSE); return; } if (!qmi_indication_pdc_list_configs_output_get_configs (output, &configs, &error)) { g_printerr ("error: couldn't list configs: %s\n", error->message); operation_shutdown (FALSE); return; } /* Preallocate config list and request details for each */ ctx->config_list = g_array_sized_new (FALSE, TRUE, sizeof (ConfigInfo), configs->len); g_array_set_size (ctx->config_list, configs->len); for (i = 0; i < configs->len; i++) { ConfigInfo *current_info; QmiIndicationPdcListConfigsOutputConfigsElement *element; guint32 token; g_autoptr(QmiMessagePdcGetConfigInfoInput) input = NULL; token = ctx->token++; element = &g_array_index (configs, QmiIndicationPdcListConfigsOutputConfigsElement, i); current_info = &g_array_index (ctx->config_list, ConfigInfo, i); current_info->token = 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 (); /* Add type with id */ if (!qmi_message_pdc_get_config_info_input_set_type_with_id_v2 (input, element->config_type, current_info->id, &error)) { g_printerr ("error: couldn't set type with id: %s\n", error->message); operation_shutdown (FALSE); return; } /* Add token */ if (!qmi_message_pdc_get_config_info_input_set_token (input, token, &error)) { g_printerr ("error: couldn't set token: %s\n", error->message); operation_shutdown (FALSE); return; } qmi_client_pdc_get_config_info (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) get_config_info_ready, NULL); } check_list_config_completed (); } static void list_configs_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcListConfigsOutput) output = NULL; output = qmi_client_pdc_list_configs_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } if (!qmi_message_pdc_list_configs_output_get_result (output, &error)) { g_printerr ("error: couldn't list configs: %s\n", error->message); operation_shutdown (FALSE); return; } } static QmiMessagePdcListConfigsInput * list_configs_input_create (const gchar *str) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcListConfigsInput) input = NULL; QmiPdcConfigurationType config_type; if (!qmicli_read_pdc_configuration_type_from_string (str, &config_type)) return NULL; input = qmi_message_pdc_list_configs_input_new (); if (!qmi_message_pdc_list_configs_input_set_config_type (input, config_type, &error) || !qmi_message_pdc_list_configs_input_set_token (input, ctx->token++, &error)) { g_printerr ("error: couldn't create input data bundle: '%s'\n", error->message); return NULL; } return g_steal_pointer (&input); } static void get_selected_config_ready_indication (QmiClientPdc *client, QmiIndicationPdcGetSelectedConfigOutput *output) { g_autoptr(GError) error = NULL; GArray *pending_id = NULL; GArray *active_id = NULL; guint16 error_code = 0; if (!qmi_indication_pdc_get_selected_config_output_get_indication_result (output, &error_code, &error)) { g_printerr ("error: couldn't get selected config: %s\n", error->message); operation_shutdown (FALSE); return; } if (error_code != 0 && error_code != QMI_PROTOCOL_ERROR_NOT_PROVISIONED) { /* No configs active */ g_printerr ("error: couldn't get selected config: %s\n", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); operation_shutdown (FALSE); return; } qmi_indication_pdc_get_selected_config_output_get_pending_id (output, &pending_id, NULL); qmi_indication_pdc_get_selected_config_output_get_active_id (output, &active_id, NULL); if (active_id) ctx->active_config_id = g_array_ref (active_id); if (pending_id) ctx->pending_config_id = g_array_ref (pending_id); ctx->ids_loaded = TRUE; check_list_config_completed (); } static void get_selected_config_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcGetSelectedConfigOutput) output = NULL; output = qmi_client_pdc_get_selected_config_finish (client, res, &error); if (!qmi_message_pdc_get_selected_config_output_get_result (output, &error)) { g_printerr ("error: couldn't get selected config: %s\n", error->message); operation_shutdown (FALSE); return; } } static QmiMessagePdcGetSelectedConfigInput * get_selected_config_input_create (const gchar *str) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcGetSelectedConfigInput) input = NULL; QmiPdcConfigurationType config_type; if (!qmicli_read_pdc_configuration_type_from_string (str, &config_type)) return NULL; input = qmi_message_pdc_get_selected_config_input_new (); if (!qmi_message_pdc_get_selected_config_input_set_config_type (input, config_type, &error) || !qmi_message_pdc_get_selected_config_input_set_token (input, ctx->token++, &error)) { g_printerr ("error: couldn't create input data bundle: '%s'\n", error->message); return NULL; } return g_steal_pointer (&input); } static void run_list_configs (void) { g_autoptr(QmiMessagePdcListConfigsInput) input = NULL; g_autoptr(QmiMessagePdcGetSelectedConfigInput) get_selected_config_input = NULL; g_debug ("Listing configs asynchronously..."); /* Results are reported via indications */ ctx->list_configs_indication_id = g_signal_connect (ctx->client, "list-configs", G_CALLBACK (list_configs_ready_indication), NULL); ctx->get_selected_config_indication_id = g_signal_connect (ctx->client, "get-selected-config", G_CALLBACK (get_selected_config_ready_indication), NULL); ctx->get_config_info_indication_id = g_signal_connect (ctx->client, "get-config-info", G_CALLBACK (get_config_info_ready_indication), NULL); input = list_configs_input_create (list_configs_str); if (!input) { operation_shutdown (FALSE); return; } get_selected_config_input = get_selected_config_input_create (list_configs_str); if (!get_selected_config_input) { operation_shutdown (FALSE); return; } /* We need a timeout, because there will be no indications if no configs * are loaded */ ctx->timeout_id = g_timeout_add_seconds (LIST_CONFIGS_TIMEOUT_SECS, (GSourceFunc) list_configs_timeout, NULL); qmi_client_pdc_list_configs (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) list_configs_ready, NULL); qmi_client_pdc_get_selected_config (ctx->client, get_selected_config_input, 10, ctx->cancellable, (GAsyncReadyCallback) get_selected_config_ready, NULL); } #endif /* HAVE_QMI_ACTION_PDC_LIST_CONFIGS */ /******************************************************************************/ /* Activate config */ #if defined HAVE_QMI_ACTION_PDC_ACTIVATE_CONFIG static void device_removed_indication (QmiDevice *device) { g_print ("[%s] Successfully requested config activation\n", qmi_device_get_path_display (ctx->device)); /* Device gone, don't attempt to release CIDs */ ctx->skip_cid_release = TRUE; /* If device gets removed during an activate config operation, * it means the operation is successful */ operation_shutdown (TRUE); } static void activate_config_ready_indication (QmiClientPdc *client, QmiIndicationPdcActivateConfigOutput *output) { g_autoptr(GError) error = NULL; guint16 error_code = 0; if (!qmi_indication_pdc_activate_config_output_get_indication_result (output, &error_code, &error)) { g_printerr ("error: couldn't activate config: %s\n", error->message); operation_shutdown (FALSE); return; } if (error_code != 0) { g_printerr ("error: couldn't activate config: %s\n", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); operation_shutdown (FALSE); return; } /* NOTE: config activation is expected to reboot the device, so we may detect the * actual reboot before receiving this indication */ g_print ("[%s] Successfully requested config activation\n", qmi_device_get_path_display (ctx->device)); operation_shutdown (TRUE); } static void activate_config_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcActivateConfigOutput) output = NULL; output = qmi_client_pdc_activate_config_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } if (!qmi_message_pdc_activate_config_output_get_result (output, &error)) { g_printerr ("error: couldn't activate config: %s\n", error->message); operation_shutdown (FALSE); return; } } static QmiMessagePdcActivateConfigInput * activate_config_input_create (const gchar *str) { g_autoptr(QmiMessagePdcActivateConfigInput) input = NULL; g_autoptr(GError) error = NULL; QmiPdcConfigurationType config_type; g_autoptr(GArray) id = NULL; /* Note: id not needed here really */ if (!parse_type_and_id (str, &config_type, &id)) return NULL; input = qmi_message_pdc_activate_config_input_new (); if (!qmi_message_pdc_activate_config_input_set_config_type (input, config_type, &error) || !qmi_message_pdc_activate_config_input_set_token (input, ctx->token++, &error)) { g_printerr ("error: couldn't create input data bundle: '%s'\n", error->message); return NULL; } return g_steal_pointer (&input); } static void set_selected_config_ready_indication (QmiClientPdc *client, QmiIndicationPdcSetSelectedConfigOutput *output) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcActivateConfigInput) input = NULL; guint16 error_code = 0; if (!qmi_indication_pdc_set_selected_config_output_get_indication_result (output, &error_code, &error)) { g_printerr ("error: couldn't set selected config: %s\n", error->message); operation_shutdown (FALSE); return; } if (error_code != 0) { g_printerr ("error: couldn't set selected config: %s\n", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); operation_shutdown (FALSE); return; } input = activate_config_input_create (activate_config_str); if (!input) { operation_shutdown (FALSE); return; } ctx->activate_config_indication_id = g_signal_connect (ctx->client, "activate-config", G_CALLBACK (activate_config_ready_indication), NULL); ctx->device_removed_indication_id = g_signal_connect (ctx->device, QMI_DEVICE_SIGNAL_REMOVED, G_CALLBACK (device_removed_indication), NULL); qmi_client_pdc_activate_config (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) activate_config_ready, NULL); } static void set_selected_config_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcSetSelectedConfigOutput) output = NULL; output = qmi_client_pdc_set_selected_config_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } } static QmiMessagePdcSetSelectedConfigInput * set_selected_config_input_create (const gchar *str) { g_autoptr(QmiMessagePdcSetSelectedConfigInput) input = NULL; g_autoptr(GError) error = NULL; QmiPdcConfigurationType config_type; g_autoptr(GArray) id = NULL; if (!parse_type_and_id (str, &config_type, &id)) return NULL; input = qmi_message_pdc_set_selected_config_input_new (); if (!qmi_message_pdc_set_selected_config_input_set_type_with_id_v2 (input, config_type, id, &error) || !qmi_message_pdc_set_selected_config_input_set_token (input, ctx->token++, &error)) { g_printerr ("error: couldn't create input data bundle: '%s'\n", error->message); return NULL; } return g_steal_pointer (&input); } static void run_activate_config (void) { g_autoptr(QmiMessagePdcSetSelectedConfigInput) input = NULL; g_debug ("Activating config asynchronously..."); input = set_selected_config_input_create (activate_config_str); if (!input) { operation_shutdown (FALSE); return; } /* Results are reported via indications */ ctx->set_selected_config_indication_id = g_signal_connect (ctx->client, "set-selected-config", G_CALLBACK (set_selected_config_ready_indication), NULL); qmi_client_pdc_set_selected_config (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) set_selected_config_ready, NULL); } #endif /* HAVE_QMI_ACTION_PDC_ACTIVATE_CONFIG */ /******************************************************************************/ /* Deactivate config */ #if defined HAVE_QMI_ACTION_PDC_DEACTIVATE_CONFIG static void deactivate_config_ready_indication (QmiClientPdc *client, QmiIndicationPdcDeactivateConfigOutput *output) { g_autoptr(GError) error = NULL; guint16 error_code = 0; if (!qmi_indication_pdc_deactivate_config_output_get_indication_result (output, &error_code, &error)) { g_printerr ("error: couldn't deactivate config: %s\n", error->message); operation_shutdown (FALSE); return; } if (error_code != 0) { g_printerr ("error: couldn't deactivate config: %s\n", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); operation_shutdown (FALSE); return; } g_print ("[%s] Successfully requested config deactivation\n", qmi_device_get_path_display (ctx->device)); operation_shutdown (TRUE); } static void deactivate_config_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcDeactivateConfigOutput) output = NULL; output = qmi_client_pdc_deactivate_config_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } if (!qmi_message_pdc_deactivate_config_output_get_result (output, &error)) { g_printerr ("error: couldn't deactivate config: %s\n", error->message); operation_shutdown (FALSE); return; } } static QmiMessagePdcDeactivateConfigInput * deactivate_config_input_create (const gchar *str) { g_autoptr(QmiMessagePdcDeactivateConfigInput) input = NULL; g_autoptr(GError) error = NULL; QmiPdcConfigurationType config_type; g_autoptr(GArray) id = NULL; /* Note: id not needed here really */ if (!parse_type_and_id (str, &config_type, &id)) return NULL; input = qmi_message_pdc_deactivate_config_input_new (); if (!qmi_message_pdc_deactivate_config_input_set_config_type (input, config_type, &error) || !qmi_message_pdc_deactivate_config_input_set_token (input, ctx->token++, &error)) { g_printerr ("error: couldn't create input data bundle: '%s'\n", error->message); return NULL; } return g_steal_pointer (&input); } static void run_deactivate_config (void) { g_autoptr(QmiMessagePdcDeactivateConfigInput) input = NULL; g_debug ("Deactivating config asynchronously..."); input = deactivate_config_input_create (deactivate_config_str); if (!input) { operation_shutdown (FALSE); return; } /* Results are reported via indications */ ctx->deactivate_config_indication_id = g_signal_connect (ctx->client, "deactivate-config", G_CALLBACK (deactivate_config_ready_indication), NULL); qmi_client_pdc_deactivate_config (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) deactivate_config_ready, NULL); } #endif /* HAVE_QMI_ACTION_PDC_DEACTIVATE_CONFIG */ /******************************************************************************/ /* Delete config */ #if defined HAVE_QMI_MESSAGE_PDC_DELETE_CONFIG static void delete_config_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcDeleteConfigOutput) output = NULL; output = qmi_client_pdc_delete_config_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } if (!qmi_message_pdc_delete_config_output_get_result (output, &error)) { g_printerr ("error: couldn't delete config: %s\n", error->message); operation_shutdown (FALSE); return; } g_print ("[%s] Successfully deleted config\n", qmi_device_get_path_display (ctx->device)); operation_shutdown (TRUE); } static QmiMessagePdcDeleteConfigInput * delete_config_input_create (const gchar *str) { g_autoptr(QmiMessagePdcDeleteConfigInput) input = NULL; g_autoptr(GError) error = NULL; QmiPdcConfigurationType config_type; g_autoptr(GArray) id = NULL; if (!parse_type_and_id (str, &config_type, &id)) return NULL; input = qmi_message_pdc_delete_config_input_new (); if (!qmi_message_pdc_delete_config_input_set_config_type (input, config_type, &error) || !qmi_message_pdc_delete_config_input_set_token (input, ctx->token++, &error) || !qmi_message_pdc_delete_config_input_set_id (input, id, &error)) { g_printerr ("error: couldn't create input data bundle: '%s'\n", error->message); return NULL; } return g_steal_pointer (&input); } static void run_delete_config (void) { g_autoptr(QmiMessagePdcDeleteConfigInput) input = NULL; g_debug ("Deleting config asynchronously..."); input = delete_config_input_create (delete_config_str); if (!input) { operation_shutdown (FALSE); return; } qmi_client_pdc_delete_config (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) delete_config_ready, NULL); } #endif /* HAVE_QMI_MESSAGE_PDC_DELETE_CONFIG */ /******************************************************************************/ /* Load config */ #if defined HAVE_QMI_ACTION_PDC_LOAD_CONFIG static LoadConfigFileData * load_config_file_from_string (const gchar *str) { g_autoptr(GError) error = NULL; g_autoptr(GMappedFile) mapped_file = NULL; LoadConfigFileData *data; guchar *file_contents; GChecksum *checksum; gsize file_size; gsize hash_size; if (!(mapped_file = g_mapped_file_new (str, FALSE, &error))) { g_printerr ("error: couldn't map config file: '%s'\n", error->message); return NULL; } if (!(file_contents = (guchar *) g_mapped_file_get_contents (mapped_file))) { g_printerr ("error: couldn't get file content\n"); return NULL; } /* Get checksum */ file_size = g_mapped_file_get_length (mapped_file); hash_size = g_checksum_type_get_length (G_CHECKSUM_SHA1); checksum = g_checksum_new (G_CHECKSUM_SHA1); g_checksum_update (checksum, file_contents, file_size); g_free (file_contents); data = g_slice_new (LoadConfigFileData); data->mapped_file = g_mapped_file_ref (mapped_file); data->checksum = g_array_sized_new (FALSE, FALSE, sizeof (guint8), hash_size); g_array_set_size (data->checksum, hash_size); data->offset = 0; g_checksum_get_digest (checksum, &g_array_index (data->checksum, guint8, 0), &hash_size); g_checksum_free (checksum); return data; } static QmiMessagePdcLoadConfigInput * load_config_input_create_chunk (LoadConfigFileData *config_file) { g_autoptr(QmiMessagePdcLoadConfigInput) input = NULL; g_autoptr(GError) error = NULL; g_autoptr(GArray) chunk = NULL; gsize full_size; gsize chunk_size; guint8 *file_content; if (!config_file) return NULL; input = qmi_message_pdc_load_config_input_new (); if (!qmi_message_pdc_load_config_input_set_token (input, ctx->token++, &error)) { g_printerr ("error: couldn't set token: '%s'\n", error->message); return NULL; } chunk = g_array_new (FALSE, FALSE, sizeof (guint8)); full_size = g_mapped_file_get_length (config_file->mapped_file); chunk_size = (((config_file->offset + LOAD_CONFIG_CHUNK_SIZE) > full_size) ? (full_size - config_file->offset) : LOAD_CONFIG_CHUNK_SIZE); file_content = (guint8 *) g_mapped_file_get_contents (config_file->mapped_file); g_array_append_vals (chunk, &file_content[config_file->offset], chunk_size); g_print ("Uploaded %" G_GSIZE_FORMAT " of %" G_GSIZE_FORMAT "\n", config_file->offset, full_size); if (!qmi_message_pdc_load_config_input_set_config_chunk (input, QMI_PDC_CONFIGURATION_TYPE_SOFTWARE, config_file->checksum, full_size, chunk, &error)) { g_printerr ("error: couldn't set chunk: '%s'\n", error->message); return NULL; } config_file->offset += chunk_size; return g_steal_pointer (&input); } static void load_config_ready (QmiClientPdc *client, GAsyncResult *res); static void load_config_ready_indication (QmiClientPdc *client, QmiIndicationPdcLoadConfigOutput *output) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcLoadConfigInput) input = NULL; gboolean frame_reset; guint32 remaining_size; guint16 error_code = 0; if (!qmi_indication_pdc_load_config_output_get_indication_result (output, &error_code, &error)) { g_printerr ("error: couldn't load config: %s\n", error->message); operation_shutdown (FALSE); return; } if (error_code != 0) { g_printerr ("error: couldn't load config: %s\n", qmi_protocol_error_get_string ((QmiProtocolError) error_code)); operation_shutdown (FALSE); return; } if (qmi_indication_pdc_load_config_output_get_frame_reset (output, &frame_reset, NULL) && frame_reset) { g_printerr ("error: frame reset requested\n"); operation_shutdown (FALSE); return; } if (!qmi_indication_pdc_load_config_output_get_remaining_size (output, &remaining_size, &error)) { g_printerr ("error: couldn't load config: %s\n", error->message); operation_shutdown (FALSE); return; } if (remaining_size == 0) { g_print ("Finished loading\n"); operation_shutdown (TRUE); return; } g_print ("Loading next chunk (%u bytes remaining)\n", remaining_size); input = load_config_input_create_chunk (ctx->load_config_file_data); if (!input) { g_printerr ("error: couldn't create next chunk\n"); operation_shutdown (FALSE); return; } qmi_client_pdc_load_config (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) load_config_ready, NULL); } static void load_config_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(GError) error = NULL; g_autoptr(QmiMessagePdcLoadConfigOutput) output = NULL; output = qmi_client_pdc_load_config_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } if (!qmi_message_pdc_load_config_output_get_result (output, &error)) { g_printerr ("error: couldn't load config: %s\n", error->message); operation_shutdown (FALSE); return; } } static QmiMessagePdcLoadConfigInput * load_config_input_create (const gchar *str) { LoadConfigFileData *config_file; QmiMessagePdcLoadConfigInput *input; config_file = load_config_file_from_string (str); if (!config_file) return NULL; input = load_config_input_create_chunk (config_file); if (!input) return NULL; ctx->load_config_file_data = config_file; return input; } static void run_load_config (void) { g_autoptr(QmiMessagePdcLoadConfigInput) input = NULL; g_debug ("Loading config asynchronously..."); input = load_config_input_create (load_config_str); if (!input) { operation_shutdown (FALSE); return; } /* Results are reported via indications */ ctx->load_config_indication_id = g_signal_connect (ctx->client, "load-config", G_CALLBACK (load_config_ready_indication), NULL); qmi_client_pdc_load_config (ctx->client, input, 10, ctx->cancellable, (GAsyncReadyCallback) load_config_ready, NULL); } #endif /* HAVE_QMI_ACTION_PDC_LOAD_CONFIG */ /******************************************************************************/ /* Refresh */ #if defined HAVE_QMI_ACTION_PDC_REFRESH #define VALIDATE_UNKNOWN(str) (str ? str : "unknown") static void monitoring_cancelled (GCancellable *cancellable) { operation_shutdown (TRUE); } static void refresh_received (QmiClientPdc *client, QmiIndicationPdcRefreshOutput *output) { g_autoptr(GError) error = NULL; QmiPdcRefreshEventType type; guint subscription_id; guint slot_id; g_print ("[%s] Received refresh indication:\n", qmi_device_get_path_display (ctx->device)); if (!qmi_indication_pdc_refresh_output_get_refresh_event (output, &type, &error)) { g_printerr ("error: refresh event has no type: %s\n", error->message); operation_shutdown (FALSE); return; } g_print (" Received event type %s", VALIDATE_UNKNOWN (qmi_pdc_refresh_event_type_get_string (type))); if (qmi_indication_pdc_refresh_output_get_subscription_id (output, &subscription_id, NULL)) g_print (", subscription ID: %u", subscription_id); if (qmi_indication_pdc_refresh_output_get_slot_id (output, &slot_id, NULL)) g_print (", slot ID: %u", slot_id); g_print ("\n"); } static void register_refresh_ready (QmiClientPdc *client, GAsyncResult *res) { g_autoptr(QmiMessagePdcRegisterOutput) output = NULL; g_autoptr(GError) error = NULL; output = qmi_client_pdc_register_finish (client, res, &error); if (!output) { g_printerr ("error: operation failed: %s\n", error->message); operation_shutdown (FALSE); return; } if (!qmi_message_pdc_register_output_get_result (output, &error)) { g_printerr ("error: could not register for refresh events: %s\n", error->message); operation_shutdown (FALSE); return; } g_debug ("Registered for refresh events..."); ctx->refresh_indication_id = g_signal_connect (ctx->client, "refresh", G_CALLBACK (refresh_received), NULL); /* User can use Ctrl+C to cancel the monitoring at any time */ g_cancellable_connect (ctx->cancellable, G_CALLBACK (monitoring_cancelled), NULL, NULL); } static void register_refresh_events (void) { g_autoptr(QmiMessagePdcRegisterInput) re_input = NULL; re_input = qmi_message_pdc_register_input_new (); qmi_message_pdc_register_input_set_enable_reporting (re_input, TRUE, NULL); qmi_message_pdc_register_input_set_enable_refresh (re_input, TRUE, NULL); qmi_client_pdc_register ( ctx->client, re_input, 10, ctx->cancellable, (GAsyncReadyCallback) register_refresh_ready, NULL); } #endif /* HAVE_QMI_ACTION_PDC_REFRESH */ /******************************************************************************/ /* Common */ static gboolean noop_cb (gpointer unused) { operation_shutdown (TRUE); return FALSE; } void qmicli_pdc_run (QmiDevice *device, QmiClientPdc *client, GCancellable *cancellable) { /* Initialize context */ ctx = context_new (device, client, cancellable); #if defined HAVE_QMI_ACTION_PDC_LIST_CONFIGS if (list_configs_str) { run_list_configs (); return; } #endif #if defined HAVE_QMI_ACTION_PDC_ACTIVATE_CONFIG if (activate_config_str) { run_activate_config (); return; } #endif #if defined HAVE_QMI_ACTION_PDC_DEACTIVATE_CONFIG if (deactivate_config_str) { run_deactivate_config (); return; } #endif #if defined HAVE_QMI_MESSAGE_PDC_DELETE_CONFIG if (delete_config_str) { run_delete_config (); return; } #endif #if defined HAVE_QMI_ACTION_PDC_LOAD_CONFIG if (load_config_str) { run_load_config (); return; } #endif #if defined HAVE_QMI_ACTION_PDC_REFRESH if (monitor_refresh_flag) { register_refresh_events (); return; } #endif /* Just client allocate/release? */ if (noop_flag) { g_idle_add (noop_cb, NULL); return; } g_warn_if_reached (); } #endif /* HAVE_QMI_SERVICE_PDC */