Add fwu raw_installer
The raw_installer can be used to install a raw image directly into
a storage volume. The raw_installer can be used for say installing
a whole firmware update contained within a single image into target
storage such as a flash partition.
Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I78de31ffdd8185adaf91c98865f457776cada2a5
diff --git a/components/service/fwu/installer/raw/component.cmake b/components/service/fwu/installer/raw/component.cmake
new file mode 100644
index 0000000..47c4cbb
--- /dev/null
+++ b/components/service/fwu/installer/raw/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}/raw_installer.c"
+ )
+
+target_compile_definitions(${TGT} PRIVATE
+ RAW_INSTALLER_AVAILABLE)
\ No newline at end of file
diff --git a/components/service/fwu/installer/raw/raw_installer.c b/components/service/fwu/installer/raw/raw_installer.c
new file mode 100644
index 0000000..55cdcff
--- /dev/null
+++ b/components/service/fwu/installer/raw/raw_installer.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <media/volume/index/volume_index.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include "raw_installer.h"
+
+
+static int raw_installer_begin(void *context,
+ unsigned int current_volume_id,
+ unsigned int update_volume_id)
+{
+ struct raw_installer *subject = (struct raw_installer *)context;
+
+ (void)current_volume_id;
+
+ int status = volume_index_find(
+ update_volume_id,
+ &subject->target_volume);
+
+ if (status == 0) {
+
+ assert(subject->target_volume);
+
+ subject->commit_count = 0;
+ subject->is_open = false;
+ }
+
+ return status;
+}
+
+static int raw_installer_finalize(void *context)
+{
+ struct raw_installer *subject = (struct raw_installer *)context;
+
+ /* Close volume if left open */
+ if (subject->is_open) {
+
+ assert(subject->target_volume);
+
+ volume_close(subject->target_volume);
+ subject->is_open = false;
+ }
+
+ return FWU_STATUS_SUCCESS;
+}
+
+static void raw_installer_abort(void *context)
+{
+ raw_installer_finalize(context);
+}
+
+static int raw_installer_open(void *context,
+ const struct image_info *image_info)
+{
+ struct raw_installer *subject = (struct raw_installer *)context;
+ int status = FWU_STATUS_DENIED;
+
+ /* Because the raw_installer uses a single image to update the
+ * target volume, it only makes sense to commit a single image
+ * during an update transaction. Defend against the case where
+ * an input update package contains more than one raw image to
+ * install into a particular location.
+ */
+ if (!subject->is_open && subject->commit_count < 1) {
+
+ assert(subject->target_volume);
+
+ status = volume_open(subject->target_volume);
+
+ if (!status) {
+
+ /* Prior to writing to the volume to install the image, ensure
+ * that the volume is erased.
+ */
+ status = volume_erase(subject->target_volume);
+
+ if (!status) {
+
+ subject->is_open = true;
+ subject->bytes_written = 0;
+ } else {
+ /* Failed to erase */
+ volume_close(subject->target_volume);
+ }
+ }
+ }
+
+ return status;
+}
+
+static int raw_installer_commit(void *context)
+{
+ struct raw_installer *subject = (struct raw_installer *)context;
+ int status = FWU_STATUS_DENIED;
+
+ if (subject->is_open) {
+
+ assert(subject->target_volume);
+
+ status = volume_close(subject->target_volume);
+
+ ++subject->commit_count;
+ subject->is_open = false;
+
+ if (!status && !subject->bytes_written) {
+
+ /* Installing a zero length image can imply an image delete
+ * operation. For certain types of installer, this is a legitimate
+ * operation. For a raw_installer, there really is no way to
+ * delete an image so return an error if an attempt was made.
+ */
+ status = FWU_STATUS_NOT_AVAILABLE;
+ }
+ }
+
+ return status;
+}
+
+static int raw_installer_write(void *context,
+ const uint8_t *data,
+ size_t data_len)
+{
+ struct raw_installer *subject = (struct raw_installer *)context;
+ int status = FWU_STATUS_DENIED;
+
+ if (subject->is_open) {
+
+ assert(subject->target_volume);
+
+ size_t len_written = 0;
+
+ status = volume_write(subject->target_volume,
+ (const uintptr_t)data, data_len,
+ &len_written);
+
+ subject->bytes_written += len_written;
+
+ /* Check for the volume full condition where not all the requested
+ * data was written.
+ */
+ if (!status && (len_written != data_len))
+ status = FWU_STATUS_OUT_OF_BOUNDS;
+ }
+
+ return status;
+}
+
+static int raw_installer_enumerate(void *context,
+ uint32_t volume_id,
+ struct fw_directory *fw_directory)
+{
+ struct raw_installer *subject = (struct raw_installer *)context;
+ struct volume *volume = NULL;
+
+ int status = volume_index_find(volume_id, &volume);
+
+ if (status != 0)
+ return status;
+
+ assert(volume);
+
+ /* Found the active volume so query it for information in order to
+ * prepare an entry in the fw_directory to represent the whole volume
+ * as an advertised updatable image.
+ */
+ struct image_info image_info = {0};
+
+ /* Limit the advertised max size to the volume size. The volume needs
+ * to be open to query its size.
+ */
+ if (!subject->is_open) {
+ /* Open if necessary */
+ status = volume_open(volume);
+ if (status != 0)
+ return status;
+ }
+
+ status = volume_size(volume, &image_info.max_size);
+ if (status != 0)
+ return status;
+
+ if (!subject->is_open) {
+ /* Leave volume in the same open state */
+ status = volume_close(volume);
+ if (status)
+ return status;
+ }
+
+ /* These attributes will have been assigned during platform configuration */
+ image_info.img_type_uuid = subject->base_installer.location_uuid;
+ image_info.location_id = subject->base_installer.location_id;
+ image_info.install_type = subject->base_installer.install_type;
+
+ status = fw_directory_add_image_info(fw_directory, &image_info);
+
+ return status;
+}
+
+void raw_installer_init(struct raw_installer *subject,
+ const struct uuid_octets *location_uuid,
+ uint32_t location_id)
+{
+ /* Define concrete installer interface */
+ static const struct installer_interface interface = {
+ raw_installer_begin,
+ raw_installer_finalize,
+ raw_installer_abort,
+ raw_installer_open,
+ raw_installer_commit,
+ raw_installer_write,
+ raw_installer_enumerate
+ };
+
+ /* Initialize base installer - a raw_installer is a type of
+ * installer that always updates a whole volume.
+ */
+ installer_init(&subject->base_installer,
+ INSTALL_TYPE_WHOLE_VOLUME,
+ location_id,
+ location_uuid,
+ subject, &interface);
+
+ /* Initialize raw_installer specifics */
+ subject->target_volume = NULL;
+ subject->commit_count = 0;
+ subject->bytes_written = 0;
+ subject->is_open = false;
+}
+
+void raw_installer_deinit(struct raw_installer *subject)
+{
+ (void)subject;
+}
diff --git a/components/service/fwu/installer/raw/raw_installer.h b/components/service/fwu/installer/raw/raw_installer.h
new file mode 100644
index 0000000..d82cbc2
--- /dev/null
+++ b/components/service/fwu/installer/raw/raw_installer.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FWU_RAW_INSTALLER_H
+#define FWU_RAW_INSTALLER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <common/uuid/uuid.h>
+#include <service/fwu/installer/installer.h>
+#include <media/volume/volume.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief raw_installer structure definition
+ *
+ * A raw_installer is an installer that takes the raw input image and writes
+ * it directly to the target storage volume with no intermediate processing.
+ * The raw_installer can be used for say installing a complete firmware image
+ * into a single volume. Because the raw_installer has no knowledge of the
+ * format of images that it installs, the assigned location_uuid is used
+ * by the enumerate method to advertise a single updatable image that
+ * corresponds to the entire raw contents of the associated target
+ * volume. Other sub-volume installers may advertise additional updatable
+ * images that reside within the same target volume.
+ */
+struct raw_installer {
+ struct installer base_installer;
+ struct volume *target_volume;
+ unsigned int commit_count;
+ size_t bytes_written;
+ bool is_open;
+};
+
+/**
+ * \brief Initialize a raw_installer
+ *
+ * \param[in] subject The subject raw_installer
+ * \param[in] location_uuid The associated location UUID
+ * \param[in] location_id Identifies where to install qualifying images
+ */
+void raw_installer_init(struct raw_installer *subject,
+ const struct uuid_octets *location_uuid,
+ uint32_t location_id);
+
+/**
+ * \brief De-initialize a raw_installer
+ *
+ * \param[in] subject The subject raw_installer
+ */
+void raw_installer_deinit(struct raw_installer *subject);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FWU_RAW_INSTALLER_H */
diff --git a/components/service/fwu/installer/raw/test/component.cmake b/components/service/fwu/installer/raw/test/component.cmake
new file mode 100644
index 0000000..adda59c
--- /dev/null
+++ b/components/service/fwu/installer/raw/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}/raw_installer_tests.cpp"
+ )
diff --git a/components/service/fwu/installer/raw/test/raw_installer_tests.cpp b/components/service/fwu/installer/raw/test/raw_installer_tests.cpp
new file mode 100644
index 0000000..e0c1c6a
--- /dev/null
+++ b/components/service/fwu/installer/raw/test/raw_installer_tests.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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/installer/raw/raw_installer.h>
+#include <service/fwu/installer/installer_index.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <CppUTest/TestHarness.h>
+
+TEST_GROUP(FwuRawInstallerTests)
+{
+ 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. In a complete integration, this will come from the
+ * fw_directory.
+ */
+ 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. Multiple locations could be configured here
+ * but for these tests, there is only one. This enables the generic
+ * banked_fw_store to be completely decoupled from the details
+ * of which fw volumes need updating on a platform.
+ */
+ 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. This provides flexibility to configure
+ * appropriate installers to handle alternative fw packages and installation
+ * strategies.
+ */
+ raw_installer_init(&m_installer,
+ &m_image_info.img_type_uuid, FW_STORE_LOCATION_ID);
+
+ installer_index_register(&m_installer.base_installer);
+ }
+
+ void teardown()
+ {
+ delete[] m_image;
+
+ raw_installer_deinit(&m_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();
+ }
+
+ int write_image_in_random_len_chunks(struct installer *installer)
+ {
+ int status = 0;
+ size_t bytes_written = 0;
+
+ while ((bytes_written < m_image_len) && !status) {
+
+ size_t write_len = rand() % 100 + 1;
+
+ if ((bytes_written + write_len) > m_image_len)
+ write_len = m_image_len - bytes_written;
+
+ status = installer_write(installer, &m_image[bytes_written], write_len);
+ bytes_written += write_len;
+ }
+
+ return status;
+ }
+
+ void check_update_installed(struct volume *volume)
+ {
+ int status = 0;
+ size_t total_read = 0;
+ uintptr_t file_handle = 0;
+
+ status = io_open(volume->dev_handle, volume->io_spec, &file_handle);
+ 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 = io_read(file_handle,
+ (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;
+ }
+
+ io_close(file_handle);
+ }
+
+ 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_installer;
+ struct image_info m_image_info;
+ uint8_t *m_image;
+ size_t m_image_len;
+};
+
+TEST(FwuRawInstallerTests, normalInstallFlow)
+{
+ int status = 0;
+
+ /* Create arbitrary length image that should easily fit into the destination partition */
+ create_image(REF_PARTITION_BLOCK_SIZE * 2 + 111);
+
+ /* 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 */
+ 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);
+
+ /* Open install stream */
+ status = installer_open(installer, &m_image_info);
+ LONGS_EQUAL(0, status);
+
+ /* Stream the update image into the installer */
+ status = write_image_in_random_len_chunks(installer);
+ LONGS_EQUAL(0, status);
+
+ /* Commit after writing all image data */
+ status = installer_commit(installer);
+ LONGS_EQUAL(0, status);
+
+ /* Finalize installation transaction */
+ status = installer_finalize(installer);
+ LONGS_EQUAL(0, status);
+
+ /* Expect the update volume to contain the update */
+ check_update_installed(m_fw_volume_a);
+}
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index e6a8cef..f0aaa8c 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -113,6 +113,8 @@
"components/service/fwu/fw_store/banked/metadata_serializer/v1"
"components/service/fwu/fw_store/banked/test"
"components/service/fwu/installer"
+ "components/service/fwu/installer/raw"
+ "components/service/fwu/installer/raw/test"
"components/service/fwu/inspector/mock"
"components/service/crypto/client/cpp"
"components/service/crypto/client/cpp/protocol/protobuf"