mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-21 12:57:56 +00:00
524 lines
13 KiB
C
524 lines
13 KiB
C
/*
|
|
* Copyright (C) 2021 Jarvis Jiang <jarvis.w.jiang@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#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) */
|