fwupd/plugins/modem-manager/fu-mbim-qdu-updater.c
Jarvis Jiang 6b79e9201d modem-manager: Add MBIM QDU support for firmware downloading in the MM plugin
Add the mbim-qdu support for firmware downloading, it will be used for T99W175 module of Foxconn.

Signed-off-by: Jarvis Jiang <jarvis.w.jiang@gmail.com>
2021-06-28 10:11:15 +01:00

486 lines
13 KiB
C

/*
* Copyright (C) 2021 Jarvis Jiang <jarvis.w.jiang@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fwupdplugin.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) */