Add FWU installer framework

Adds the base installer and installer_index to allow specialized
installers to be added to handle different types of update
image.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I18fa376106dae36a36f0078ed76c7fab5fb83a16
diff --git a/components/service/fwu/installer/component.cmake b/components/service/fwu/installer/component.cmake
new file mode 100644
index 0000000..5f124f1
--- /dev/null
+++ b/components/service/fwu/installer/component.cmake
@@ -0,0 +1,27 @@
+#-------------------------------------------------------------------------------
+# 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}/installer.c"
+	"${CMAKE_CURRENT_LIST_DIR}/installer_index.c"
+	)
+
+# Configurable build parameters
+# 	TS_CFG_FWU_MAX_INSTALLERS   	Maximum number of installers allowed
+#   TS_CFG_FWU_MAX_LOCATIONS		Maximum number of updatable locations
+if(DEFINED TS_CFG_FWU_MAX_INSTALLERS)
+	target_compile_definitions(${TGT} PRIVATE
+		INSTALLER_INDEX_LIMIT=${TS_CFG_FWU_MAX_INSTALLERS})
+endif()
+
+if(DEFINED TS_CFG_FWU_MAX_LOCATIONS)
+	target_compile_definitions(${TGT} PRIVATE
+		INSTALLER_INDEX_LOCATION_ID_LIMIT=${TS_CFG_FWU_MAX_LOCATIONS})
+endif()
diff --git a/components/service/fwu/installer/installer.c b/components/service/fwu/installer/installer.c
new file mode 100644
index 0000000..3c78a65
--- /dev/null
+++ b/components/service/fwu/installer/installer.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include "installer.h"
+
+void installer_init(
+	struct installer *installer,
+	enum install_type install_type,
+	uint32_t location_id,
+	const struct uuid_octets *location_uuid,
+	void *context,
+	const struct installer_interface *interface)
+{
+	assert(installer);
+	assert(location_uuid);
+	assert(context);
+	assert(interface);
+
+	installer->install_type = install_type;
+	installer->location_id = location_id;
+	installer->location_uuid = *location_uuid;
+	installer->context = context;
+	installer->interface = interface;
+
+	installer->install_status = FWU_STATUS_SUCCESS;
+	installer->is_active = false;
+	installer->next = NULL;
+}
+
+int installer_begin(
+	struct installer *installer,
+	uint32_t current_volume_id,
+	uint32_t update_volume_id)
+{
+	assert(installer);
+	assert(installer->interface);
+	assert(installer->interface->begin);
+
+	installer->install_status = FWU_STATUS_SUCCESS;
+	installer->is_active = true;
+
+	return installer->interface->begin(
+		installer->context,
+		current_volume_id,
+		update_volume_id);
+}
+
+int installer_finalize(
+	struct installer *installer)
+{
+	assert(installer);
+	assert(installer->interface);
+	assert(installer->interface->finalize);
+
+	installer->is_active = false;
+
+	return installer->interface->finalize(
+		installer->context);
+}
+
+void installer_abort(
+	struct installer *installer)
+{
+	assert(installer);
+	assert(installer->interface);
+	assert(installer->interface->abort);
+
+	installer->is_active = false;
+
+	installer->interface->abort(
+		installer->context);
+}
+
+int installer_open(
+	struct installer *installer,
+	const struct image_info *image_info)
+{
+	assert(installer);
+	assert(installer->interface);
+	assert(installer->interface->open);
+
+	int status = installer->interface->open(
+		installer->context,
+		image_info);
+
+	if (status && !installer->install_status)
+		installer->install_status = status;
+
+	return status;
+}
+
+int installer_commit(
+	struct installer *installer)
+{
+	assert(installer);
+	assert(installer->interface);
+	assert(installer->interface->commit);
+
+	int status = installer->interface->commit(
+		installer->context);
+
+	if (status && !installer->install_status)
+		installer->install_status = status;
+
+	return status;
+}
+
+int installer_write(
+	struct installer *installer,
+	const uint8_t *data,
+	size_t data_len)
+{
+	assert(installer);
+	assert(installer->interface);
+	assert(installer->interface->write);
+
+	int status = installer->interface->write(
+		installer->context,
+		data, data_len);
+
+	if (status && !installer->install_status)
+		installer->install_status = status;
+
+	return status;
+}
+
+int installer_enumerate(
+	struct installer *installer,
+	uint32_t volume_id,
+	struct fw_directory *fw_directory)
+{
+	assert(installer);
+	assert(installer->interface);
+	assert(installer->interface->enumerate);
+
+	return installer->interface->enumerate(
+		installer->context,
+		volume_id,
+		fw_directory);
+}
diff --git a/components/service/fwu/installer/installer.h b/components/service/fwu/installer/installer.h
new file mode 100644
index 0000000..49c822c
--- /dev/null
+++ b/components/service/fwu/installer/installer.h
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FWU_INSTALLATION_INSTALLER_H
+#define FWU_INSTALLATION_INSTALLER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <common/uuid/uuid.h>
+#include <service/fwu/agent/install_type.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct image_info;
+struct fw_directory;
+
+/**
+ * \brief Image installer interface
+ *
+ * The installer_interface structure provides a common interface for
+ * any image installer. Concrete installers will implement installation
+ * strategies that match the class of image being installed (e.g. component,
+ * whole firmware).
+ */
+struct installer_interface {
+	/**
+	 * \brief Begin a transaction of one or more image install operations
+	 *
+	 * \param[in]  context            The concrete installer context
+	 * \param[in]  current_volume_id  Where the active fw was loaded from
+	 * \param[out] update_volume_id   Where the update should be installed
+	 *
+	 * \return FWU status
+	 */
+	int (*begin)(void *context,
+		uint32_t current_volume_id,
+		uint32_t update_volume_id);
+
+	/**
+	 * \brief Finalize a transaction of one or more image install operations
+	 *
+	 * \param[in]  context         The concrete installer context
+	 *
+	 * \return FWU status
+	 */
+	int (*finalize)(void *context);
+
+	/**
+	 * \brief Abort a transaction
+	 *
+	 * \param[in]  context         The concrete installer context
+	 */
+	void (*abort)(void *context);
+
+	/**
+	 * \brief Open a stream for writing installation data
+	 *
+	 * \param[in]  context         The concrete installer context
+	 * \param[in]  image_info      Describes the image to install
+	 *
+	 * \return FWU status
+	 */
+	int (*open)(void *context,
+		const struct image_info *image_info);
+
+	/**
+	 * \brief Commit installed data (called once per open)
+	 *
+	 * \param[in]  context         The concrete installer context
+	 *
+	 * \return FWU status
+	 */
+	int (*commit)(void *context);
+
+	/**
+	 * \brief Write installation data to an opened installer
+	 *
+	 * \param[in]  context         The concrete installer context
+	 * \param[in]  data            Data to write
+	 * \param[in]  data_len        Length of data
+	 *
+	 * \return FWU status
+	 */
+	int (*write)(void *context,
+		const uint8_t *data,
+		size_t data_len);
+
+	/**
+	 * \brief Enumerate the collection of images that can be handled by the installer
+	 *
+	 * A concrete installer will use its specialized knowledge of the associated
+	 * storage volume to enumerate the set of images that can be handled by the
+	 * installer.
+	 *
+	 * \param[in]  context         The concrete installer context
+	 * \param[in]  volume_id       Identifies the target volume
+	 * \param[in]  fw_directory    Add results to this fw_directory
+	 *
+	 * \return FWU status
+	 */
+	int (*enumerate)(void *context,
+		uint32_t volume_id,
+		struct fw_directory *fw_directory);
+};
+
+/**
+ * \brief Base installer structure
+ *
+ */
+struct installer {
+
+	/* The installation type handled by the installer */
+	enum install_type install_type;
+
+	/* The location id is a short ID that identifies an updatable logical
+	 * portion of the firmware store. The location id only needs to be
+	 * unique within the device and will have been assigned dynamically
+	 * during FWU configuration. The location id is used to bind a set of
+	 * installers to the intended set of target volumes.
+	 */
+	uint32_t location_id;
+
+	/* The location UUID is a globally unique ID that reflects the same
+	 * logical scope as a location id. It is used to associate incoming
+	 * update images, either directly or indirectly, to the corresponding
+	 * location within the firmware store. For images contained within a
+	 * disk partition, this will correspond to the partition type UUID.
+	 */
+	struct uuid_octets location_uuid;
+
+	/* The opaque context for the concrete installer */
+	void *context;
+
+	/* Pointer to a concrete installer_interface */
+	const struct installer_interface *interface;
+
+	/* Error status encountered during an update transaction. During an update,
+	 * an installer will be called multiple times. If one or more errors are encountered
+	 * during the update transaction, the first error status is saved to allow for
+	 * appropriate handling.
+	 */
+	uint32_t install_status;
+
+	/**
+	 * During a multi-image update, images may be delegated to different concrete
+	 * installers to allow for alternative installation strategies and install destinations.
+	 * Each active installer must be sequenced through a begin->finalize transaction to
+	 * ensure that installation operations are completed for all images handled by an
+	 * installer and by all installers used during the update.
+	 */
+	bool is_active;
+	struct installer *next;
+};
+
+static inline bool installer_is_active(const struct installer *installer)
+{
+	return installer->is_active;
+}
+
+static inline uint32_t installer_status(const struct installer *installer)
+{
+	return installer->install_status;
+}
+
+void installer_init(
+	struct installer *installer,
+	enum install_type install_type,
+	uint32_t location_id,
+	const struct uuid_octets *location_uuid,
+	void *context,
+	const struct installer_interface *interface);
+
+int installer_begin(
+	struct installer *installer,
+	uint32_t current_volume_id,
+	uint32_t update_volume_id);
+
+int installer_finalize(
+	struct installer *installer);
+
+void installer_abort(
+	struct installer *installer);
+
+int installer_open(
+	struct installer *installer,
+	const struct image_info *image_info);
+
+int installer_commit(
+	struct installer *installer);
+
+int installer_write(
+	struct installer *installer,
+	const uint8_t *data,
+	size_t data_len);
+
+int installer_enumerate(
+	struct installer *installer,
+	uint32_t volume_id,
+	struct fw_directory *fw_directory);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FWU_INSTALLATION_INSTALLER_H */
diff --git a/components/service/fwu/installer/installer_index.c b/components/service/fwu/installer/installer_index.c
new file mode 100644
index 0000000..b2b24ef
--- /dev/null
+++ b/components/service/fwu/installer/installer_index.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <string.h>
+#include <stdint.h>
+#include <trace.h>
+#include "installer.h"
+#include "installer_index.h"
+
+#ifndef INSTALLER_INDEX_LIMIT
+#define INSTALLER_INDEX_LIMIT        (8)
+#endif
+
+#ifndef INSTALLER_INDEX_LOCATION_ID_LIMIT
+#define INSTALLER_INDEX_LOCATION_ID_LIMIT    (8)
+#endif
+
+/**
+ * Singleton index of installers to use for different classes of image.
+ */
+static struct {
+
+	/* An index for registered installers to handle update installation
+	 * for the platform. The set of registered installers will have been
+	 * selected for compatibility with the class of update image handled
+	 * by the installer.
+	 */
+	size_t num_registered;
+	struct installer *installers[INSTALLER_INDEX_LIMIT];
+
+	/* Each installer is associated with a location via the assigned
+	 * location_id. This is used as a logical identifier for the part
+	 * of the device firmware that the installer handles. Multiple
+	 * installers may be associated with the same location.
+	 */
+	size_t num_location_ids;
+	uint32_t location_ids[INSTALLER_INDEX_LOCATION_ID_LIMIT];
+
+} installer_index;
+
+static void add_location_id(uint32_t location_id)
+{
+	/* Check if location_id already added. Because 1..* installers
+	 * may be associated with the same location_id, we can ignore
+	 * location_ids that have already been added.
+	 */
+	for (size_t i = 0; i < installer_index.num_location_ids; i++) {
+
+		if (location_id == installer_index.location_ids[i])
+			return;
+	}
+
+	/* It's a new location_id so add it */
+	if (installer_index.num_location_ids < INSTALLER_INDEX_LOCATION_ID_LIMIT) {
+
+		installer_index.location_ids[installer_index.num_location_ids] = location_id;
+		++installer_index.num_location_ids;
+	} else {
+		EMSG("Too many FWU locations");
+	}
+}
+
+void installer_index_init(void)
+{
+	installer_index_clear();
+}
+
+void installer_index_clear(void)
+{
+	memset(&installer_index, 0, sizeof(installer_index));
+}
+
+void installer_index_register(
+	struct installer *installer)
+{
+	assert(installer);
+
+	if (installer_index.num_registered < INSTALLER_INDEX_LIMIT) {
+
+		installer_index.installers[installer_index.num_registered] = installer;
+		++installer_index.num_registered;
+
+
+		add_location_id(installer->location_id);
+	} else {
+		EMSG("FWU configuration exceeds installer limit");
+	}
+}
+
+struct installer *installer_index_find(
+	enum install_type install_type,
+	uint32_t location_id)
+{
+	struct installer *result = NULL;
+
+	for (size_t i = 0; i < installer_index.num_registered; i++) {
+
+		struct installer *installer = installer_index.installers[i];
+
+		if ((installer->install_type == install_type) &&
+			(installer->location_id == location_id)) {
+
+			result = installer;
+			break;
+		}
+	}
+
+	return result;
+}
+
+struct installer *installer_index_find_by_location_uuid(
+	const struct uuid_octets *location_uuid)
+{
+	struct installer *result = NULL;
+
+	for (size_t i = 0; i < installer_index.num_registered; i++) {
+
+		struct installer *installer = installer_index.installers[i];
+
+		if (uuid_is_equal(location_uuid->octets,
+				installer->location_uuid.octets)) {
+
+			result = installer;
+			break;
+		}
+	}
+
+	return result;
+}
+
+struct installer *installer_index_get(
+	unsigned int index)
+{
+	struct installer *result = NULL;
+
+	if (index < installer_index.num_registered)
+		result = installer_index.installers[index];
+
+	return result;
+}
+
+const uint32_t *installer_index_get_location_ids(size_t *num_ids)
+{
+	*num_ids = installer_index.num_location_ids;
+	return installer_index.location_ids;
+}
diff --git a/components/service/fwu/installer/installer_index.h b/components/service/fwu/installer/installer_index.h
new file mode 100644
index 0000000..1626257
--- /dev/null
+++ b/components/service/fwu/installer/installer_index.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef INSTALLER_INDEX_H
+#define INSTALLER_INDEX_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <service/fwu/agent/install_type.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct installer;
+
+/**
+ * @brief  Initialize the installer index
+ *
+ * The installer_index is a singleton that holds the set of installers that are suitable
+ * for the deployment. The mix of installers will depend on factors such as the image
+ * container used by firmware or whether images are encrypted etc.
+ */
+void installer_index_init(void);
+
+/**
+ * @brief  Clears the installer index
+ *
+ * Clears all mappings.
+ */
+void installer_index_clear(void);
+
+/**
+ * @brief  Register an installer
+ *
+ * Registers an installer to handle image installation for a particular install_type
+ * and location.
+ *
+ * @param[in] installer      The installer to use
+ */
+void installer_index_register(
+	struct installer *installer);
+
+/**
+ * @brief  Find an installer for the specified type and location
+ *
+ * @param[in]  install_type  The type of installer
+ * @param[in]  location_id   The location it serves
+ *
+ * @return Pointer to a concrete installer or NULL if none found
+ */
+struct installer *installer_index_find(
+	enum install_type install_type,
+	uint32_t location_id);
+
+/**
+ * @brief  Find a registered installer associated with the specified location UUID
+ *
+ * @param[in]  location_uuid  The associated location UUID
+ *
+ * @return Pointer to a concrete installer or NULL if none found
+ */
+struct installer *installer_index_find_by_location_uuid(
+	const struct uuid_octets *location_uuid);
+
+/**
+ * @brief  Iterator function
+ *
+ * @param[in]  index  0..n
+ *
+ * @return Pointer to a concrete installer or NULL if iterated beyond final entry
+ */
+struct installer *installer_index_get(unsigned int index);
+
+/**
+ * @brief  Get an array of location_ids
+ *
+ * Updatable firmware may be distributed over multiple locations. Each location is
+ * assigned a location_id when the set of installers are registered for the platform.
+ * This function returns a variable length array of locations_ids for the platform.
+ * This is useful for checking if an incoming update has included components that
+ * update all locations or not.
+ *
+ * @param[out]  num_ids  0..n
+ *
+ * @return Pointer to a static array of location ids
+ */
+const uint32_t *installer_index_get_location_ids(size_t *num_ids);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INSTALLER_INDEX_H */
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index c1e40fb..4fd0009 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -109,6 +109,7 @@
 		"components/service/block_storage/factory/ref_ram_gpt"
 		"components/service/block_storage/factory/client"
 		"components/service/fwu/agent"
+		"components/service/fwu/installer"
 		"components/service/crypto/client/cpp"
 		"components/service/crypto/client/cpp/protocol/protobuf"
 		"components/service/crypto/client/cpp/protocol/packed-c"