| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * qmi-firmware-update -- Command line tool to update firmware in 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 <http://www.gnu.org/licenses/>. |
| * |
| * Copyright (C) 2016 Zodiac Inflight Innovations |
| * Copyright (C) 2016-2017 Aleksander Morgado <aleksander@aleksander.es> |
| */ |
| |
| #include <config.h> |
| #include <string.h> |
| |
| #include <gio/gio.h> |
| |
| #include <libqmi-glib.h> |
| |
| #include "qfu-log.h" |
| #include "qfu-image-factory.h" |
| #include "qfu-updater.h" |
| #include "qfu-reseter.h" |
| #include "qfu-utils.h" |
| #include "qfu-helpers.h" |
| #include "qfu-qdl-device.h" |
| #include "qfu-sahara-device.h" |
| #include "qfu-enum-types.h" |
| |
| G_DEFINE_TYPE (QfuUpdater, qfu_updater, G_TYPE_OBJECT) |
| |
| #define CLEAR_LINE "\33[2K\r" |
| |
| typedef enum { |
| UPDATER_TYPE_UNKNOWN, |
| UPDATER_TYPE_GENERIC, |
| UPDATER_TYPE_DOWNLOAD, |
| } UpdaterType; |
| |
| struct _QfuUpdaterPrivate { |
| UpdaterType type; |
| QfuDeviceSelection *device_selection; |
| gchar *firmware_version; |
| gchar *config_version; |
| gchar *carrier; |
| QmiDeviceOpenFlags device_open_flags; |
| gboolean ignore_version_errors; |
| gboolean override_download; |
| guint8 modem_storage_index; |
| gboolean skip_validation; |
| }; |
| |
| /******************************************************************************/ |
| |
| static const gchar *progress[] = { |
| "(*-----)", |
| "(-*----)", |
| "(--*---)", |
| "(---*--)", |
| "(----*-)", |
| "(-----*)", |
| "(----*-)", |
| "(---*--)", |
| "(--*---)", |
| "(-*----)" |
| }; |
| |
| /******************************************************************************/ |
| /* Run */ |
| |
| /* Wait time after the upgrade has been done, before using the cdc-wdm port */ |
| #define WAIT_FOR_BOOT_TIMEOUT_SECS 5 |
| #define WAIT_FOR_BOOT_RETRIES 12 |
| |
| typedef enum { |
| RUN_CONTEXT_STEP_QMI_CLIENT, |
| RUN_CONTEXT_STEP_GET_FIRMWARE_PREFERENCE, |
| RUN_CONTEXT_STEP_SET_FIRMWARE_PREFERENCE, |
| RUN_CONTEXT_STEP_POWER_CYCLE, |
| RUN_CONTEXT_STEP_CLEANUP_QMI_DEVICE, |
| RUN_CONTEXT_STEP_WAIT_FOR_TTY, |
| RUN_CONTEXT_STEP_SELECT_DEVICE, |
| RUN_CONTEXT_STEP_SELECT_IMAGE, |
| RUN_CONTEXT_STEP_DOWNLOAD_IMAGE, |
| RUN_CONTEXT_STEP_CLEANUP_IMAGE, |
| RUN_CONTEXT_STEP_CLEANUP_DEVICE, |
| RUN_CONTEXT_STEP_WAIT_FOR_CDC_WDM, |
| RUN_CONTEXT_STEP_WAIT_FOR_BOOT, |
| RUN_CONTEXT_STEP_QMI_CLIENT_AFTER, |
| RUN_CONTEXT_STEP_CLEANUP_QMI_DEVICE_FULL, |
| RUN_CONTEXT_STEP_LAST |
| } RunContextStep; |
| |
| typedef struct { |
| /* Device selection */ |
| GFile *cdc_wdm_file; |
| GFile *serial_file; |
| |
| /* Context step */ |
| RunContextStep step; |
| |
| /* Old/New info and capabilities */ |
| gchar *revision; |
| gboolean supports_stored_image_management; |
| guint8 max_modem_storage_index; |
| gboolean supports_firmware_preference_management; |
| QmiMessageDmsGetFirmwarePreferenceOutput *firmware_preference; |
| QmiMessageDmsSwiGetCurrentFirmwareOutput *current_firmware; |
| gchar *new_revision; |
| gboolean new_supports_stored_image_management; |
| gboolean new_supports_firmware_preference_management; |
| QmiMessageDmsGetFirmwarePreferenceOutput *new_firmware_preference; |
| QmiMessageDmsSwiGetCurrentFirmwareOutput *new_current_firmware; |
| |
| /* List of pending QfuImages to download, and the current one being |
| * processed. */ |
| GList *pending_images; |
| QfuImage *current_image; |
| |
| /* QMI device and client */ |
| QmiDevice *qmi_device; |
| QmiClientDms *qmi_client; |
| |
| /* Reset configuration */ |
| gboolean boothold_reset; |
| |
| /* Information gathered from the firmware images themselves */ |
| gchar *firmware_version; |
| gchar *config_version; |
| gchar *carrier; |
| |
| /* Waiting for boot */ |
| guint wait_for_boot_seconds_elapsed; |
| guint wait_for_boot_retries; |
| |
| /* Device to use while already in download mode */ |
| QfuQdlDevice *qdl_device; |
| QfuSaharaDevice *sahara_device; |
| } RunContext; |
| |
| static void |
| run_context_free (RunContext *ctx) |
| { |
| if (ctx->current_firmware) |
| qmi_message_dms_swi_get_current_firmware_output_unref (ctx->current_firmware); |
| if (ctx->new_current_firmware) |
| qmi_message_dms_swi_get_current_firmware_output_unref (ctx->new_current_firmware); |
| if (ctx->firmware_preference) |
| qmi_message_dms_get_firmware_preference_output_unref (ctx->firmware_preference); |
| if (ctx->new_firmware_preference) |
| qmi_message_dms_get_firmware_preference_output_unref (ctx->new_firmware_preference); |
| g_free (ctx->new_revision); |
| g_free (ctx->revision); |
| g_free (ctx->firmware_version); |
| g_free (ctx->config_version); |
| g_free (ctx->carrier); |
| |
| if (ctx->qmi_client) { |
| g_assert (ctx->qmi_device); |
| /* This release only happens when cleaning up from an error, |
| * therefore always release CID */ |
| qmi_device_release_client (ctx->qmi_device, |
| QMI_CLIENT (ctx->qmi_client), |
| QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, |
| 10, NULL, NULL, NULL); |
| g_object_unref (ctx->qmi_client); |
| } |
| if (ctx->qmi_device) { |
| qmi_device_close_async (ctx->qmi_device, 10, NULL, NULL, NULL); |
| g_object_unref (ctx->qmi_device); |
| } |
| if (ctx->cdc_wdm_file) |
| g_object_unref (ctx->cdc_wdm_file); |
| |
| g_clear_object (&ctx->qdl_device); |
| g_clear_object (&ctx->sahara_device); |
| g_clear_object (&ctx->serial_file); |
| g_clear_object (&ctx->current_image); |
| g_list_free_full (ctx->pending_images, g_object_unref); |
| g_slice_free (RunContext, ctx); |
| } |
| |
| gboolean |
| qfu_updater_run_finish (QfuUpdater *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| print_firmware_preference (QmiMessageDmsGetFirmwarePreferenceOutput *firmware_preference, |
| const gchar *prefix) |
| { |
| GArray *array; |
| guint i; |
| |
| qmi_message_dms_get_firmware_preference_output_get_list (firmware_preference, &array, NULL); |
| |
| if (array->len > 0) { |
| for (i = 0; i < array->len; i++) { |
| QmiMessageDmsGetFirmwarePreferenceOutputListImage *image; |
| gchar *unique_id_str; |
| |
| image = &g_array_index (array, QmiMessageDmsGetFirmwarePreferenceOutputListImage, i); |
| unique_id_str = qfu_utils_get_firmware_image_unique_id_printable (image->unique_id); |
| g_print ("%simage '%s': unique id '%s', build id '%s'\n", |
| prefix, |
| qmi_dms_firmware_image_type_get_string (image->type), |
| unique_id_str, |
| image->build_id); |
| g_free (unique_id_str); |
| } |
| } else |
| g_debug ("%sno preference specified\n", prefix); |
| } |
| |
| static void |
| print_current_firmware (QmiMessageDmsSwiGetCurrentFirmwareOutput *current_firmware, |
| const gchar *prefix) |
| { |
| const gchar *model = NULL; |
| const gchar *boot_version = NULL; |
| const gchar *amss_version = NULL; |
| const gchar *sku_id = NULL; |
| const gchar *package_id = NULL; |
| const gchar *carrier_id = NULL; |
| const gchar *pri_version = NULL; |
| const gchar *carrier = NULL; |
| const gchar *config_version = NULL; |
| |
| qmi_message_dms_swi_get_current_firmware_output_get_model (current_firmware, &model, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_boot_version (current_firmware, &boot_version, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_amss_version (current_firmware, &amss_version, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_sku_id (current_firmware, &sku_id, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_package_id (current_firmware, &package_id, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_carrier_id (current_firmware, &carrier_id, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_pri_version (current_firmware, &pri_version, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_carrier (current_firmware, &carrier, NULL); |
| qmi_message_dms_swi_get_current_firmware_output_get_config_version (current_firmware, &config_version, NULL); |
| |
| if (model) |
| g_print ("%sModel: %s\n", prefix, model); |
| if (boot_version) |
| g_print ("%sBoot version: %s\n", prefix, boot_version); |
| if (amss_version) |
| g_print ("%sAMSS version: %s\n", prefix, amss_version); |
| if (sku_id) |
| g_print ("%sSKU ID: %s\n", prefix, sku_id); |
| if (package_id) |
| g_print ("%sPackage ID: %s\n", prefix, package_id); |
| if (carrier_id) |
| g_print ("%sCarrier ID: %s\n", prefix, carrier_id); |
| if (config_version) |
| g_print ("%sConfig version: %s\n", prefix, config_version); |
| } |
| |
| static void |
| run_context_step_last (GTask *task) |
| { |
| RunContext *ctx; |
| QfuUpdater *self; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| g_assert (ctx); |
| |
| self = g_task_get_source_object (task); |
| |
| if (self->priv->type == UPDATER_TYPE_DOWNLOAD) { |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (self->priv->type == UPDATER_TYPE_GENERIC) { |
| g_print ("\n" |
| "------------------------------------------------------------------------\n"); |
| |
| g_print ("\n" |
| " original firmware revision was:\n" |
| " %s\n", ctx->revision ? ctx->revision : "unknown"); |
| if (ctx->current_firmware) { |
| g_print (" original running firmware details:\n"); |
| print_current_firmware (ctx->current_firmware, " "); |
| } |
| if (ctx->firmware_preference) { |
| g_print (" original firmware preference details:\n"); |
| print_firmware_preference (ctx->firmware_preference, " "); |
| } |
| |
| g_print ("\n" |
| " new firmware revision is:\n" |
| " %s\n", ctx->new_revision ? ctx->new_revision : "unknown"); |
| if (ctx->new_current_firmware) { |
| g_print (" new running firmware details:\n"); |
| print_current_firmware (ctx->new_current_firmware, " "); |
| } |
| if (ctx->new_firmware_preference) { |
| g_print (" new firmware preference details:\n"); |
| print_firmware_preference (ctx->new_firmware_preference, " "); |
| } |
| |
| if (ctx->new_supports_stored_image_management) |
| g_print ("\n" |
| " NOTE: this device supports stored image management\n" |
| " with qmicli operations:\n" |
| " --dms-list-stored-images\n" |
| " --dms-select-stored-image\n" |
| " --dms-delete-stored-image\n"); |
| |
| if (ctx->new_supports_firmware_preference_management) |
| g_print ("\n" |
| " NOTE: this device supports firmware preference management\n" |
| " with qmicli operations:\n" |
| " --dms-get-firmware-preference\n" |
| " --dms-set-firmware-preference\n"); |
| |
| g_print ("\n" |
| "------------------------------------------------------------------------\n" |
| "\n"); |
| |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| static void run_context_step (GTask *task); |
| |
| static gboolean |
| run_context_step_cb (GTask *task) |
| { |
| run_context_step (task); |
| return FALSE; |
| } |
| |
| static void |
| run_context_step_next (GTask *task, RunContextStep next) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| ctx->step = next; |
| |
| /* Schedule next step in an idle */ |
| g_idle_add ((GSourceFunc) run_context_step_cb, task); |
| } |
| |
| static void |
| run_context_step_next_no_idle (GTask *task, RunContextStep next) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| ctx->step = next; |
| |
| /* Run right away */ |
| run_context_step (task); |
| } |
| |
| static void |
| close_ready (QmiDevice *dev, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| RunContext *ctx; |
| GError *error = NULL; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| if (!qmi_device_close_finish (dev, res, &error)) { |
| g_warning ("[qfu-updater] couldn't close device: %s", error->message); |
| g_error_free (error); |
| } else |
| g_debug ("[qfu-updater] closed"); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| release_client_ready (QmiDevice *dev, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| RunContext *ctx; |
| GError *error = NULL; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| if (!qmi_device_release_client_finish (dev, res, &error)) { |
| g_warning ("[qfu-updater] couldn't release client: %s", error->message); |
| g_error_free (error); |
| } else |
| g_debug ("[qfu-updater] client released"); |
| |
| qmi_device_close_async (ctx->qmi_device, |
| 10, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) close_ready, |
| task); |
| g_clear_object (&ctx->qmi_device); |
| } |
| |
| static void |
| run_context_step_cleanup_qmi_device_full (GTask *task) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| qmi_device_release_client (ctx->qmi_device, |
| QMI_CLIENT (ctx->qmi_client), |
| QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID, |
| 10, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) release_client_ready, |
| task); |
| g_clear_object (&ctx->qmi_client); |
| } |
| |
| static void |
| new_client_dms_after_ready (gpointer unused, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| RunContext *ctx; |
| GError *error = NULL; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| g_assert (!ctx->qmi_device); |
| g_assert (!ctx->qmi_client); |
| |
| /* After the upgrade, non-fatal error if DMS client couldn't be created */ |
| if (!qfu_utils_new_client_dms_finish (res, |
| &ctx->qmi_device, |
| &ctx->qmi_client, |
| &ctx->new_revision, |
| &ctx->new_supports_stored_image_management, |
| NULL, /* we don't care about the max number of images */ |
| &ctx->new_supports_firmware_preference_management, |
| &ctx->new_firmware_preference, |
| &ctx->new_current_firmware, |
| &error)) { |
| if (ctx->wait_for_boot_retries == WAIT_FOR_BOOT_RETRIES) { |
| g_warning ("couldn't create DMS client after upgrade: %s", error->message); |
| run_context_step_next (task, ctx->step + 1); |
| } else { |
| g_debug ("couldn't create DMS client after upgrade: %s (will retry)", error->message); |
| run_context_step_next (task, RUN_CONTEXT_STEP_WAIT_FOR_BOOT); |
| } |
| g_error_free (error); |
| return; |
| } |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_qmi_client_after (GTask *task) |
| { |
| RunContext *ctx; |
| QfuUpdater *self; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| ctx->wait_for_boot_retries++; |
| g_print ("loading device information after the update (%u/%u)...\n", |
| ctx->wait_for_boot_retries, WAIT_FOR_BOOT_RETRIES); |
| |
| g_debug ("[qfu-updater] creating QMI DMS client after upgrade..."); |
| g_assert (ctx->cdc_wdm_file); |
| qfu_utils_new_client_dms (ctx->cdc_wdm_file, |
| 1, /* single try to allocate DMS client */ |
| self->priv->device_open_flags, |
| TRUE, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) new_client_dms_after_ready, |
| task); |
| } |
| |
| static gboolean |
| wait_for_boot_ready (GTask *task) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| ctx->wait_for_boot_seconds_elapsed++; |
| |
| if (ctx->wait_for_boot_seconds_elapsed < WAIT_FOR_BOOT_TIMEOUT_SECS) { |
| if (!qfu_log_get_verbose_stdout ()) |
| g_print (CLEAR_LINE "%s %u", |
| progress[ctx->wait_for_boot_seconds_elapsed % G_N_ELEMENTS (progress)], |
| WAIT_FOR_BOOT_TIMEOUT_SECS - ctx->wait_for_boot_seconds_elapsed); |
| return G_SOURCE_CONTINUE; |
| } |
| |
| if (!qfu_log_get_verbose_stdout ()) |
| g_print (CLEAR_LINE); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| run_context_step_wait_for_boot (GTask *task) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| ctx->wait_for_boot_seconds_elapsed = 0; |
| |
| g_debug ("[qfu-updater] waiting some time (%us) before accessing the cdc-wdm device...", |
| WAIT_FOR_BOOT_TIMEOUT_SECS); |
| |
| if (!qfu_log_get_verbose_stdout ()) { |
| g_print ("waiting some time for the device to boot...\n"); |
| g_print ("%s %u", progress[0], WAIT_FOR_BOOT_TIMEOUT_SECS); |
| } |
| |
| g_timeout_add_seconds (1, (GSourceFunc) wait_for_boot_ready, task); |
| } |
| |
| static void |
| wait_for_cdc_wdm_ready (QfuDeviceSelection *device_selection, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| RunContext *ctx; |
| QfuUpdater *self; |
| gchar *path; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| g_assert (!ctx->cdc_wdm_file); |
| ctx->cdc_wdm_file = qfu_device_selection_wait_for_cdc_wdm_finish (device_selection, res, &error); |
| if (!ctx->cdc_wdm_file) { |
| g_prefix_error (&error, "error waiting for cdc-wdm: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| path = g_file_get_path (ctx->cdc_wdm_file); |
| g_debug ("[qfu-updater] cdc-wdm device found: %s", path); |
| g_free (path); |
| |
| g_print ("normal mode detected\n"); |
| |
| /* If no need to validate, we're done */ |
| if (self->priv->skip_validation) { |
| run_context_step_next (task, RUN_CONTEXT_STEP_LAST); |
| return; |
| } |
| |
| g_print ("\n" |
| "------------------------------------------------------------------------\n" |
| " NOTE: in order to validate which is the firmware running in the\n" |
| " module, the program will wait for a complete boot; this process\n" |
| " may take some time and several retries.\n" |
| "------------------------------------------------------------------------\n" |
| "\n"); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_wait_for_cdc_wdm (GTask *task) |
| { |
| QfuUpdater *self; |
| |
| self = g_task_get_source_object (task); |
| |
| g_debug ("[qfu-updater] now waiting for cdc-wdm device..."); |
| |
| qfu_device_selection_wait_for_cdc_wdm (self->priv->device_selection, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) wait_for_cdc_wdm_ready, |
| task); |
| } |
| |
| static void |
| run_context_step_cleanup_device (GTask *task) |
| { |
| RunContext *ctx; |
| QfuUpdater *self; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| g_assert (ctx->serial_file); |
| |
| if (ctx->qdl_device) { |
| g_debug ("[qfu-updater] QDL reset"); |
| qfu_qdl_device_reset (ctx->qdl_device, g_task_get_cancellable (task), NULL); |
| g_clear_object (&ctx->qdl_device); |
| } else if (ctx->sahara_device) { |
| g_debug ("[qfu-updater] firehose reset"); |
| qfu_sahara_device_firehose_reset (ctx->sahara_device, g_task_get_cancellable (task), NULL); |
| g_clear_object (&ctx->sahara_device); |
| } else |
| g_assert_not_reached (); |
| |
| g_clear_object (&ctx->serial_file); |
| |
| g_print ("rebooting in normal mode...\n"); |
| |
| /* If we were running in download mode, we don't even wait for the reboot to finish */ |
| if (self->priv->type == UPDATER_TYPE_DOWNLOAD) { |
| run_context_step_next (task, RUN_CONTEXT_STEP_LAST); |
| return; |
| } |
| |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_cleanup_image (GTask *task) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| g_assert (ctx->current_image); |
| g_clear_object (&ctx->current_image); |
| |
| /* Select next image */ |
| if (ctx->pending_images) { |
| run_context_step_next (task, RUN_CONTEXT_STEP_SELECT_IMAGE); |
| return; |
| } |
| |
| /* If no more files to download, we're done! */ |
| g_debug ("[qfu-updater] no more files to download"); |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static gboolean |
| download_image_firehose (QfuSaharaDevice *device, |
| QfuImage *image, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| guint sequence; |
| guint n_blocks; |
| |
| if (!qfu_sahara_device_firehose_setup_download (device, image, &n_blocks, cancellable, error)) { |
| g_prefix_error (error, "couldn't prepare download: "); |
| return FALSE; |
| } |
| |
| for (sequence = 0; sequence < n_blocks; sequence++) { |
| if (!qfu_log_get_verbose_stdout ()) { |
| if (n_blocks > 1) { |
| g_print (CLEAR_LINE "%s %04.1lf%%", |
| progress[sequence % G_N_ELEMENTS (progress)], |
| 100.0 * ((gdouble) sequence / (gdouble) (n_blocks - 1))); |
| } |
| } |
| if (!qfu_sahara_device_firehose_write_block (device, image, sequence, cancellable, error)) { |
| g_prefix_error (error, "couldn't write in session: "); |
| return FALSE; |
| } |
| } |
| |
| g_debug ("[qfu-updater] all blocks downloaded"); |
| |
| if (!qfu_log_get_verbose_stdout ()) |
| g_print (CLEAR_LINE "finalizing download... (may take several minutes, be patient)\n"); |
| |
| if (!qfu_sahara_device_firehose_teardown_download (device, image, cancellable, error)) { |
| g_prefix_error (error, "couldn't teardown download: "); |
| return FALSE; |
| } |
| |
| if (!qfu_log_get_verbose_stdout ()) |
| g_print (CLEAR_LINE); |
| |
| g_debug ("[qfu-updater] sahara/firehose download finished"); |
| return TRUE; |
| } |
| |
| static gboolean |
| download_image_qdl (QfuQdlDevice *device, |
| QfuImage *image, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| guint16 sequence; |
| guint16 n_chunks; |
| |
| if (!qfu_qdl_device_hello (device, cancellable, error)) { |
| g_prefix_error (error, "couldn't send greetings to device: "); |
| return FALSE; |
| } |
| |
| if (!qfu_qdl_device_ufopen (device, image, cancellable, error)) { |
| g_prefix_error (error, "couldn't open session: "); |
| return FALSE; |
| } |
| |
| n_chunks = qfu_image_get_n_data_chunks (image); |
| for (sequence = 0; sequence < n_chunks; sequence++) { |
| if (!qfu_log_get_verbose_stdout ()) { |
| /* Use n-1 chunks for progress reporting; because the last one will take |
| * a lot longer. */ |
| if (n_chunks > 1 && sequence < (n_chunks - 1)) |
| g_print (CLEAR_LINE "%s %04.1lf%%", |
| progress[sequence % G_N_ELEMENTS (progress)], |
| 100.0 * ((gdouble) sequence / (gdouble) (n_chunks - 1))); |
| else if (sequence == (n_chunks - 1)) |
| g_print (CLEAR_LINE "finalizing download... (may take more than one minute, be patient)\n"); |
| } |
| if (!qfu_qdl_device_ufwrite (device, image, sequence, cancellable, error)) { |
| g_prefix_error (error, "couldn't write in session: "); |
| return FALSE; |
| } |
| } |
| |
| g_debug ("[qfu-updater] all chunks ack-ed"); |
| |
| if (!qfu_log_get_verbose_stdout ()) |
| g_print (CLEAR_LINE); |
| |
| if (!qfu_qdl_device_ufclose (device, cancellable, error)) { |
| g_prefix_error (error, "couldn't close session: "); |
| return FALSE; |
| } |
| |
| g_debug ("[qfu-updater] qdl/sdp download finished"); |
| return TRUE; |
| } |
| |
| static void |
| run_context_step_download_image (GTask *task) |
| { |
| RunContext *ctx; |
| GError *error = NULL; |
| GCancellable *cancellable; |
| GTimer *timer; |
| gdouble elapsed; |
| gchar *aux; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| cancellable = g_task_get_cancellable (task); |
| |
| timer = g_timer_new (); |
| |
| aux = g_format_size ((guint64) qfu_image_get_size (ctx->current_image)); |
| g_print ("downloading %s image: %s (%s)...\n", |
| qfu_image_type_get_string (qfu_image_get_image_type (ctx->current_image)), |
| qfu_image_get_display_name (ctx->current_image), |
| aux); |
| g_free (aux); |
| |
| /* QDL/SDP based download */ |
| if (ctx->qdl_device) |
| download_image_qdl (ctx->qdl_device, |
| ctx->current_image, |
| cancellable, |
| &error); |
| /* Sahara based download */ |
| else if (ctx->sahara_device) |
| download_image_firehose (ctx->sahara_device, |
| ctx->current_image, |
| cancellable, |
| &error); |
| else |
| g_assert_not_reached (); |
| |
| elapsed = g_timer_elapsed (timer, NULL); |
| |
| g_timer_destroy (timer); |
| |
| if (error) { |
| g_prefix_error (&error, "error downloading image: "); |
| g_task_return_error (task, error); |
| return; |
| } |
| |
| aux = g_format_size ((guint64) ((qfu_image_get_size (ctx->current_image)) / elapsed)); |
| g_print ("successfully downloaded in %.2lfs (%s/s)\n", elapsed, aux); |
| g_free (aux); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_select_image (GTask *task) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| g_assert (!ctx->current_image); |
| g_assert (ctx->pending_images); |
| |
| /* Select new current image */ |
| ctx->current_image = QFU_IMAGE (ctx->pending_images->data); |
| ctx->pending_images = g_list_delete_link (ctx->pending_images, ctx->pending_images); |
| |
| g_debug ("[qfu-updater] selected file '%s' (%" G_GOFFSET_FORMAT " bytes)", |
| qfu_image_get_display_name (ctx->current_image), |
| qfu_image_get_size (ctx->current_image)); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_select_device (GTask *task) |
| { |
| RunContext *ctx; |
| GError *error = NULL; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| g_assert (ctx->serial_file); |
| g_assert (!ctx->qdl_device); |
| g_assert (!ctx->sahara_device); |
| |
| /* Check if we can setup a Sahara device. Always check this first, because |
| * the sahara stack is very sensitive to any kind of data sent to the port. */ |
| ctx->sahara_device = qfu_sahara_device_new (ctx->serial_file, g_task_get_cancellable (task), &error); |
| if (!ctx->sahara_device) { |
| g_debug ("[qfu-updater] sahara device creation failed: %s", error->message); |
| g_clear_error (&error); |
| |
| /* Check if we can setup a QDL device */ |
| ctx->qdl_device = qfu_qdl_device_new (ctx->serial_file, g_task_get_cancellable (task), &error); |
| if (!ctx->qdl_device) { |
| g_debug ("[qfu-updater] qdl device creation failed: %s", error->message); |
| g_clear_error (&error); |
| } |
| } |
| |
| if (!ctx->qdl_device && !ctx->sahara_device) { |
| g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "unsupported download protocol"); |
| g_object_unref (task); |
| return; |
| } |
| |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| wait_for_tty_ready (QfuDeviceSelection *device_selection, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| RunContext *ctx; |
| gchar *path; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| g_assert (!ctx->serial_file); |
| ctx->serial_file = qfu_device_selection_wait_for_tty_finish (device_selection, res, &error); |
| if (!ctx->serial_file) { |
| g_prefix_error (&error, "error waiting for serial device in download mode: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| path = g_file_get_path (ctx->serial_file); |
| g_debug ("[qfu-updater] serial device in download mode found: %s", path); |
| g_free (path); |
| |
| g_print ("download mode detected\n"); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_wait_for_tty (GTask *task) |
| { |
| QfuUpdater *self; |
| |
| self = g_task_get_source_object (task); |
| |
| g_print ("rebooting in download mode...\n"); |
| |
| g_debug ("[qfu-updater] reset requested, now waiting for serial device..."); |
| qfu_device_selection_wait_for_tty (self->priv->device_selection, |
| QFU_HELPERS_DEVICE_MODE_DOWNLOAD, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) wait_for_tty_ready, |
| task); |
| } |
| |
| static void |
| run_context_step_cleanup_qmi_device (GTask *task) |
| { |
| RunContext *ctx; |
| QmiDevice *tmp; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| g_debug ("[qfu-updater] cleaning up QMI device..."); |
| |
| /* We don't release CID as we're going to reset anyway */ |
| qmi_device_release_client (ctx->qmi_device, |
| QMI_CLIENT (ctx->qmi_client), |
| QMI_DEVICE_RELEASE_CLIENT_FLAGS_NONE, |
| 10, NULL, NULL, NULL); |
| g_clear_object (&ctx->qmi_client); |
| |
| /* We want to close and unref the QmiDevice only AFTER having set the wait |
| * tasks for cdc-wdm or tty devices. This is because the close operation may |
| * take a long time if doing QMI over MBIM (as the MBIM close async |
| * operation is run internally). If we don't do this in this sequence, we |
| * may end up getting udev events reported before the wait have started. */ |
| tmp = g_object_ref (ctx->qmi_device); |
| |
| g_clear_object (&ctx->qmi_device); |
| g_clear_object (&ctx->cdc_wdm_file); |
| |
| /* If nothing to download we don't need to wait for QDL download mode */ |
| if (!ctx->pending_images) |
| run_context_step_next_no_idle (task, RUN_CONTEXT_STEP_WAIT_FOR_CDC_WDM); |
| else |
| /* Go on */ |
| run_context_step_next_no_idle (task, ctx->step + 1); |
| |
| /* After the wait operation has been started, we do run the close */ |
| qmi_device_close_async (tmp, 10, NULL, NULL, NULL); |
| g_object_unref (tmp); |
| } |
| |
| static void |
| reseter_run_ready (QfuReseter *reseter, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| if (!qfu_reseter_run_finish (reseter, res, &error)) { |
| g_prefix_error (&error, "boothold reseter operation failed: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| g_debug ("[qfu-updater] boothold reset requested successfully..."); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| power_cycle_ready (QmiClientDms *qmi_client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| if (!qfu_utils_power_cycle_finish (qmi_client, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| g_debug ("[qfu-updater] reset requested successfully..."); |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_power_cycle (GTask *task) |
| { |
| RunContext *ctx; |
| QfuUpdater *self; |
| QfuReseter *reseter; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| g_debug ("[qfu-updater] power cycling..."); |
| if (!ctx->boothold_reset) { |
| qfu_utils_power_cycle (ctx->qmi_client, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) power_cycle_ready, |
| task); |
| return; |
| } |
| |
| /* Boothold is required when firmware preference isn't supported; and if so, |
| * there must always be images to download. The only case in which we don't |
| * have images listed for download is when we're told that there is nothing |
| * to download via firmware preference. */ |
| g_assert (ctx->pending_images != NULL); |
| |
| /* Boothold reset */ |
| reseter = qfu_reseter_new (self->priv->device_selection, ctx->qmi_client, self->priv->device_open_flags); |
| qfu_reseter_run (reseter, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) reseter_run_ready, |
| task); |
| g_object_unref (reseter); |
| } |
| |
| static void |
| set_firmware_preference_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| RunContext *ctx; |
| QmiMessageDmsSetFirmwarePreferenceOutput *output; |
| GError *error = NULL; |
| GArray *array = NULL; |
| guint next_step; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| output = qmi_client_dms_set_firmware_preference_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed, couldn't set firmware preference: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!qmi_message_dms_set_firmware_preference_output_get_result (output, &error)) { |
| g_prefix_error (&error, "couldn't set firmware preference: "); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| qmi_message_dms_set_firmware_preference_output_unref (output); |
| return; |
| } |
| |
| /* We'll go to next step, unless told to finish early */ |
| next_step = ctx->step + 1; |
| |
| /* list images we need to download? */ |
| if (qmi_message_dms_set_firmware_preference_output_get_image_download_list (output, &array, &error)) { |
| if (!array->len) { |
| g_print ("device already contains the given firmware/config version: no download needed\n"); |
| g_print ("forcing the download may be requested with the --override-download option\n"); |
| g_print ("now power cycling to apply the new firmware preference...\n"); |
| g_list_free_full (ctx->pending_images, g_object_unref); |
| ctx->pending_images = NULL; |
| } else { |
| GString *images = NULL; |
| QmiDmsFirmwareImageType type; |
| guint i; |
| |
| images = g_string_new (""); |
| for (i = 0; i < array->len; i++) { |
| type = g_array_index (array, QmiDmsFirmwareImageType, i); |
| g_string_append (images, qmi_dms_firmware_image_type_get_string (type)); |
| if (i < array->len -1) |
| g_string_append (images, ", "); |
| } |
| |
| g_debug ("[qfu-updater] need to download the following images: %s", images->str); |
| g_string_free (images, TRUE); |
| } |
| } |
| |
| qmi_message_dms_set_firmware_preference_output_unref (output); |
| |
| /* Go on */ |
| run_context_step_next (task, next_step); |
| } |
| |
| static void |
| run_context_step_set_firmware_preference (GTask *task) |
| { |
| QfuUpdater *self; |
| RunContext *ctx; |
| QmiMessageDmsSetFirmwarePreferenceInput *input; |
| GArray *array; |
| QmiMessageDmsSetFirmwarePreferenceInputListImage modem_image_id; |
| QmiMessageDmsSetFirmwarePreferenceInputListImage pri_image_id; |
| const gchar *firmware_version; |
| const gchar *config_version; |
| const gchar *carrier; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| firmware_version = self->priv->firmware_version ? self->priv->firmware_version : ctx->firmware_version; |
| config_version = self->priv->config_version ? self->priv->config_version : ctx->config_version; |
| carrier = self->priv->carrier ? self->priv->carrier : ctx->carrier; |
| |
| g_assert (firmware_version); |
| g_assert (config_version); |
| g_assert (carrier); |
| |
| g_print ("setting firmware preference:\n"); |
| g_print (" firmware version: '%s'\n", firmware_version); |
| g_print (" config version: '%s'\n", config_version); |
| g_print (" carrier: '%s'\n", carrier); |
| |
| /* Set modem image info */ |
| modem_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM; |
| modem_image_id.unique_id = g_array_sized_new (FALSE, TRUE, sizeof (gchar), 16); |
| g_array_insert_vals (modem_image_id.unique_id, 0, "?_?", 3); |
| g_array_set_size (modem_image_id.unique_id, 16); |
| modem_image_id.build_id = g_strdup_printf ("%s_?", firmware_version); |
| |
| /* Set pri image info */ |
| pri_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI; |
| pri_image_id.unique_id = g_array_sized_new (FALSE, TRUE, sizeof (gchar), 16); |
| g_array_insert_vals (pri_image_id.unique_id, 0, config_version, strlen (config_version)); |
| g_array_set_size (pri_image_id.unique_id, 16); |
| pri_image_id.build_id = g_strdup_printf ("%s_%s", firmware_version, carrier); |
| |
| array = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageDmsSetFirmwarePreferenceInputListImage), 2); |
| g_array_append_val (array, modem_image_id); |
| g_array_append_val (array, pri_image_id); |
| |
| input = qmi_message_dms_set_firmware_preference_input_new (); |
| qmi_message_dms_set_firmware_preference_input_set_list (input, array, NULL); |
| g_array_unref (array); |
| |
| if (self->priv->override_download) |
| qmi_message_dms_set_firmware_preference_input_set_download_override (input, TRUE, NULL); |
| |
| if (self->priv->modem_storage_index > 0) |
| qmi_message_dms_set_firmware_preference_input_set_modem_storage_index (input, (guint8) self->priv->modem_storage_index, NULL); |
| |
| g_debug ("[qfu-updater] setting firmware preference..."); |
| g_debug ("[qfu-updater] modem image: unique id '%.16s', build id '%s'", |
| (gchar *) (modem_image_id.unique_id->data), modem_image_id.build_id); |
| g_debug ("[qfu-updater] pri image: unique id '%.16s', build id '%s'", |
| (gchar *) (pri_image_id.unique_id->data), pri_image_id.build_id); |
| g_debug ("[qfu-updater] override download: %s", |
| self->priv->override_download ? "yes" : "no"); |
| |
| qmi_client_dms_set_firmware_preference (ctx->qmi_client, |
| input, |
| 10, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) set_firmware_preference_ready, |
| task); |
| |
| g_array_unref (modem_image_id.unique_id); |
| g_free (modem_image_id.build_id); |
| g_array_unref (pri_image_id.unique_id); |
| g_free (pri_image_id.build_id); |
| |
| qmi_message_dms_set_firmware_preference_input_unref (input); |
| } |
| |
| static gboolean |
| validate_firmware_config_carrier (QfuUpdater *self, |
| RunContext *ctx, |
| GError **error) |
| { |
| GList *l; |
| |
| /* Try to preload information like firmware/config/carrier from CWE images */ |
| for (l = ctx->pending_images; l; l = g_list_next (l)) { |
| const gchar *firmware_version; |
| const gchar *config_version; |
| const gchar *carrier; |
| QfuImageCwe *image; |
| |
| if (!QFU_IS_IMAGE_CWE (l->data)) |
| continue; |
| |
| image = QFU_IMAGE_CWE (l->data); |
| firmware_version = qfu_image_cwe_get_parsed_firmware_version (image); |
| config_version = qfu_image_cwe_get_parsed_config_version (image); |
| carrier = qfu_image_cwe_get_parsed_carrier (image); |
| |
| if (firmware_version) { |
| if (!ctx->firmware_version) |
| ctx->firmware_version = g_strdup (firmware_version); |
| else if (!g_str_equal (firmware_version, ctx->firmware_version)) { |
| if (!self->priv->ignore_version_errors) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, |
| "couldn't detect firmware version: " |
| "firmware version strings don't match on specified images: " |
| "'%s' != '%s'", |
| firmware_version, ctx->firmware_version); |
| return FALSE; |
| } |
| g_warning ("firmware version strings don't match on specified images: " |
| "'%s' != '%s' (IGNORED with --ignore-version-errors)", |
| firmware_version, ctx->firmware_version); |
| } |
| } |
| |
| if (config_version) { |
| if (!ctx->config_version) |
| ctx->config_version = g_strdup (config_version); |
| else if (!g_str_equal (config_version, ctx->config_version)) { |
| if (!self->priv->ignore_version_errors) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, |
| "couldn't detect config version: " |
| "config version strings don't match on specified images: " |
| "'%s' != '%s'", |
| config_version, ctx->config_version); |
| return FALSE; |
| } |
| g_warning ("[qfu-updater] config version strings don't match on specified images: " |
| "'%s' != '%s' (IGNORED with --ignore-version-errors)", |
| config_version, ctx->config_version); |
| } |
| } |
| |
| if (carrier) { |
| if (!ctx->carrier) |
| ctx->carrier = g_strdup (carrier); |
| else if (!g_str_equal (carrier, ctx->carrier)) { |
| if (!self->priv->ignore_version_errors) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, |
| "couldn't detect carrier: " |
| "carrier strings don't match on specified images: " |
| "'%s' != '%s'", |
| carrier, ctx->carrier); |
| return FALSE; |
| } |
| g_warning ("[qfu-updater] carrier strings don't match on specified images: " |
| "'%s' != '%s' (IGNORED with --ignore-version-errors)", |
| carrier, ctx->carrier); |
| } |
| } |
| } |
| |
| /* If given firmware version doesn't match the one in the image, error out */ |
| if (self->priv->firmware_version && (g_strcmp0 (self->priv->firmware_version, ctx->firmware_version) != 0)) { |
| if (!self->priv->ignore_version_errors) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, |
| "error validating firmware version: " |
| "user provided firmware version doesn't match the one in the specified images: " |
| "'%s' != '%s'", |
| self->priv->firmware_version, ctx->firmware_version); |
| return FALSE; |
| } |
| g_warning ("[qfu-updater] user provided firmware version doesn't match the one in the specified images: " |
| "'%s' != '%s' (IGNORED with --ignore-version-errors)", |
| self->priv->firmware_version, ctx->firmware_version); |
| } |
| |
| /* If given config version doesn't match the one in the image, error out */ |
| if (self->priv->config_version && (g_strcmp0 (self->priv->config_version, ctx->config_version) != 0)) { |
| if (!self->priv->ignore_version_errors) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, |
| "error validating firmware version: " |
| "user provided firmware version doesn't match the one in the specified images: " |
| "'%s' != '%s'", |
| self->priv->config_version, ctx->config_version); |
| return FALSE; |
| } |
| g_warning ("[qfu-updater] user provided config version doesn't match the one in the specified images: " |
| "'%s' != '%s' (IGNORED with --ignore-version-errors)", |
| self->priv->firmware_version, ctx->firmware_version); |
| } |
| |
| /* If given carrier doesn't match the one in the image, error out */ |
| if (self->priv->carrier && (g_strcmp0 (self->priv->carrier, ctx->carrier) != 0)) { |
| if (!self->priv->ignore_version_errors) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, |
| "error validating carrier: " |
| "user provided carrier doesn't match the one in the specified images: " |
| "'%s' != '%s'", |
| self->priv->carrier, ctx->carrier); |
| return FALSE; |
| } |
| g_warning ("[qfu-updater] user provided carrier doesn't match the one in the specified images: " |
| "'%s' != '%s' (IGNORED with --ignore-version-errors)", |
| self->priv->carrier, ctx->carrier); |
| } |
| |
| /* No firmware version? */ |
| if (!self->priv->firmware_version && !ctx->firmware_version) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, |
| "firmware version required"); |
| return FALSE; |
| } |
| |
| /* No config version? */ |
| if (!self->priv->config_version && !ctx->config_version) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, |
| "config version required"); |
| return FALSE; |
| } |
| |
| /* No carrier? */ |
| if (!self->priv->carrier && !ctx->carrier) { |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, |
| "carrier required"); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| run_context_step_get_firmware_preference (GTask *task) |
| { |
| RunContext *ctx; |
| QfuUpdater *self; |
| GError *error = NULL; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| if (!ctx->supports_firmware_preference_management) { |
| /* Firmware preference setting not supported; fail if we got those settings explicitly */ |
| if (self->priv->firmware_version || self->priv->config_version || self->priv->carrier) { |
| g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, |
| "setting firmware/config/carrier is not supported by this device"); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Jump to the reset step and run boothold there */ |
| ctx->boothold_reset = TRUE; |
| run_context_step_next (task, RUN_CONTEXT_STEP_POWER_CYCLE); |
| return; |
| } |
| |
| /* Firmware preference setting is supported so we require firmware/config/carrier */ |
| if (!validate_firmware_config_carrier (self, ctx, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| new_client_dms_ready (gpointer unused, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| RunContext *ctx; |
| QfuUpdater *self; |
| GError *error = NULL; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| g_assert (!ctx->qmi_device); |
| g_assert (!ctx->qmi_client); |
| |
| if (!qfu_utils_new_client_dms_finish (res, |
| &ctx->qmi_device, |
| &ctx->qmi_client, |
| &ctx->revision, |
| &ctx->supports_stored_image_management, |
| &ctx->max_modem_storage_index, |
| &ctx->supports_firmware_preference_management, |
| &ctx->firmware_preference, |
| &ctx->current_firmware, |
| &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Validate modem storage index, if one specified */ |
| if (self->priv->modem_storage_index > ctx->max_modem_storage_index) { |
| g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, |
| "modem storage index out of bounds (%u > %u)", |
| self->priv->modem_storage_index, |
| ctx->max_modem_storage_index); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Go on */ |
| run_context_step_next (task, ctx->step + 1); |
| } |
| |
| static void |
| run_context_step_qmi_client (GTask *task) |
| { |
| RunContext *ctx; |
| QfuUpdater *self; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| self = g_task_get_source_object (task); |
| |
| g_print ("loading device information before the update...\n"); |
| |
| g_debug ("[qfu-updater] creating QMI DMS client..."); |
| g_assert (ctx->cdc_wdm_file); |
| qfu_utils_new_client_dms (ctx->cdc_wdm_file, |
| 3, /* initially, up to 3 tries to allocate DMS client */ |
| self->priv->device_open_flags, |
| TRUE, |
| g_task_get_cancellable (task), |
| (GAsyncReadyCallback) new_client_dms_ready, |
| task); |
| } |
| |
| typedef void (* RunContextStepFunc) (GTask *task); |
| static const RunContextStepFunc run_context_step_func[] = { |
| [RUN_CONTEXT_STEP_QMI_CLIENT] = run_context_step_qmi_client, |
| [RUN_CONTEXT_STEP_GET_FIRMWARE_PREFERENCE] = run_context_step_get_firmware_preference, |
| [RUN_CONTEXT_STEP_SET_FIRMWARE_PREFERENCE] = run_context_step_set_firmware_preference, |
| [RUN_CONTEXT_STEP_POWER_CYCLE] = run_context_step_power_cycle, |
| [RUN_CONTEXT_STEP_CLEANUP_QMI_DEVICE] = run_context_step_cleanup_qmi_device, |
| [RUN_CONTEXT_STEP_WAIT_FOR_TTY] = run_context_step_wait_for_tty, |
| [RUN_CONTEXT_STEP_SELECT_DEVICE] = run_context_step_select_device, |
| [RUN_CONTEXT_STEP_SELECT_IMAGE] = run_context_step_select_image, |
| [RUN_CONTEXT_STEP_DOWNLOAD_IMAGE] = run_context_step_download_image, |
| [RUN_CONTEXT_STEP_CLEANUP_IMAGE] = run_context_step_cleanup_image, |
| [RUN_CONTEXT_STEP_CLEANUP_DEVICE] = run_context_step_cleanup_device, |
| [RUN_CONTEXT_STEP_WAIT_FOR_CDC_WDM] = run_context_step_wait_for_cdc_wdm, |
| [RUN_CONTEXT_STEP_WAIT_FOR_BOOT] = run_context_step_wait_for_boot, |
| [RUN_CONTEXT_STEP_QMI_CLIENT_AFTER] = run_context_step_qmi_client_after, |
| [RUN_CONTEXT_STEP_CLEANUP_QMI_DEVICE_FULL] = run_context_step_cleanup_qmi_device_full, |
| }; |
| |
| G_STATIC_ASSERT (G_N_ELEMENTS (run_context_step_func) == RUN_CONTEXT_STEP_LAST); |
| |
| static void |
| run_context_step (GTask *task) |
| { |
| RunContext *ctx; |
| |
| ctx = (RunContext *) g_task_get_task_data (task); |
| |
| /* Early halt operation if cancelled */ |
| if (g_task_return_error_if_cancelled (task)) { |
| g_object_unref (task); |
| return; |
| } |
| |
| if (ctx->step < G_N_ELEMENTS (run_context_step_func)) { |
| run_context_step_func [ctx->step] (task); |
| return; |
| } |
| |
| g_debug ("[qfu-updater] operation finished"); |
| run_context_step_last (task); |
| } |
| |
| static gint |
| image_sort_by_size (QfuImage *a, QfuImage *b) |
| { |
| return qfu_image_get_size (b) - qfu_image_get_size (a); |
| } |
| |
| static gboolean |
| preload_images (RunContext *ctx, |
| GList *image_file_list, |
| GCancellable *cancellable, |
| GError **error) |
| { |
| GList *l; |
| |
| /* Build QfuImage objects for each image file given */ |
| for (l = image_file_list; l; l = g_list_next (l)) { |
| QfuImage *image; |
| |
| image = qfu_image_factory_build (G_FILE (l->data), cancellable, error); |
| if (!image) |
| return FALSE; |
| |
| ctx->pending_images = g_list_append (ctx->pending_images, image); |
| } |
| |
| /* Sort by size, we want to download bigger images first, as that is usually |
| * the use case anyway, first flash e.g. the .cwe file, then the .nvu one. */ |
| ctx->pending_images = g_list_sort (ctx->pending_images, (GCompareFunc) image_sort_by_size); |
| |
| return TRUE; |
| } |
| |
| void |
| qfu_updater_run (QfuUpdater *self, |
| GList *image_file_list, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| RunContext *ctx; |
| GTask *task; |
| GError *error = NULL; |
| |
| g_assert (image_file_list); |
| |
| ctx = g_slice_new0 (RunContext); |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) run_context_free); |
| |
| if (!preload_images (ctx, image_file_list, cancellable, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| switch (self->priv->type) { |
| case UPDATER_TYPE_GENERIC: |
| ctx->step = RUN_CONTEXT_STEP_QMI_CLIENT; |
| ctx->cdc_wdm_file = qfu_device_selection_get_single_cdc_wdm (self->priv->device_selection); |
| if (!ctx->cdc_wdm_file) { |
| g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| "No cdc-wdm device found to run update operation"); |
| g_object_unref (task); |
| return; |
| } |
| break; |
| case UPDATER_TYPE_DOWNLOAD: |
| ctx->step = RUN_CONTEXT_STEP_SELECT_DEVICE; |
| ctx->serial_file = qfu_device_selection_get_single_tty (self->priv->device_selection, QFU_HELPERS_DEVICE_MODE_DOWNLOAD); |
| if (!ctx->serial_file) { |
| g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, |
| "No serial device in download mode found to run QDL update operation"); |
| g_object_unref (task); |
| return; |
| } |
| break; |
| case UPDATER_TYPE_UNKNOWN: |
| default: |
| g_assert_not_reached (); |
| } |
| |
| run_context_step (task); |
| } |
| |
| /******************************************************************************/ |
| |
| QfuUpdater * |
| qfu_updater_new (QfuDeviceSelection *device_selection, |
| const gchar *firmware_version, |
| const gchar *config_version, |
| const gchar *carrier, |
| QmiDeviceOpenFlags device_open_flags, |
| gboolean ignore_version_errors, |
| gboolean override_download, |
| guint8 modem_storage_index, |
| gboolean skip_validation) |
| { |
| QfuUpdater *self; |
| |
| g_assert (QFU_IS_DEVICE_SELECTION (device_selection)); |
| |
| self = g_object_new (QFU_TYPE_UPDATER, NULL); |
| self->priv->type = UPDATER_TYPE_GENERIC; |
| self->priv->device_selection = g_object_ref (device_selection); |
| self->priv->device_open_flags = device_open_flags; |
| self->priv->firmware_version = (firmware_version ? g_strdup (firmware_version) : NULL); |
| self->priv->config_version = (config_version ? g_strdup (config_version) : NULL); |
| self->priv->carrier = (carrier ? g_strdup (carrier) : NULL); |
| self->priv->ignore_version_errors = ignore_version_errors; |
| self->priv->override_download = override_download; |
| self->priv->modem_storage_index = modem_storage_index; |
| self->priv->skip_validation = skip_validation; |
| |
| return self; |
| } |
| |
| QfuUpdater * |
| qfu_updater_new_download (QfuDeviceSelection *device_selection) |
| { |
| QfuUpdater *self; |
| |
| g_assert (QFU_IS_DEVICE_SELECTION (device_selection)); |
| |
| self = g_object_new (QFU_TYPE_UPDATER, NULL); |
| self->priv->type = UPDATER_TYPE_DOWNLOAD; |
| self->priv->device_selection = g_object_ref (device_selection); |
| |
| return self; |
| } |
| |
| static void |
| qfu_updater_init (QfuUpdater *self) |
| { |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_UPDATER, QfuUpdaterPrivate); |
| self->priv->type = UPDATER_TYPE_UNKNOWN; |
| } |
| |
| static void |
| dispose (GObject *object) |
| { |
| QfuUpdater *self = QFU_UPDATER (object); |
| |
| g_clear_object (&self->priv->device_selection); |
| |
| G_OBJECT_CLASS (qfu_updater_parent_class)->dispose (object); |
| } |
| |
| static void |
| finalize (GObject *object) |
| { |
| QfuUpdater *self = QFU_UPDATER (object); |
| |
| g_free (self->priv->firmware_version); |
| g_free (self->priv->config_version); |
| g_free (self->priv->carrier); |
| |
| G_OBJECT_CLASS (qfu_updater_parent_class)->finalize (object); |
| } |
| |
| static void |
| qfu_updater_class_init (QfuUpdaterClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (QfuUpdaterPrivate)); |
| |
| object_class->dispose = dispose; |
| object_class->finalize = finalize; |
| } |