mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-02 16:45:45 +00:00
860 lines
24 KiB
C
860 lines
24 KiB
C
/*
|
|
* Copyright (C) 2020 Aleksander Morgado <aleksander@aleksander.es>
|
|
* Copyright (C) 2021 Quectel Wireless Solutions Co., Ltd.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include "fu-firehose-updater.h"
|
|
|
|
/* Maximum amount of non-"response" (e.g. "log") XML messages that can be
|
|
* received from the module when expecting a "response". This is just a safe
|
|
* upper limit to avoid reading forever. */
|
|
#define MAX_RECV_MESSAGES 100
|
|
|
|
/* When initializing the conversation with the firehose interpreter, the
|
|
* first step is to receive and process a bunch of messages sent by the
|
|
* module. The initial timeout to receive the first message is longer in case
|
|
* the module needs some initialization time itself; all the messages after
|
|
* the first one are expected to be received much quicker. The default timeout
|
|
* value should not be extremely long because the initialization phase ends
|
|
* when we don't receive more messages, so it's expected that the timeout will
|
|
* fully elapse after the last message sent by the module. */
|
|
#define INITIALIZE_INITIAL_TIMEOUT_MS 3000
|
|
#define INITIALIZE_TIMEOUT_MS 250
|
|
|
|
/* Maximum amount of time to wait for a message from the module. */
|
|
#define DEFAULT_RECV_TIMEOUT_MS 15000
|
|
|
|
/* The first configure attempt sent to the module will include all the defaults
|
|
* listed below. If the module replies with a NAK specifying a different
|
|
* (shorter) max payload size to use, the second configure attempt will be done
|
|
* with that new suggested max payload size value. Only 2 configure attempts are
|
|
* therefore expected. */
|
|
#define MAX_CONFIGURE_ATTEMPTS 2
|
|
|
|
/* Defaults for the firehose configuration step. The max payload size to target
|
|
* in bytes may end up being a different if the module requests a shorter one.
|
|
*/
|
|
#define CONFIGURE_MEMORY_NAME "nand"
|
|
#define CONFIGURE_VERBOSE 0
|
|
#define CONFIGURE_ALWAYS_VALIDATE 0
|
|
#define CONFIGURE_MAX_DIGEST_TABLE_SIZE_IN_BYTES 2048
|
|
#define CONFIGURE_MAX_PAYLOAD_SIZE_TO_TARGET_IN_BYTES 8192
|
|
#define CONFIGURE_ZLP_AWARE_HOST 1
|
|
#define CONFIGURE_SKIP_STORAGE_INIT 0
|
|
|
|
enum { SIGNAL_WRITE_PERCENTAGE, SIGNAL_LAST };
|
|
|
|
static guint signals[SIGNAL_LAST] = {0};
|
|
|
|
struct _FuFirehoseUpdater {
|
|
GObject parent_instance;
|
|
gchar *port;
|
|
FuIOChannel *io_channel;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuFirehoseUpdater, fu_firehose_updater, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
fu_firehose_updater_log_message(const gchar *action, GBytes *msg)
|
|
{
|
|
const gchar *msg_data;
|
|
gsize msg_size;
|
|
g_autofree gchar *msg_strsafe = NULL;
|
|
|
|
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") == NULL)
|
|
return;
|
|
|
|
msg_data = (const gchar *)g_bytes_get_data(msg, &msg_size);
|
|
if (msg_size > G_MAXINT)
|
|
return;
|
|
|
|
msg_strsafe = fu_common_strsafe(msg_data, msg_size);
|
|
|
|
g_debug("%s: %.*s", action, (gint)msg_size, msg_strsafe);
|
|
}
|
|
|
|
static gboolean
|
|
validate_program_action(XbNode *program, FuArchive *archive, GError **error)
|
|
{
|
|
const gchar *filename_attr;
|
|
GBytes *file;
|
|
gsize file_size;
|
|
guint64 computed_num_partition_sectors;
|
|
guint64 num_partition_sectors;
|
|
guint64 sector_size_in_bytes;
|
|
|
|
filename_attr = xb_node_get_attr(program, "filename");
|
|
if (filename_attr == NULL) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Missing 'filename' attribute in 'program' action");
|
|
return FALSE;
|
|
}
|
|
|
|
/* contents of the CAB file are flat, no subdirectories; look for the
|
|
* exact filename */
|
|
file = fu_archive_lookup_by_fn(archive, filename_attr, error);
|
|
if (file == NULL)
|
|
return FALSE;
|
|
|
|
file_size = g_bytes_get_size(file);
|
|
|
|
num_partition_sectors = xb_node_get_attr_as_uint(program, "num_partition_sectors");
|
|
if (num_partition_sectors == G_MAXUINT64) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Missing 'num_partition_sectors' attribute in 'program' action for "
|
|
"filename '%s'",
|
|
filename_attr);
|
|
return FALSE;
|
|
}
|
|
sector_size_in_bytes = xb_node_get_attr_as_uint(program, "SECTOR_SIZE_IN_BYTES");
|
|
if (sector_size_in_bytes == G_MAXUINT64) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Missing 'SECTOR_SIZE_IN_BYTES' attribute in 'program' action for "
|
|
"filename '%s'",
|
|
filename_attr);
|
|
return FALSE;
|
|
}
|
|
|
|
computed_num_partition_sectors = file_size / sector_size_in_bytes;
|
|
if ((file_size % sector_size_in_bytes) != 0)
|
|
computed_num_partition_sectors++;
|
|
|
|
if (computed_num_partition_sectors != num_partition_sectors) {
|
|
g_set_error(
|
|
error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Invalid 'num_partition_sectors' in 'program' action for filename '%s': "
|
|
"expected %" G_GUINT64_FORMAT " instead of %" G_GUINT64_FORMAT " bytes",
|
|
filename_attr,
|
|
computed_num_partition_sectors,
|
|
num_partition_sectors);
|
|
return FALSE;
|
|
}
|
|
|
|
xb_node_set_data(program, "fwupd:ProgramFile", file);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_firehose_validate_rawprogram(GBytes *rawprogram,
|
|
FuArchive *archive,
|
|
XbSilo **out_silo,
|
|
GPtrArray **out_action_nodes,
|
|
GError **error)
|
|
{
|
|
g_autoptr(XbBuilder) builder = xb_builder_new();
|
|
g_autoptr(XbBuilderSource) source = xb_builder_source_new();
|
|
g_autoptr(XbSilo) silo = NULL;
|
|
g_autoptr(XbNode) data_node = NULL;
|
|
g_autoptr(GPtrArray) action_nodes = NULL;
|
|
|
|
if (!xb_builder_source_load_bytes(source, rawprogram, XB_BUILDER_SOURCE_FLAG_NONE, error))
|
|
return FALSE;
|
|
xb_builder_import_source(builder, source);
|
|
silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
|
|
if (silo == NULL)
|
|
return FALSE;
|
|
|
|
data_node = xb_silo_get_root(silo);
|
|
action_nodes = xb_node_get_children(data_node);
|
|
if (action_nodes == NULL || action_nodes->len == 0) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "No actions given");
|
|
return FALSE;
|
|
}
|
|
|
|
for (guint i = 0; i < action_nodes->len; i++) {
|
|
XbNode *n = g_ptr_array_index(action_nodes, i);
|
|
if ((g_strcmp0(xb_node_get_element(n), "program") == 0) &&
|
|
!validate_program_action(n, archive, error)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
*out_silo = g_steal_pointer(&silo);
|
|
*out_action_nodes = g_steal_pointer(&action_nodes);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_firehose_updater_open(FuFirehoseUpdater *self, GError **error)
|
|
{
|
|
g_debug("opening firehose port...");
|
|
self->io_channel = fu_io_channel_new_file(self->port, error);
|
|
return (self->io_channel != NULL);
|
|
}
|
|
|
|
gboolean
|
|
fu_firehose_updater_close(FuFirehoseUpdater *self, GError **error)
|
|
{
|
|
g_debug("closing firehose port...");
|
|
if (!fu_io_channel_shutdown(self->io_channel, error))
|
|
return FALSE;
|
|
g_clear_object(&self->io_channel);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_check_operation_result(XbNode *node, gboolean *out_rawmode)
|
|
{
|
|
g_warn_if_fail(g_strcmp0(xb_node_get_element(node), "response") == 0);
|
|
if (g_strcmp0(xb_node_get_attr(node, "value"), "ACK") != 0)
|
|
return FALSE;
|
|
if (out_rawmode)
|
|
*out_rawmode = (g_strcmp0(xb_node_get_attr(node, "rawmode"), "true") == 0);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_process_response(GBytes *rsp_bytes,
|
|
XbSilo **out_silo,
|
|
XbNode **out_response_node,
|
|
GError **error)
|
|
{
|
|
g_autoptr(XbBuilder) builder = xb_builder_new();
|
|
g_autoptr(XbBuilderSource) source = xb_builder_source_new();
|
|
g_autoptr(XbSilo) silo = NULL;
|
|
g_autoptr(XbNode) data_node = NULL;
|
|
g_autoptr(GPtrArray) action_nodes = NULL;
|
|
|
|
if (!xb_builder_source_load_bytes(source, rsp_bytes, XB_BUILDER_SOURCE_FLAG_NONE, error))
|
|
return FALSE;
|
|
xb_builder_import_source(builder, source);
|
|
silo = xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
|
|
if (silo == NULL)
|
|
return FALSE;
|
|
|
|
data_node = xb_silo_get_root(silo);
|
|
if (data_node == NULL) {
|
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing root data node");
|
|
return FALSE;
|
|
}
|
|
|
|
action_nodes = xb_node_get_children(data_node);
|
|
if (action_nodes != NULL) {
|
|
for (guint j = 0; j < action_nodes->len; j++) {
|
|
XbNode *node = g_ptr_array_index(action_nodes, j);
|
|
|
|
if (g_strcmp0(xb_node_get_element(node), "response") == 0) {
|
|
if (out_silo)
|
|
*out_silo = g_steal_pointer(&silo);
|
|
if (out_response_node)
|
|
*out_response_node = g_object_ref(node);
|
|
return TRUE;
|
|
}
|
|
|
|
if (g_strcmp0(xb_node_get_element(node), "log") == 0) {
|
|
const gchar *value_attr = xb_node_get_attr(node, "value");
|
|
if (value_attr)
|
|
g_debug("device log: %s", value_attr);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (out_silo != NULL)
|
|
*out_silo = NULL;
|
|
if (out_response_node != NULL)
|
|
*out_response_node = NULL;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_send_and_receive(FuFirehoseUpdater *self,
|
|
GByteArray *take_cmd_bytearray,
|
|
XbSilo **out_silo,
|
|
XbNode **out_response_node,
|
|
GError **error)
|
|
{
|
|
if (take_cmd_bytearray) {
|
|
const gchar *cmd_header = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<data>\n";
|
|
const gchar *cmd_trailer = "</data>";
|
|
g_autoptr(GBytes) cmd_bytes = NULL;
|
|
|
|
g_byte_array_prepend(take_cmd_bytearray,
|
|
(const guint8 *)cmd_header,
|
|
strlen(cmd_header));
|
|
g_byte_array_append(take_cmd_bytearray,
|
|
(const guint8 *)cmd_trailer,
|
|
strlen(cmd_trailer));
|
|
cmd_bytes = g_byte_array_free_to_bytes(take_cmd_bytearray);
|
|
|
|
fu_firehose_updater_log_message("writing", cmd_bytes);
|
|
if (!fu_io_channel_write_bytes(self->io_channel,
|
|
cmd_bytes,
|
|
1500,
|
|
FU_IO_CHANNEL_FLAG_FLUSH_INPUT,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to write command: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
for (guint i = 0; i < MAX_RECV_MESSAGES; i++) {
|
|
g_autoptr(GBytes) rsp_bytes = NULL;
|
|
g_autoptr(XbSilo) silo = NULL;
|
|
g_autoptr(XbNode) response_node = NULL;
|
|
|
|
rsp_bytes = fu_io_channel_read_bytes(self->io_channel,
|
|
-1,
|
|
DEFAULT_RECV_TIMEOUT_MS,
|
|
FU_IO_CHANNEL_FLAG_SINGLE_SHOT,
|
|
error);
|
|
if (rsp_bytes == NULL) {
|
|
g_prefix_error(error, "Failed to read XML message: ");
|
|
return FALSE;
|
|
}
|
|
|
|
fu_firehose_updater_log_message("reading", rsp_bytes);
|
|
if (!fu_firehose_updater_process_response(rsp_bytes,
|
|
&silo,
|
|
&response_node,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to parse XML message: ");
|
|
return FALSE;
|
|
}
|
|
|
|
if (silo != NULL && response_node != NULL) {
|
|
*out_silo = g_steal_pointer(&silo);
|
|
*out_response_node = g_steal_pointer(&response_node);
|
|
return TRUE;
|
|
}
|
|
|
|
/* continue until we get a 'response_node' */
|
|
}
|
|
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT,
|
|
"Didn't get any response in the last %d messages",
|
|
MAX_RECV_MESSAGES);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_initialize(FuFirehoseUpdater *self, GError **error)
|
|
{
|
|
guint n_msg = 0;
|
|
|
|
for (guint i = 0; i < MAX_RECV_MESSAGES; i++) {
|
|
g_autoptr(GBytes) rsp_bytes = NULL;
|
|
|
|
rsp_bytes = fu_io_channel_read_bytes(
|
|
self->io_channel,
|
|
-1,
|
|
(i == 0 ? INITIALIZE_INITIAL_TIMEOUT_MS : INITIALIZE_TIMEOUT_MS),
|
|
FU_IO_CHANNEL_FLAG_SINGLE_SHOT,
|
|
NULL);
|
|
if (rsp_bytes == NULL)
|
|
break;
|
|
|
|
fu_firehose_updater_log_message("reading", rsp_bytes);
|
|
if (!fu_firehose_updater_process_response(rsp_bytes, NULL, NULL, error)) {
|
|
g_prefix_error(error, "Failed to parse XML message: ");
|
|
return FALSE;
|
|
}
|
|
|
|
n_msg++;
|
|
}
|
|
|
|
if (n_msg == 0) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Couldn't read initial firehose messages from device");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static guint
|
|
fu_firehose_updater_configure(FuFirehoseUpdater *self, GError **error)
|
|
{
|
|
gint max_payload_size = CONFIGURE_MAX_PAYLOAD_SIZE_TO_TARGET_IN_BYTES;
|
|
|
|
for (guint i = 0; i < MAX_CONFIGURE_ATTEMPTS; i++) {
|
|
GByteArray *cmd_bytearray = NULL;
|
|
g_autoptr(XbSilo) rsp_silo = NULL;
|
|
g_autoptr(XbNode) rsp_node = NULL;
|
|
GString *cmd_str = g_string_new(NULL);
|
|
|
|
g_string_append_printf(cmd_str, "<configure");
|
|
g_string_append_printf(cmd_str, " MemoryName=\"%s\"", CONFIGURE_MEMORY_NAME);
|
|
g_string_append_printf(cmd_str, " Verbose=\"%d\"", CONFIGURE_VERBOSE);
|
|
g_string_append_printf(cmd_str,
|
|
" AlwaysValidate=\"%d\"",
|
|
CONFIGURE_ALWAYS_VALIDATE);
|
|
g_string_append_printf(cmd_str,
|
|
" MaxDigestTableSizeInBytes=\"%d\"",
|
|
CONFIGURE_MAX_DIGEST_TABLE_SIZE_IN_BYTES);
|
|
g_string_append_printf(cmd_str,
|
|
" MaxPayloadSizeToTargetInBytes=\"%d\"",
|
|
max_payload_size);
|
|
g_string_append_printf(cmd_str, " ZlpAwareHost=\"%d\"", CONFIGURE_ZLP_AWARE_HOST);
|
|
g_string_append_printf(cmd_str,
|
|
" SkipStorageInit=\"%d\"",
|
|
CONFIGURE_SKIP_STORAGE_INIT);
|
|
g_string_append_printf(cmd_str, "/>");
|
|
|
|
cmd_bytearray = g_bytes_unref_to_array(g_string_free_to_bytes(cmd_str));
|
|
|
|
if (!fu_firehose_updater_send_and_receive(self,
|
|
cmd_bytearray,
|
|
&rsp_silo,
|
|
&rsp_node,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to run configure command: ");
|
|
return 0;
|
|
}
|
|
|
|
/* retry if we're told to use a different max payload size */
|
|
if (!fu_firehose_updater_check_operation_result(rsp_node, NULL)) {
|
|
guint64 suggested_max_payload_size;
|
|
g_autoptr(XbNode) root = NULL;
|
|
|
|
root = xb_silo_get_root(rsp_silo);
|
|
suggested_max_payload_size =
|
|
xb_node_get_attr_as_uint(root, "MaxPayloadSizeToTargetInBytes");
|
|
if ((suggested_max_payload_size > G_MAXINT) ||
|
|
((gint)suggested_max_payload_size == max_payload_size)) {
|
|
break;
|
|
}
|
|
|
|
suggested_max_payload_size = max_payload_size;
|
|
continue;
|
|
}
|
|
|
|
/* if operation is successful, return the max payload size we requested */
|
|
return max_payload_size;
|
|
}
|
|
|
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Configure operation failed");
|
|
return 0;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_reset(FuFirehoseUpdater *self, GError **error)
|
|
{
|
|
guint recv_cnt = 20;
|
|
const gchar *cmd_str = "<power value=\"reset\" />";
|
|
GByteArray *cmd_bytearray = NULL;
|
|
g_autoptr(XbSilo) rsp_silo = NULL;
|
|
g_autoptr(XbNode) rsp_node = NULL;
|
|
|
|
cmd_bytearray =
|
|
g_byte_array_append(g_byte_array_new(), (const guint8 *)cmd_str, strlen(cmd_str));
|
|
if (!fu_firehose_updater_send_and_receive(self,
|
|
cmd_bytearray,
|
|
&rsp_silo,
|
|
&rsp_node,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to run reset command: ");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_firehose_updater_check_operation_result(rsp_node, NULL)) {
|
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Reset operation failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* read out all of the remaining messages. otherwise modem won't go into reset */
|
|
while (--recv_cnt &&
|
|
fu_firehose_updater_send_and_receive(self, NULL, &rsp_silo, &rsp_node, NULL))
|
|
;
|
|
|
|
g_warn_if_fail(recv_cnt > 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_send_program_file(FuFirehoseUpdater *self,
|
|
const gchar *program_filename,
|
|
GBytes *program_file,
|
|
guint payload_size,
|
|
guint sector_size,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
FuChunk *chk;
|
|
|
|
chunks = fu_chunk_array_new_from_bytes(program_file, 0, 0, payload_size);
|
|
/* last block needs to be padded to the next payload_size,
|
|
* so that we always send full sectors */
|
|
chk = g_ptr_array_index(chunks, chunks->len - 1);
|
|
if (fu_chunk_get_data_sz(chk) != payload_size) {
|
|
g_autoptr(GBytes) padded_bytes = NULL;
|
|
g_autofree guint8 *padded_block = g_malloc0(payload_size);
|
|
g_return_val_if_fail(padded_block != NULL, FALSE);
|
|
memcpy(padded_block, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk));
|
|
padded_bytes = g_bytes_new(padded_block, payload_size);
|
|
fu_chunk_set_bytes(chk, padded_bytes);
|
|
g_return_val_if_fail(fu_chunk_get_data_sz(chk) == payload_size, FALSE);
|
|
}
|
|
for (guint i = 0; i < chunks->len; i++) {
|
|
chk = g_ptr_array_index(chunks, i);
|
|
|
|
/* log only in blocks of 250 plus first/last */
|
|
if (i == 0 || i == (chunks->len - 1) || (i + 1) % 250 == 0)
|
|
g_debug("sending %u bytes in block %u/%u of file '%s'",
|
|
fu_chunk_get_data_sz(chk),
|
|
i + 1,
|
|
chunks->len,
|
|
program_filename);
|
|
|
|
if (!fu_io_channel_write_bytes(self->io_channel,
|
|
fu_chunk_get_bytes(chk),
|
|
1500,
|
|
FU_IO_CHANNEL_FLAG_FLUSH_INPUT,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"Failed to write block %u/%u of file '%s': ",
|
|
i + 1,
|
|
chunks->len,
|
|
program_filename);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_actions_validate(GPtrArray *action_nodes,
|
|
guint max_payload_size,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail(action_nodes != NULL, FALSE);
|
|
|
|
for (guint i = 0; i < action_nodes->len; i++) {
|
|
const gchar *name = NULL;
|
|
const gchar *program_filename = NULL;
|
|
GBytes *program_file = NULL;
|
|
guint64 program_sector_size_in_bytes = 0;
|
|
|
|
XbNode *node = g_ptr_array_index(action_nodes, i);
|
|
const gchar *action = xb_node_get_element(node);
|
|
|
|
if (g_strcmp0(action, "program") != 0)
|
|
continue;
|
|
|
|
name = "fwupd:ProgramFile";
|
|
program_file = xb_node_get_data(node, name);
|
|
if (program_file == NULL) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed to validate program file '%s' command: "
|
|
"failed to get %s",
|
|
program_filename,
|
|
name);
|
|
return FALSE;
|
|
}
|
|
name = "filename";
|
|
program_filename = xb_node_get_attr(node, name);
|
|
if (program_filename == NULL) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed to validate program file '%s' command: "
|
|
"failed to get %s",
|
|
program_filename,
|
|
name);
|
|
return FALSE;
|
|
}
|
|
name = "SECTOR_SIZE_IN_BYTES";
|
|
program_sector_size_in_bytes = xb_node_get_attr_as_uint(node, name);
|
|
if (program_sector_size_in_bytes > max_payload_size) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed to validate program file '%s' command: "
|
|
"requested sector size bigger (%" G_GUINT64_FORMAT " bytes) "
|
|
"than maximum payload size agreed with device (%u bytes)",
|
|
program_filename,
|
|
program_sector_size_in_bytes,
|
|
max_payload_size);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gsize
|
|
fu_firehose_updater_actions_get_total_file_size(GPtrArray *action_nodes)
|
|
{
|
|
gsize total_bytes = 0;
|
|
g_return_val_if_fail(action_nodes != NULL, 0);
|
|
|
|
for (guint i = 0; i < action_nodes->len; i++) {
|
|
GBytes *program_file = NULL;
|
|
XbNode *node = g_ptr_array_index(action_nodes, i);
|
|
const gchar *action = xb_node_get_element(node);
|
|
|
|
if (g_strcmp0(action, "program") != 0)
|
|
continue;
|
|
|
|
program_file = xb_node_get_data(node, "fwupd:ProgramFile");
|
|
|
|
if (program_file != NULL)
|
|
total_bytes += g_bytes_get_size(program_file);
|
|
}
|
|
|
|
return total_bytes;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_run_action_program(FuFirehoseUpdater *self,
|
|
XbNode *node,
|
|
gboolean rawmode,
|
|
guint max_payload_size,
|
|
gsize *sent_bytes,
|
|
GError **error)
|
|
{
|
|
GBytes *program_file = NULL;
|
|
const gchar *program_filename = NULL;
|
|
guint64 program_sector_size = 0;
|
|
guint payload_size = 0;
|
|
g_autoptr(XbSilo) rsp_silo = NULL;
|
|
g_autoptr(XbNode) rsp_node = NULL;
|
|
|
|
program_file = xb_node_get_data(node, "fwupd:ProgramFile");
|
|
if (program_file == NULL)
|
|
return FALSE;
|
|
program_filename = xb_node_get_attr(node, "filename");
|
|
if (program_filename == NULL)
|
|
return FALSE;
|
|
program_sector_size = xb_node_get_attr_as_uint(node, "SECTOR_SIZE_IN_BYTES");
|
|
if (program_sector_size == G_MAXUINT64)
|
|
return FALSE;
|
|
|
|
if (rawmode == FALSE) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed to download program file '%s': rawmode not enabled",
|
|
program_filename);
|
|
return FALSE;
|
|
}
|
|
|
|
while ((payload_size + (guint)program_sector_size) < max_payload_size)
|
|
payload_size += (guint)program_sector_size;
|
|
|
|
g_debug("sending program file '%s' (%zu bytes)",
|
|
program_filename,
|
|
g_bytes_get_size(program_file));
|
|
if (!fu_firehose_updater_send_program_file(self,
|
|
program_filename,
|
|
program_file,
|
|
payload_size,
|
|
program_sector_size,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to send program file '%s': ", program_filename);
|
|
return FALSE;
|
|
}
|
|
|
|
g_debug("waiting for program file download confirmation...");
|
|
if (!fu_firehose_updater_send_and_receive(self, NULL, &rsp_silo, &rsp_node, error)) {
|
|
g_prefix_error(error,
|
|
"Download confirmation not received for file '%s': ",
|
|
program_filename);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_firehose_updater_check_operation_result(rsp_node, &rawmode)) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Download confirmation failed for file '%s'",
|
|
program_filename);
|
|
return FALSE;
|
|
}
|
|
|
|
if (rawmode != FALSE) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Download confirmation failed for file '%s': rawmode still enabled",
|
|
program_filename);
|
|
return FALSE;
|
|
}
|
|
|
|
if (sent_bytes != NULL)
|
|
*sent_bytes += g_bytes_get_size(program_file);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_run_action(FuFirehoseUpdater *self,
|
|
XbNode *node,
|
|
guint max_payload_size,
|
|
gsize *sent_bytes,
|
|
GError **error)
|
|
{
|
|
const gchar *action;
|
|
gchar *cmd_str = NULL;
|
|
gboolean rawmode = FALSE;
|
|
GByteArray *cmd_bytearray = NULL;
|
|
g_autoptr(XbSilo) rsp_silo = NULL;
|
|
g_autoptr(XbNode) rsp_node = NULL;
|
|
|
|
action = xb_node_get_element(node);
|
|
|
|
#if LIBXMLB_CHECK_VERSION(0, 2, 2)
|
|
cmd_str = xb_node_export(node, XB_NODE_EXPORT_FLAG_COLLAPSE_EMPTY, error);
|
|
#else
|
|
cmd_str = xb_node_export(node, XB_NODE_EXPORT_FLAG_NONE, error);
|
|
#endif
|
|
if (cmd_str == NULL)
|
|
return FALSE;
|
|
cmd_bytearray = g_byte_array_new_take((guint8 *)cmd_str, strlen(cmd_str));
|
|
|
|
g_debug("running command '%s'...", action);
|
|
if (!fu_firehose_updater_send_and_receive(self,
|
|
cmd_bytearray,
|
|
&rsp_silo,
|
|
&rsp_node,
|
|
error)) {
|
|
g_prefix_error(error, "Failed to run command '%s': ", action);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!fu_firehose_updater_check_operation_result(rsp_node, &rawmode)) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command '%s' failed", action);
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_strcmp0(action, "program") == 0)
|
|
return fu_firehose_updater_run_action_program(self,
|
|
node,
|
|
rawmode,
|
|
max_payload_size,
|
|
sent_bytes,
|
|
error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_firehose_updater_run_actions(FuFirehoseUpdater *self,
|
|
XbSilo *silo,
|
|
GPtrArray *action_nodes,
|
|
guint max_payload_size,
|
|
GError **error)
|
|
{
|
|
gsize sent_bytes = 0;
|
|
gsize total_bytes = 0;
|
|
|
|
g_warn_if_fail(action_nodes->len > 0);
|
|
|
|
if (!fu_firehose_updater_actions_validate(action_nodes, max_payload_size, error))
|
|
return FALSE;
|
|
|
|
total_bytes = fu_firehose_updater_actions_get_total_file_size(action_nodes);
|
|
|
|
for (guint i = 0; i < action_nodes->len; i++) {
|
|
XbNode *node = g_ptr_array_index(action_nodes, i);
|
|
|
|
if (!fu_firehose_updater_run_action(self,
|
|
node,
|
|
max_payload_size,
|
|
&sent_bytes,
|
|
error))
|
|
return FALSE;
|
|
|
|
g_signal_emit(self,
|
|
signals[SIGNAL_WRITE_PERCENTAGE],
|
|
0,
|
|
(guint)((100.0 * (gdouble)sent_bytes) / (gdouble)total_bytes));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_firehose_updater_write(FuFirehoseUpdater *self,
|
|
XbSilo *silo,
|
|
GPtrArray *action_nodes,
|
|
GError **error)
|
|
{
|
|
guint max_payload_size;
|
|
gboolean result;
|
|
g_autoptr(GError) error_local = NULL;
|
|
|
|
g_signal_emit(self, signals[SIGNAL_WRITE_PERCENTAGE], 0, 0);
|
|
|
|
if (!fu_firehose_updater_initialize(self, error))
|
|
return FALSE;
|
|
|
|
max_payload_size = fu_firehose_updater_configure(self, error);
|
|
if (max_payload_size == 0)
|
|
return FALSE;
|
|
|
|
result = fu_firehose_updater_run_actions(self, silo, action_nodes, max_payload_size, error);
|
|
|
|
if (!fu_firehose_updater_reset(self, &error_local)) {
|
|
if (result)
|
|
g_propagate_error(error, g_steal_pointer(&error_local));
|
|
return FALSE;
|
|
}
|
|
|
|
g_signal_emit(self, signals[SIGNAL_WRITE_PERCENTAGE], 0, 100);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
fu_firehose_updater_init(FuFirehoseUpdater *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_firehose_updater_finalize(GObject *object)
|
|
{
|
|
FuFirehoseUpdater *self = FU_FIREHOSE_UPDATER(object);
|
|
g_warn_if_fail(self->io_channel == NULL);
|
|
g_free(self->port);
|
|
G_OBJECT_CLASS(fu_firehose_updater_parent_class)->finalize(object);
|
|
}
|
|
|
|
static void
|
|
fu_firehose_updater_class_init(FuFirehoseUpdaterClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
|
object_class->finalize = fu_firehose_updater_finalize;
|
|
|
|
signals[SIGNAL_WRITE_PERCENTAGE] = g_signal_new("write-percentage",
|
|
G_TYPE_FROM_CLASS(object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__UINT,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_UINT);
|
|
}
|
|
|
|
FuFirehoseUpdater *
|
|
fu_firehose_updater_new(const gchar *port)
|
|
{
|
|
FuFirehoseUpdater *self = g_object_new(FU_TYPE_FIREHOSE_UPDATER, NULL);
|
|
self->port = g_strdup(port);
|
|
return self;
|
|
}
|