mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-10 04:51:39 +00:00
753 lines
24 KiB
C
753 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;
|
|
}
|