/* * Copyright (C) 2021 Jarvis Jiang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include #include #include #include "fu-mbim-qdu-updater.h" #include "fu-mm-utils.h" #if MBIM_CHECK_VERSION(1,25,3) #define FU_MBIM_QDU_MAX_OPEN_ATTEMPTS 8 struct _FuMbimQduUpdater { GObject parent_instance; gchar *mbim_port; MbimDevice *mbim_device; }; G_DEFINE_TYPE (FuMbimQduUpdater, fu_mbim_qdu_updater, G_TYPE_OBJECT) typedef struct { GMainLoop *mainloop; MbimDevice *mbim_device; GError *error; guint open_attempts; } OpenContext; static void fu_mbim_qdu_updater_mbim_device_open_attempt (OpenContext *ctx); static void fu_mbim_qdu_updater_mbim_device_open_ready (GObject *mbim_device, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *) user_data; g_assert (ctx->open_attempts > 0); if (!mbim_device_open_full_finish (MBIM_DEVICE (mbim_device), res, &ctx->error)) { ctx->open_attempts--; if (ctx->open_attempts == 0) { g_clear_object (&ctx->mbim_device); g_main_loop_quit (ctx->mainloop); return; } /* retry */ g_debug ("error: couldn't open mbim device: %s", ctx->error->message); g_clear_error (&ctx->error); fu_mbim_qdu_updater_mbim_device_open_attempt (ctx); return; } g_main_loop_quit (ctx->mainloop); } static void fu_mbim_qdu_updater_mbim_device_open_attempt (OpenContext *ctx) { /* all communication through the proxy */ MbimDeviceOpenFlags open_flags = MBIM_DEVICE_OPEN_FLAGS_PROXY; g_debug ("trying to open MBIM device..."); mbim_device_open_full (ctx->mbim_device, open_flags, 10, NULL, fu_mbim_qdu_updater_mbim_device_open_ready, ctx); } static void fu_mbim_qdu_updater_mbim_device_new_ready (GObject *source, GAsyncResult *res, gpointer user_data) { OpenContext *ctx = (OpenContext *) user_data; ctx->mbim_device = mbim_device_new_finish (res, &ctx->error); if (ctx->mbim_device == NULL) { g_main_loop_quit (ctx->mainloop); return; } fu_mbim_qdu_updater_mbim_device_open_attempt (ctx); } gboolean fu_mbim_qdu_updater_open (FuMbimQduUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); g_autoptr(GFile) mbim_device_file = g_file_new_for_path (self->mbim_port); OpenContext ctx = { .mainloop = mainloop, .mbim_device = NULL, .error = NULL, .open_attempts = FU_MBIM_QDU_MAX_OPEN_ATTEMPTS, }; mbim_device_new (mbim_device_file, NULL, fu_mbim_qdu_updater_mbim_device_new_ready, &ctx); g_main_loop_run (mainloop); /* either we have all device or otherwise error is set */ if (ctx.mbim_device != NULL) { g_warn_if_fail (ctx.error == NULL); self->mbim_device = ctx.mbim_device; /* success */ return TRUE; } g_warn_if_fail (ctx.error != NULL); g_warn_if_fail (ctx.mbim_device == NULL); g_propagate_error (error, ctx.error); return FALSE; } typedef struct { GMainLoop *mainloop; MbimDevice *mbim_device; GError *error; } CloseContext; static void fu_mbim_qdu_updater_mbim_device_close_ready (GObject *mbim_device, GAsyncResult *res, gpointer user_data) { CloseContext *ctx = (CloseContext *) user_data; /* ignore errors when closing */ mbim_device_close_finish (MBIM_DEVICE (mbim_device), res, &ctx->error); g_clear_object (&ctx->mbim_device); g_main_loop_quit (ctx->mainloop); } gboolean fu_mbim_qdu_updater_close (FuMbimQduUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); CloseContext ctx = { .mainloop = mainloop, .mbim_device = g_steal_pointer (&self->mbim_device), .error = NULL, }; if (ctx.mbim_device == NULL) return TRUE; mbim_device_close (ctx.mbim_device, 5, NULL, fu_mbim_qdu_updater_mbim_device_close_ready, &ctx); g_main_loop_run (mainloop); /* we should always have both device cleared, and optionally error set */ g_warn_if_fail (ctx.mbim_device == NULL); if (ctx.error != NULL) { g_propagate_error (error, ctx.error); return FALSE; } /* update attach right after this */ return TRUE; } typedef struct { GMainLoop *mainloop; GError *error; gchar *firmware_version; } GetFirmwareVersionContext; static void fu_mbim_qdu_updater_caps_query_ready (MbimDevice *device, GAsyncResult *res, gpointer user_data) { GetFirmwareVersionContext *ctx = user_data; g_autofree gchar *firmware_version = NULL; g_autoptr(MbimMessage) response = NULL; response = mbim_device_command_finish (device, res, &ctx->error); if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug ("error: operation failed: %s", ctx->error->message); g_main_loop_quit (ctx->mainloop); return; } if (!mbim_message_device_caps_response_parse (response, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &firmware_version, NULL, &ctx->error)) { g_debug ("error: couldn't parse response message: %s", ctx->error->message); g_main_loop_quit (ctx->mainloop); return; } g_debug ("[%s] Successfully request modem to query caps", mbim_device_get_path_display (device)); ctx->firmware_version = g_strdup (firmware_version); g_main_loop_quit (ctx->mainloop); } static void fu_mbim_qdu_updater_caps_query (MbimDevice *device, GetFirmwareVersionContext *ctx) { g_autoptr(MbimMessage) request = NULL; request = mbim_message_device_caps_query_new (NULL); mbim_device_command (device, request, 10, NULL, (GAsyncReadyCallback) fu_mbim_qdu_updater_caps_query_ready, ctx); } gchar * fu_mbim_qdu_updater_check_ready (FuMbimQduUpdater *self, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); GetFirmwareVersionContext ctx = { .mainloop = mainloop, .error = NULL, .firmware_version = NULL, }; fu_mbim_qdu_updater_caps_query (self->mbim_device, &ctx); g_main_loop_run (mainloop); if (ctx.error != NULL) { g_propagate_error (error, ctx.error); return NULL; } return ctx.firmware_version; } typedef struct { GMainLoop *mainloop; MbimDevice *mbim_device; GError *error; GBytes *blob; GArray *digest; GPtrArray *chunks; guint chunk_sent; FuDevice *device; } WriteContext; static gboolean fu_mbim_qdu_updater_reboot_timeout (gpointer user_data) { WriteContext *ctx = user_data; g_ptr_array_unref (ctx->chunks); g_main_loop_quit (ctx->mainloop); return G_SOURCE_REMOVE; } static void fu_mbim_qdu_updater_file_write_ready (MbimDevice *device, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = user_data; g_autoptr(MbimMessage) response = NULL; response = mbim_device_command_finish (device, res, &ctx->error); if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug ("error: operation failed: %s", ctx->error->message); g_ptr_array_unref (ctx->chunks); g_main_loop_quit (ctx->mainloop); return; } if (!mbim_message_qdu_file_write_response_parse (response, &ctx->error)) { g_debug ("error: couldn't parse response message: %s", ctx->error->message); g_ptr_array_unref (ctx->chunks); g_main_loop_quit (ctx->mainloop); return; } fu_device_set_progress_full (FU_DEVICE (ctx->device), (gsize) ctx->chunk_sent, (gsize) ctx->chunks->len); ctx->chunk_sent++; if (ctx->chunk_sent < ctx->chunks->len) { FuChunk *chk = g_ptr_array_index (ctx->chunks, ctx->chunk_sent); g_autoptr(MbimMessage) request = mbim_message_qdu_file_write_set_new (fu_chunk_get_data_sz (chk), (const guint8 *) fu_chunk_get_data (chk), NULL); mbim_device_command (ctx->mbim_device, request, 10, NULL, (GAsyncReadyCallback) fu_mbim_qdu_updater_file_write_ready, ctx); return; } fu_device_set_progress (FU_DEVICE (ctx->device), 100); fu_device_set_status (FU_DEVICE (ctx->device), FWUPD_STATUS_DEVICE_RESTART); /* device will auto reboot right after update finish */ g_timeout_add_seconds (10, fu_mbim_qdu_updater_reboot_timeout, ctx); } static void fu_mbim_qdu_updater_file_open_ready (MbimDevice *device, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = user_data; guint32 out_max_transfer_size; guint32 total_sending_numbers = 0; FuChunk *chk = NULL; g_autoptr(MbimMessage) request = NULL; g_autoptr(MbimMessage) response = NULL; response = mbim_device_command_finish (device, res, &ctx->error); if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug ("error: operation failed: %s", ctx->error->message); g_main_loop_quit (ctx->mainloop); return; } if (!mbim_message_qdu_file_open_response_parse (response, &out_max_transfer_size, NULL, &ctx->error)) { g_debug ("error: couldn't parse response message: %s", ctx->error->message); g_main_loop_quit (ctx->mainloop); return; } total_sending_numbers = g_bytes_get_size (ctx->blob) / out_max_transfer_size; if (g_bytes_get_size (ctx->blob) % out_max_transfer_size != 0) total_sending_numbers ++; ctx->chunks = fu_chunk_array_new_from_bytes (ctx->blob, 0x00, /* start addr */ 0x00, /* page_sz */ out_max_transfer_size); chk = g_ptr_array_index (ctx->chunks, 0); request = mbim_message_qdu_file_write_set_new (fu_chunk_get_data_sz (chk), (const guint8 *) fu_chunk_get_data (chk), NULL); mbim_device_command (ctx->mbim_device, request, 10, NULL, (GAsyncReadyCallback) fu_mbim_qdu_updater_file_write_ready, ctx); } static void fu_mbim_qdu_updater_session_ready (MbimDevice *device, GAsyncResult *res, gpointer user_data) { WriteContext *ctx = user_data; g_autoptr(MbimMessage) response = NULL; g_autoptr(MbimMessage) request = NULL; response = mbim_device_command_finish (device, res, &ctx->error); if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &ctx->error)) { g_debug ("error: operation failed: %s", ctx->error->message); g_main_loop_quit (ctx->mainloop); return; } if (!mbim_message_qdu_update_session_response_parse (response, NULL, NULL, NULL, NULL, NULL, NULL, &ctx->error)) { g_debug ("error: couldn't parse response message: %s", ctx->error->message); g_main_loop_quit (ctx->mainloop); return; } g_debug ("[%s] Successfully request modem to update session", mbim_device_get_path_display (device)); request = mbim_message_qdu_file_open_set_new (MBIM_QDU_FILE_TYPE_LITTLE_ENDIAN_PACKAGE, g_bytes_get_size (ctx->blob), NULL); mbim_device_command (device, request, 10, NULL, (GAsyncReadyCallback) fu_mbim_qdu_updater_file_open_ready, ctx); } static void fu_mbim_qdu_updater_set_update_session (MbimDevice *device, WriteContext *ctx) { g_autoptr(MbimMessage) request = NULL; request = mbim_message_qdu_update_session_set_new (MBIM_QDU_SESSION_ACTION_START, MBIM_QDU_SESSION_TYPE_LE, NULL); mbim_device_command (device, request, 10, NULL, (GAsyncReadyCallback) fu_mbim_qdu_updater_session_ready, ctx); } static GArray * fu_mbim_qdu_updater_get_checksum (GBytes *blob) { gsize file_size; gsize hash_size; GArray *digest; g_autoptr(GChecksum) checksum = NULL; /* get checksum, to be used as unique id */ file_size = g_bytes_get_size (blob); hash_size = g_checksum_type_get_length (G_CHECKSUM_SHA256); checksum = g_checksum_new (G_CHECKSUM_SHA256); g_checksum_update (checksum, g_bytes_get_data (blob, NULL), file_size); /* libqmi expects a GArray of bytes, not a GByteArray */ digest = g_array_sized_new (FALSE, FALSE, sizeof (guint8), hash_size); g_array_set_size (digest, hash_size); g_checksum_get_digest (checksum, (guint8 *) digest->data, &hash_size); return digest; } GArray * fu_mbim_qdu_updater_write (FuMbimQduUpdater *self, const gchar *filename, GBytes *blob, FuDevice *device, GError **error) { g_autoptr(GMainLoop) mainloop = g_main_loop_new (NULL, FALSE); g_autoptr(GArray) digest = fu_mbim_qdu_updater_get_checksum (blob); g_autoptr(GPtrArray) chunks = NULL; WriteContext ctx = { .mainloop = mainloop, .mbim_device = self->mbim_device, .error = NULL, .blob = blob, .digest = digest, .chunks = chunks, .chunk_sent = 0, .device = device, }; fu_mbim_qdu_updater_set_update_session (self->mbim_device, &ctx); g_main_loop_run (mainloop); if (ctx.error != NULL) { g_propagate_error (error, ctx.error); return NULL; } return g_steal_pointer (&digest); } static void fu_mbim_qdu_updater_init (FuMbimQduUpdater *self) { } static void fu_mbim_qdu_updater_finalize (GObject *object) { FuMbimQduUpdater *self = FU_MBIM_QDU_UPDATER (object); g_warn_if_fail (self->mbim_device == NULL); g_free (self->mbim_port); G_OBJECT_CLASS (fu_mbim_qdu_updater_parent_class)->finalize (object); } static void fu_mbim_qdu_updater_class_init (FuMbimQduUpdaterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = fu_mbim_qdu_updater_finalize; } FuMbimQduUpdater * fu_mbim_qdu_updater_new (const gchar *path) { FuMbimQduUpdater *self = g_object_new (FU_TYPE_MBIM_QDU_UPDATER, NULL); self->mbim_port = g_strdup (path); return self; } #endif /* MBIM_CHECK_VERSION(1,25,3) */