Add fwu copy_installer
The copy_installer can be used to copy the contents from a source
volume into a destination volume. It is intended to be used to
handle the case where an incoming update package only describes
a partial update. Images not included in the update need to be
installed into the update bank using a copy of the previously
active bank.
Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: Ibd2778b6efa5533adfb5fb309e2661e743d2dbff
diff --git a/components/service/fwu/installer/copy/component.cmake b/components/service/fwu/installer/copy/component.cmake
new file mode 100644
index 0000000..6703209
--- /dev/null
+++ b/components/service/fwu/installer/copy/component.cmake
@@ -0,0 +1,16 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+ message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/copy_installer.c"
+ )
+
+target_compile_definitions(${TGT} PRIVATE
+ COPY_INSTALLER_AVAILABLE)
\ No newline at end of file
diff --git a/components/service/fwu/installer/copy/copy_installer.c b/components/service/fwu/installer/copy/copy_installer.c
new file mode 100644
index 0000000..3ef19f3
--- /dev/null
+++ b/components/service/fwu/installer/copy/copy_installer.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <util.h>
+#include <media/volume/index/volume_index.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include "copy_installer.h"
+
+#define COPY_CHUNK_SIZE (4096)
+
+
+static int close_volumes_on_error(
+ struct copy_installer *subject)
+{
+ volume_close(subject->source_volume);
+ volume_close(subject->destination_volume);
+
+ return FWU_STATUS_UNKNOWN;
+}
+
+static int copy_volume_contents(
+ struct copy_installer *subject,
+ size_t target_copy_len)
+{
+ int status = FWU_STATUS_SUCCESS;
+ size_t copy_len = 0;
+ uint8_t *copy_buf = malloc(COPY_CHUNK_SIZE);
+
+ if (!copy_buf)
+ return FWU_STATUS_UNKNOWN;
+
+ while (copy_len < target_copy_len) {
+
+ size_t actual_read_len = 0;
+ size_t actual_write_len = 0;
+ size_t remaining_len = target_copy_len - copy_len;
+ size_t requested_read_len = (remaining_len < COPY_CHUNK_SIZE) ?
+ remaining_len : COPY_CHUNK_SIZE;
+
+ status = volume_read(
+ subject->source_volume,
+ (uintptr_t)copy_buf,
+ requested_read_len,
+ &actual_read_len);
+
+ if (status)
+ break;
+
+ status = volume_write(
+ subject->destination_volume,
+ (const uintptr_t)copy_buf,
+ actual_read_len,
+ &actual_write_len);
+
+ if (status)
+ break;
+
+ if (actual_read_len != actual_write_len) {
+ status = FWU_STATUS_UNKNOWN;
+ break;
+ }
+
+ copy_len += actual_read_len;
+ }
+
+ free(copy_buf);
+
+ return status;
+}
+
+static int copy_installer_begin(void *context,
+ unsigned int current_volume_id,
+ unsigned int update_volume_id)
+{
+ struct copy_installer *subject = (struct copy_installer *)context;
+
+ int status = volume_index_find(
+ update_volume_id,
+ &subject->destination_volume);
+
+ if (status == 0) {
+
+ status = volume_index_find(
+ current_volume_id,
+ &subject->source_volume);
+ }
+
+ if (status != 0) {
+
+ subject->destination_volume = NULL;
+ subject->source_volume = NULL;
+ }
+
+ return status;
+}
+
+static int copy_installer_finalize(void *context)
+{
+ struct copy_installer *subject = (struct copy_installer *)context;
+
+ assert(subject->source_volume);
+ assert(subject->destination_volume);
+
+ /* Open the source and destination volumes */
+ int source_status = volume_open(subject->source_volume);
+
+ if (source_status)
+ return source_status;
+
+ int destination_status = volume_open(subject->destination_volume);
+
+ if (destination_status) {
+
+ volume_close(subject->source_volume);
+ return destination_status;
+ }
+
+ /* Prepare the destination volume for writes */
+ destination_status = volume_erase(subject->destination_volume);
+
+ if (destination_status)
+ return close_volumes_on_error(subject);
+
+ /* Determine how much to copy */
+ size_t source_size = 0;
+ size_t destination_size = 0;
+
+ source_status = volume_size(subject->source_volume, &source_size);
+
+ if (source_status)
+ return close_volumes_on_error(subject);
+
+ destination_status = volume_size(subject->source_volume, &destination_size);
+
+ if (destination_status)
+ return close_volumes_on_error(subject);
+
+ /* Perform the copy */
+ int copy_status = copy_volume_contents(subject, MIN(source_size, destination_size));
+
+ /* All done */
+ source_status = volume_close(subject->source_volume);
+ destination_status = volume_close(subject->destination_volume);
+
+ return
+ (copy_status) ? copy_status :
+ (destination_status) ? destination_status :
+ (source_status) ? source_status :
+ FWU_STATUS_SUCCESS;
+}
+
+static void copy_installer_abort(void *context)
+{
+ struct copy_installer *subject = (struct copy_installer *)context;
+
+ subject->source_volume = NULL;
+ subject->destination_volume = NULL;
+}
+
+static int copy_installer_open(void *context,
+ const struct image_info *image_info)
+{
+ (void)context;
+ (void)image_info;
+
+ return FWU_STATUS_DENIED;
+}
+
+static int copy_installer_commit(void *context)
+{
+ (void)context;
+
+ return FWU_STATUS_DENIED;
+}
+
+static int copy_installer_write(void *context,
+ const uint8_t *data,
+ size_t data_len)
+{
+ (void)context;
+ (void)data;
+ (void)data_len;
+
+ return FWU_STATUS_DENIED;
+}
+
+static int copy_installer_enumerate(void *context,
+ uint32_t volume_id,
+ struct fw_directory *fw_directory)
+{
+ (void)volume_id;
+ (void)fw_directory;
+
+ /* A copy_installer can never be used to install externally provided images
+ * don't advertise any images via the fw_directory. Just quietly return success.
+ */
+ return FWU_STATUS_SUCCESS;
+}
+
+void copy_installer_init(struct copy_installer *subject,
+ const struct uuid_octets *location_uuid,
+ uint32_t location_id)
+{
+ /* Define concrete installer interface */
+ static const struct installer_interface interface = {
+ copy_installer_begin,
+ copy_installer_finalize,
+ copy_installer_abort,
+ copy_installer_open,
+ copy_installer_commit,
+ copy_installer_write,
+ copy_installer_enumerate
+ };
+
+ /* Initialize base installer - a copy_installer is a type of
+ * installer that always updates a whole volume by copying
+ * from another.
+ */
+ installer_init(&subject->base_installer,
+ INSTALL_TYPE_WHOLE_VOLUME_COPY,
+ location_id,
+ location_uuid,
+ subject, &interface);
+
+ /* Initialize copy_installer specifics */
+ subject->source_volume = NULL;
+ subject->destination_volume = NULL;
+}
+
+void copy_installer_deinit(struct copy_installer *subject)
+{
+ (void)subject;
+}
diff --git a/components/service/fwu/installer/copy/copy_installer.h b/components/service/fwu/installer/copy/copy_installer.h
new file mode 100644
index 0000000..c59745b
--- /dev/null
+++ b/components/service/fwu/installer/copy/copy_installer.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FWU_COPY_INSTALLER_H
+#define FWU_COPY_INSTALLER_H
+
+#include <stdint.h>
+#include <service/fwu/installer/installer.h>
+#include <media/volume/volume.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief copy_installer structure definition
+ *
+ * A copy_installer is an installer that copies previously installed images
+ * from a source volume into a destination volume. A copy_installer does not
+ * consume externally provided installation data. A copy_installer may be
+ * used in situations where an update has resulted in no change to the
+ * firmware for a subsystem (location) but it is necessary to copy image
+ * data to the update bank. The copy_installer has no knowledge of the
+ * actual size of image data that needs to copied so the entire source
+ * volume is copied to the destination. This will potentially be wasteful
+ * in that unnecessary data may be copied.
+ */
+struct copy_installer {
+ struct installer base_installer;
+ struct volume *destination_volume;
+ struct volume *source_volume;
+};
+
+/**
+ * \brief Initialize a copy_installer
+ *
+ * \param[in] subject The subject copy_installer
+ * \param[in] location_uuid The associated location UUID
+ * \param[in] location_id Identifies where to install qualifying images
+ */
+void copy_installer_init(struct copy_installer *subject,
+ const struct uuid_octets *location_uuid,
+ uint32_t location_id);
+
+/**
+ * \brief De-initialize a copy_installer
+ *
+ * \param[in] subject The subject copy_installer
+ */
+void copy_installer_deinit(struct copy_installer *subject);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FWU_COPY_INSTALLER_H */
diff --git a/components/service/fwu/installer/copy/test/component.cmake b/components/service/fwu/installer/copy/test/component.cmake
new file mode 100644
index 0000000..61a12ad
--- /dev/null
+++ b/components/service/fwu/installer/copy/test/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+if (NOT DEFINED TGT)
+ message(FATAL_ERROR "mandatory parameter TGT is not defined.")
+endif()
+
+target_sources(${TGT} PRIVATE
+ "${CMAKE_CURRENT_LIST_DIR}/copy_installer_tests.cpp"
+ )
diff --git a/components/service/fwu/installer/copy/test/copy_installer_tests.cpp b/components/service/fwu/installer/copy/test/copy_installer_tests.cpp
new file mode 100644
index 0000000..3a51033
--- /dev/null
+++ b/components/service/fwu/installer/copy/test/copy_installer_tests.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cstdlib>
+#include <cstring>
+#include <common/uuid/uuid.h>
+#include <media/disk/guid.h>
+#include <media/volume/block_volume/block_volume.h>
+#include <media/volume/index/volume_index.h>
+#include <media/volume/volume.h>
+#include <service/block_storage/factory/ref_ram_gpt/block_store_factory.h>
+#include <service/block_storage/config/ref/ref_partition_configurator.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <service/fwu/installer/copy/copy_installer.h>
+#include <service/fwu/installer/raw/raw_installer.h>
+#include <service/fwu/installer/installer_index.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include <CppUTest/TestHarness.h>
+
+TEST_GROUP(FwuCopyInstallerTests)
+{
+ void setup()
+ {
+ int result;
+ struct uuid_octets partition_guid;
+
+ m_image = NULL;
+
+ installer_index_init();
+ volume_index_init();
+
+ /* Use the reference disk configuration and use partition 1 & 2 as
+ * storage for A and B firmware banks.
+ */
+ m_block_store = ref_ram_gpt_block_store_factory_create();
+
+ /* Construct fw volume A */
+ uuid_guid_octets_from_canonical(&partition_guid, REF_PARTITION_1_GUID);
+
+ result = block_volume_init(&m_block_volume_a,
+ m_block_store, &partition_guid,
+ &m_fw_volume_a);
+
+ LONGS_EQUAL(0, result);
+ CHECK_TRUE(m_fw_volume_a);
+
+ /* Construct fw volume B */
+ uuid_guid_octets_from_canonical(&partition_guid, REF_PARTITION_2_GUID);
+
+ result = block_volume_init(&m_block_volume_b,
+ m_block_store, &partition_guid,
+ &m_fw_volume_b);
+
+ LONGS_EQUAL(0, result);
+ CHECK_TRUE(m_fw_volume_b);
+
+ /* Prepare an image_info structure to describe the image to
+ * install into one of volumes.
+ */
+ uuid_guid_octets_from_canonical(&m_image_info.img_type_uuid,
+ "1c22ca2c-9732-49e6-ba3b-eed40e27fda3");
+
+ m_image_info.max_size =
+ (REF_PARTITION_1_ENDING_LBA - REF_PARTITION_1_STARTING_LBA + 1) *
+ REF_PARTITION_BLOCK_SIZE;
+ m_image_info.lowest_accepted_version = 1;
+ m_image_info.active_version = 1;
+ m_image_info.permissions = 0;
+ m_image_info.image_index = 0;
+ m_image_info.location_id = FW_STORE_LOCATION_ID;
+ m_image_info.install_type = INSTALL_TYPE_WHOLE_VOLUME;
+
+ /* Mimic a platform configuration where storage volumes are assigned
+ * location and usage IDs. These usage IDs correspond to an A/B banked
+ * firmware store.
+ */
+ volume_index_add(
+ banked_volume_id(FW_STORE_LOCATION_ID, BANKED_USAGE_ID_FW_BANK_A),
+ m_fw_volume_a);
+ volume_index_add(
+ banked_volume_id(FW_STORE_LOCATION_ID, BANKED_USAGE_ID_FW_BANK_B),
+ m_fw_volume_b);
+
+ /* A platform configuration will also determine which installers are
+ * assigned to which locations. For these tests, there is a raw_installer
+ * to install the initial image and a copy_installer to install a copy
+ * into the other bank.
+ */
+ raw_installer_init(&m_raw_installer,
+ &m_image_info.img_type_uuid, FW_STORE_LOCATION_ID);
+ installer_index_register(&m_raw_installer.base_installer);
+
+ copy_installer_init(&m_copy_installer,
+ &m_image_info.img_type_uuid, FW_STORE_LOCATION_ID);
+ installer_index_register(&m_copy_installer.base_installer);
+ }
+
+ void teardown()
+ {
+ delete[] m_image;
+
+ raw_installer_deinit(&m_raw_installer);
+ copy_installer_deinit(&m_copy_installer);
+
+ installer_index_clear();
+ volume_index_clear();
+
+ block_volume_deinit(&m_block_volume_a);
+ block_volume_deinit(&m_block_volume_b);
+ ref_ram_gpt_block_store_factory_destroy(m_block_store);
+ }
+
+ void create_image(size_t len)
+ {
+ m_image = new uint8_t[len];
+ m_image_len = len;
+
+ for (size_t i = 0; i < len; i++)
+ m_image[i] = (uint8_t)rand();
+ }
+
+ void install_initial_image(size_t len)
+ {
+ create_image(len);
+
+ /* Expect to find a suitable installer for the given image_info */
+ struct installer *installer = installer_index_find(
+ m_image_info.install_type, m_image_info.location_id);
+ CHECK_TRUE(installer);
+ UNSIGNED_LONGS_EQUAL(FW_STORE_LOCATION_ID, installer->location_id);
+
+ /* Begin installation transaction - installing into volume A */
+ int status = installer_begin(installer,
+ banked_volume_id(FW_STORE_LOCATION_ID,
+ BANKED_USAGE_ID_FW_BANK_B), /* Current volume */
+ banked_volume_id(FW_STORE_LOCATION_ID,
+ BANKED_USAGE_ID_FW_BANK_A)); /* Update volume */
+ LONGS_EQUAL(0, status);
+
+ status = installer_open(installer, &m_image_info);
+ LONGS_EQUAL(0, status);
+
+ status = installer_write(installer, m_image, m_image_len);
+ LONGS_EQUAL(0, status);
+
+ status = installer_commit(installer);
+ LONGS_EQUAL(0, status);
+
+ status = installer_finalize(installer);
+ LONGS_EQUAL(0, status);
+
+ check_update_installed(m_fw_volume_a);
+ }
+
+ void check_update_installed(struct volume *volume)
+ {
+ int status = 0;
+ size_t total_read = 0;
+
+ status = volume_open(volume);
+ LONGS_EQUAL(0, status);
+
+ while (total_read < m_image_len) {
+
+ uint8_t read_buf[1000];
+ size_t len_read = 0;
+ size_t bytes_remaining = m_image_len - total_read;
+ size_t req_len = (bytes_remaining > sizeof(read_buf)) ?
+ sizeof(read_buf) : bytes_remaining;
+
+ memset(read_buf, 0, sizeof(read_buf));
+
+ status = volume_read(volume,
+ (uintptr_t)read_buf, req_len,
+ &len_read);
+ LONGS_EQUAL(0, status);
+ UNSIGNED_LONGS_EQUAL(req_len, len_read);
+
+ MEMCMP_EQUAL(&m_image[total_read], read_buf, len_read);
+
+ total_read += len_read;
+ }
+
+ status = volume_close(volume);
+ LONGS_EQUAL(0, status);
+ }
+
+
+ static const unsigned int FW_STORE_LOCATION_ID = 0x100;
+
+ struct block_store *m_block_store;
+ struct block_volume m_block_volume_a;
+ struct block_volume m_block_volume_b;
+ struct volume *m_fw_volume_a;
+ struct volume *m_fw_volume_b;
+ struct raw_installer m_raw_installer;
+ struct copy_installer m_copy_installer;
+ struct image_info m_image_info;
+ uint8_t *m_image;
+ size_t m_image_len;
+};
+
+TEST(FwuCopyInstallerTests, installAndCopy)
+{
+ /* Install an arbitrary size image into bank A */
+ install_initial_image(13011);
+
+ /* Expect to find a suitable copy installer */
+ struct installer *installer = installer_index_find(
+ INSTALL_TYPE_WHOLE_VOLUME_COPY, FW_STORE_LOCATION_ID);
+ CHECK_TRUE(installer);
+ UNSIGNED_LONGS_EQUAL(FW_STORE_LOCATION_ID, installer->location_id);
+
+ /* Begin installation transaction - installing into volume B */
+ int status = installer_begin(installer,
+ banked_volume_id(FW_STORE_LOCATION_ID,
+ BANKED_USAGE_ID_FW_BANK_A), /* Current volume */
+ banked_volume_id(FW_STORE_LOCATION_ID,
+ BANKED_USAGE_ID_FW_BANK_B)); /* Update volume */
+ LONGS_EQUAL(0, status);
+
+ /* Finalize the installation - the copy should happen here */
+ status = installer_finalize(installer);
+ LONGS_EQUAL(0, status);
+
+ /* Expect volume B to contain a copy of what's in volume A */
+ check_update_installed(m_fw_volume_b);
+}
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index f0aaa8c..13077f1 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -115,6 +115,8 @@
"components/service/fwu/installer"
"components/service/fwu/installer/raw"
"components/service/fwu/installer/raw/test"
+ "components/service/fwu/installer/copy"
+ "components/service/fwu/installer/copy/test"
"components/service/fwu/inspector/mock"
"components/service/crypto/client/cpp"
"components/service/crypto/client/cpp/protocol/protobuf"