Add banked_fw_store with FWU metadata management

Adds an A/B banked fw_store that manages update installation
and metadata in-line with the Arm FWU-A specification.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: Icf7af243081ba9edd208ddb87597ce35f46a924b
diff --git a/components/service/fwu/fw_store/banked/bank_scheme.h b/components/service/fwu/fw_store/banked/bank_scheme.h
new file mode 100644
index 0000000..83a9f0d
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/bank_scheme.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef BANKED_FW_STORE_BANK_SCHEME_H
+#define BANKED_FW_STORE_BANK_SCHEME_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Definitions for an A/B banked scheme.
+ */
+#define BANK_SCHEME_NUM_BANKS    (2)
+
+/**
+ * \brief Returns the index of the next bank to use
+ *
+ * Given a bank index, returns the index of the next bank to use.
+ *
+ * \param[in]  bank_index
+ *
+ * \return Index of next bank to use
+ */
+static inline uint32_t bank_scheme_next_index(uint32_t bank_index)
+{
+	return (bank_index == 0) ? 1 : 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BANKED_FW_STORE_BANK_SCHEME_H */
diff --git a/components/service/fwu/fw_store/banked/bank_tracker.c b/components/service/fwu/fw_store/banked/bank_tracker.c
new file mode 100644
index 0000000..239be37
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/bank_tracker.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include "bank_tracker.h"
+
+
+void bank_tracker_init(
+	struct bank_tracker *subject)
+{
+	memset(subject, 0, sizeof(struct bank_tracker));
+}
+
+void bank_tracker_deinit(
+	struct bank_tracker *subject)
+{
+	(void)subject;
+}
+
+void bank_tracker_accept(
+	struct bank_tracker *subject,
+	unsigned int bank_index,
+	unsigned int image_index)
+{
+	assert(bank_index < BANK_SCHEME_NUM_BANKS);
+	assert(image_index < FWU_MAX_FW_DIRECTORY_ENTRIES);
+
+	subject->bank_state[bank_index].is_accepted[image_index] = true;
+
+}
+
+void bank_tracker_copy_accept(
+	struct bank_tracker *subject,
+	unsigned int from_bank_index,
+	unsigned int to_bank_index,
+	unsigned int image_index)
+{
+	assert(from_bank_index < BANK_SCHEME_NUM_BANKS);
+	assert(to_bank_index < BANK_SCHEME_NUM_BANKS);
+	assert(image_index < FWU_MAX_FW_DIRECTORY_ENTRIES);
+
+	subject->bank_state[to_bank_index].is_accepted[image_index] =
+		subject->bank_state[from_bank_index].is_accepted[image_index];
+}
+
+void bank_tracker_set_no_content(
+	struct bank_tracker *subject,
+	unsigned int bank_index)
+{
+	assert(bank_index < BANK_SCHEME_NUM_BANKS);
+
+	subject->bank_state[bank_index].is_content = false;
+
+	for (unsigned int i = 0; i < FWU_MAX_FW_DIRECTORY_ENTRIES; i++)
+		subject->bank_state[bank_index].is_accepted[i] = false;
+}
+
+void bank_tracker_set_holds_content(
+	struct bank_tracker *subject,
+	unsigned int bank_index)
+{
+	assert(bank_index < BANK_SCHEME_NUM_BANKS);
+
+	subject->bank_state[bank_index].is_content = true;
+}
+
+void bank_tracker_set_holds_accepted_content(
+	struct bank_tracker *subject,
+	unsigned int bank_index)
+{
+	assert(bank_index < BANK_SCHEME_NUM_BANKS);
+
+	subject->bank_state[bank_index].is_content = true;
+
+	for (unsigned int i = 0; i < FWU_MAX_FW_DIRECTORY_ENTRIES; i++)
+		subject->bank_state[bank_index].is_accepted[i] = true;
+}
+
+bool bank_tracker_is_content(
+	const struct bank_tracker *subject,
+	unsigned int bank_index)
+{
+	assert(bank_index < BANK_SCHEME_NUM_BANKS);
+
+	return subject->bank_state[bank_index].is_content;
+}
+
+bool bank_tracker_is_accepted(
+	const struct bank_tracker *subject,
+	unsigned int bank_index,
+	unsigned int image_index)
+{
+	assert(bank_index < BANK_SCHEME_NUM_BANKS);
+	assert(image_index < FWU_MAX_FW_DIRECTORY_ENTRIES);
+
+	return subject->bank_state[bank_index].is_accepted[image_index];
+}
+
+bool bank_tracker_is_all_accepted(
+	const struct bank_tracker *subject,
+	unsigned int bank_index,
+	unsigned int num_images)
+{
+	assert(bank_index < BANK_SCHEME_NUM_BANKS);
+	assert(num_images <= FWU_MAX_FW_DIRECTORY_ENTRIES);
+
+	for (unsigned int image_index = 0; image_index < num_images; image_index++) {
+
+		if (!subject->bank_state[bank_index].is_accepted[image_index])
+			return false;
+	}
+
+	return true;
+}
diff --git a/components/service/fwu/fw_store/banked/bank_tracker.h b/components/service/fwu/fw_store/banked/bank_tracker.h
new file mode 100644
index 0000000..cf3279d
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/bank_tracker.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef BANK_TRACKER_H
+#define BANK_TRACKER_H
+
+#include <stdbool.h>
+#include <service/fwu/agent/fw_directory.h>
+#include "bank_scheme.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief bank_tracker structure definition
+ *
+ * Tracks the state of fw_store banks.
+ */
+struct bank_tracker {
+
+	struct {
+
+		/* True if bank holds content */
+		bool is_content;
+
+		/* Image accepted state for images contained in the bank */
+		bool is_accepted[FWU_MAX_FW_DIRECTORY_ENTRIES];
+
+	} bank_state[BANK_SCHEME_NUM_BANKS];
+
+};
+
+/**
+ * \brief Initialize the bank_tracker
+ *
+ * \param[in] subject       This instance
+ */
+void bank_tracker_init(
+	struct bank_tracker *subject);
+
+/**
+ * \brief De-initialize the bank_tracker
+ *
+ * \param[in] subject      This instance
+ */
+void bank_tracker_deinit(
+	struct bank_tracker *subject);
+
+/**
+ * \brief Mark image as accepted
+ *
+ * \param[in] subject      This instance
+ * \param[in] bank_index   The firmware bank
+ * \param[in] image_index  The image index (from fw_directory)
+ */
+void bank_tracker_accept(
+	struct bank_tracker *subject,
+	unsigned int bank_index,
+	unsigned int image_index);
+
+/**
+ * \brief Copy image accept state
+ *
+ * \param[in] subject      This instance
+ * \param[in] from_bank_index Copy accepted state from bank index
+ * \param[in] to_bank_index Copy accepted state to bank index
+ * \param[in] image_index  The image index (from fw_directory)
+ */
+void bank_tracker_copy_accept(
+	struct bank_tracker *subject,
+	unsigned int from_bank_index,
+	unsigned int to_bank_index,
+	unsigned int image_index);
+
+/**
+ * \brief Sets bank as holding no content
+ *
+ * \param[in] subject      This instance
+ * \param[in] bank_index   The firmware bank
+ */
+void bank_tracker_set_no_content(
+	struct bank_tracker *subject,
+	unsigned int bank_index);
+
+/**
+ * \brief Set bank as holding content
+ *
+ * \param[in] subject      This instance
+ * \param[in] bank_index   The firmware bank
+ */
+void bank_tracker_set_holds_content(
+	struct bank_tracker *subject,
+	unsigned int bank_index);
+
+/**
+ * \brief Set bank as holding fully accepted content
+ *
+ * \param[in] subject      This instance
+ * \param[in] bank_index   The firmware bank
+ */
+void bank_tracker_set_holds_accepted_content(
+	struct bank_tracker *subject,
+	unsigned int bank_index);
+
+/**
+ * \brief Check if bank holds contents
+ *
+ * \param[in] subject      This instance
+ * \param[in] bank_index   The firmware bank
+ *
+ * \return True if bank holds content
+ */
+bool bank_tracker_is_content(
+	const struct bank_tracker *subject,
+	unsigned int bank_index);
+
+/**
+ * \brief Check if image is accepted
+ *
+ * \param[in] subject      This instance
+ * \param[in] bank_index   The firmware bank
+ * \param[in] image_index  The image index
+ *
+ * \return True if an image has been accepted
+ */
+bool bank_tracker_is_accepted(
+	const struct bank_tracker *subject,
+	unsigned int bank_index,
+	unsigned int image_index);
+
+/**
+ * \brief Check if all images are accepted
+ *
+ * \param[in] subject      This instance
+ * \param[in] bank_index   The firmware bank
+ * \param[in] num_images   Number of images to consider
+ *
+ * \return True if all images have been accepted
+ */
+bool bank_tracker_is_all_accepted(
+	const struct bank_tracker *subject,
+	unsigned int bank_index,
+	unsigned int num_images);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BANK_TRACKER_H */
diff --git a/components/service/fwu/fw_store/banked/banked_fw_store.c b/components/service/fwu/fw_store/banked/banked_fw_store.c
new file mode 100644
index 0000000..dec576a
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/banked_fw_store.c
@@ -0,0 +1,595 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <common/uuid/uuid.h>
+#include <protocols/service/fwu/packed-c/fwu_proto.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include <service/fwu/installer/installer.h>
+#include <service/fwu/installer/installer_index.h>
+#include "banked_fw_store.h"
+#include "bank_scheme.h"
+#include "volume_id.h"
+
+static int activate_installer(
+	struct fw_store *fw_store,
+	struct installer *installer,
+	uint32_t location_id)
+{
+	int status = FWU_STATUS_UNKNOWN;
+
+	/* An image has been presented that requires a new installer to
+	 * be activated. This involves associating the installer with
+	 * the correct storage volumes that reflect the current bank
+	 * state. Active installers are held in a linked list during
+	 * the installation phase of an update transaction.
+	 */
+	unsigned int update_volume_id = banked_volume_id(
+		location_id, banked_usage_id(fw_store->update_index));
+
+	unsigned int current_volume_id = banked_volume_id(
+		location_id, banked_usage_id(fw_store->boot_index));
+
+	status = installer_begin(installer, current_volume_id, update_volume_id);
+
+	if (status == FWU_STATUS_SUCCESS) {
+
+		/* Add to list of active installers */
+		installer->next = fw_store->active_installers;
+		fw_store->active_installers = installer;
+	}
+
+	return status;
+}
+
+static void copy_accepted_state_for_location(
+	struct fw_store *fw_store,
+	uint32_t location_id)
+{
+	size_t index = 0;
+
+	while (1) {
+
+		const struct image_info *image_info = fw_directory_get_image_info(
+			fw_store->fw_directory, index);
+
+		if (image_info) {
+
+			if (image_info->location_id == location_id)
+				bank_tracker_copy_accept(
+					&fw_store->bank_tracker,
+					fw_store->boot_index,
+					fw_store->update_index,
+					image_info->image_index);
+		} else
+			break;
+
+		++index;
+	}
+}
+
+static int install_unchanged_images(
+	struct fw_store *fw_store)
+{
+	int status = FWU_STATUS_SUCCESS;
+	size_t num_locations = 0;
+	const uint32_t *location_ids = installer_index_get_location_ids(&num_locations);
+
+	/* Iterate over each of the location_ids that require updating for a viable update.
+	 * If there are locations without an active installer, attempt to copy from the
+	 * currently active bank.
+	 */
+	for (size_t i = 0; !status && i < num_locations; i++) {
+
+		bool is_installer_found = false;
+		uint32_t location_id = location_ids[i];
+		struct installer *installer = fw_store->active_installers;
+
+		while (installer) {
+
+			if ((installer->location_id == location_id) &&
+				!installer_status(installer)) {
+
+				/* There was an installer for this location and the installation
+				 * went without errors.
+				 */
+				is_installer_found = true;
+				break;
+			}
+
+			installer = installer->next;
+		}
+
+		if (!is_installer_found) {
+
+			/* There is no installer for the location so assume the active bank
+			 * should be copied. This relies on the platform integrator providing
+			 * a suitable installer to do the copying. It's a legitimate platform
+			 * configuration to not provide one and only allow updates that consist
+			 * of images for all locations.
+			 */
+			struct installer *copy_installer = installer_index_find(
+				INSTALL_TYPE_WHOLE_VOLUME_COPY, location_id);
+
+			if (copy_installer) {
+
+				/* A copy installer doesn't accept any external data. Instead, it
+				 * copies from the current volume to the update volume during the
+				 * finalize operation.
+				 */
+				unsigned int update_volume_id = banked_volume_id(
+					location_id, banked_usage_id(fw_store->update_index));
+
+				unsigned int current_volume_id = banked_volume_id(
+					location_id, banked_usage_id(fw_store->boot_index));
+
+				status = installer_begin(copy_installer,
+					current_volume_id, update_volume_id);
+
+				if (status == FWU_STATUS_SUCCESS) {
+
+					status = installer_finalize(copy_installer);
+
+					/* If a whole volume image was successfully copied from
+					 * the currently active bank to the update bank, also copy the
+					 * corresponding accepted state for all images associated with the
+					 * location. This saves a client from having to re-accept images
+					 * that have already been accepted during a previous update.
+					 */
+					if (status == FWU_STATUS_SUCCESS)
+						copy_accepted_state_for_location(fw_store, location_id);
+				}
+
+			} else {
+
+				/* Platform configuration doesn't include a suitable copy installer
+				 * so treat this update attempt as not viable.
+				 */
+				status = FWU_STATUS_NOT_AVAILABLE;
+			}
+		}
+	}
+
+	return status;
+}
+
+int banked_fw_store_init(
+	struct fw_store *fw_store,
+	const struct metadata_serializer *serializer)
+{
+	fw_store->fw_directory = NULL;
+	fw_store->active_installers = NULL;
+
+	fw_store->update_index = 0;
+	fw_store->boot_index = 0;
+
+	bank_tracker_init(&fw_store->bank_tracker);
+
+	return metadata_manager_init(&fw_store->metadata_manager, serializer);
+}
+
+void banked_fw_store_deinit(
+	struct fw_store *fw_store)
+{
+	bank_tracker_deinit(&fw_store->bank_tracker);
+	metadata_manager_deinit(&fw_store->metadata_manager);
+}
+
+int fw_store_synchronize(
+	struct fw_store *fw_store,
+	struct fw_directory *fw_dir,
+	unsigned int boot_index)
+{
+	int status = FWU_STATUS_UNKNOWN;
+
+	/* Associate with the fw_directory */
+	fw_store->fw_directory = fw_dir;
+
+	/* Note the boot index decision made by the boot loader and choose an
+	 * alternative index for a prospective update.
+	 */
+	fw_store->boot_index = boot_index;
+	fw_store->update_index = bank_scheme_next_index(boot_index);
+
+	/* Start building a view of the boot_info that will be advertised by the fw_directory */
+	struct boot_info boot_info = {0};
+
+	boot_info.boot_index = boot_index;
+
+	/* Ensure that FWU metadata is in a good state. Corruption can occur
+	 * due to power failures. The following steps will repair a corrupted
+	 * metadata copy or generate fresh metadata if necessary.
+	 */
+	status = metadata_manager_check_and_repair(
+		&fw_store->metadata_manager, fw_dir);
+
+	if (status != FWU_STATUS_SUCCESS) {
+
+		/* No viable metadata exists so a fresh version needs to be created. We
+		 * can only assume that the boot_index is good. Also assume that it is
+		 * fully accepted as there is no knowledge that a rollback to a previous
+		 * bank is possible.
+		 */
+		boot_info.active_index = boot_index;
+		boot_info.previous_active_index = boot_index;
+
+		bank_tracker_set_holds_accepted_content(
+			&fw_store->bank_tracker,
+			fw_store->boot_index);
+
+		status = metadata_manager_update(
+			&fw_store->metadata_manager,
+			boot_info.active_index,
+			boot_info.previous_active_index,
+			fw_dir,
+			&fw_store->bank_tracker);
+
+	} else {
+
+		/* Metadata was successfully loaded from storage so synchronize local
+		 * state to the NV state reflected by the metadata.
+		 */
+		status = metadata_manager_get_active_indices(
+			&fw_store->metadata_manager,
+			&boot_info.active_index,
+			&boot_info.previous_active_index);
+
+		/* Synchronize image approval state with NV information contained in the metadata */
+		metadata_manager_preload_bank_tracker(
+			&fw_store->metadata_manager,
+			&fw_store->bank_tracker);
+
+		/* Check for the case where the bootloader has not booted from the active bank.
+		 * This will occur when boot loader conditions for a successful boot are not
+		 * met and it was necessary to fallback to a previous bank. To prevent
+		 * repeated failed boots, the metadata is updated in-line with the
+		 * bootloader's decision.
+		 */
+		if ((status == FWU_STATUS_SUCCESS) &&
+			(boot_index != boot_info.active_index)) {
+
+			boot_info.active_index = boot_index;
+			boot_info.previous_active_index = boot_index;
+
+			status = metadata_manager_update(
+				&fw_store->metadata_manager,
+				boot_info.active_index,
+				boot_info.previous_active_index,
+				fw_dir,
+				&fw_store->bank_tracker);
+		}
+	}
+
+	/* Synchronize the fw_directory's view of the boot info */
+	fw_directory_set_boot_info(fw_dir, &boot_info);
+
+	return status;
+}
+
+int fw_store_begin_install(
+	struct fw_store *fw_store)
+{
+	assert(!fw_store->active_installers);
+
+	/* Begin the update transaction with the update bank marked as holding
+	 * no content and all updatable images in the unaccepted state. Installed
+	 * images may be committed as accepted after installation or accepted
+	 * during the trial state.
+	 */
+	bank_tracker_set_no_content(
+		&fw_store->bank_tracker,
+		fw_store->update_index);
+
+	/* Protect the update bank from use while the installation is in
+	 * progress by setting the active and previous active indices to be equal.
+	 * If a system restart occurs during installation, this prevents the
+	 * possibility of the boot loader attempting to boot from a bank in a partially
+	 * installed state.
+	 */
+	int status = metadata_manager_update(
+		&fw_store->metadata_manager,
+		fw_store->boot_index,
+		fw_store->boot_index,
+		fw_store->fw_directory,
+		&fw_store->bank_tracker);
+
+	return status;
+}
+
+void fw_store_cancel_install(
+	struct fw_store *fw_store)
+{
+	/* Abort all active installers - each installer will do its best to
+	 * clean-up to a state where a follow-in installation is possible.
+	 */
+	while (fw_store->active_installers) {
+
+		struct installer *installer = fw_store->active_installers;
+
+		fw_store->active_installers = installer->next;
+
+		installer_abort(installer);
+	}
+}
+
+int fw_store_finalize_install(
+	struct fw_store *fw_store)
+{
+	int status = FWU_STATUS_NOT_AVAILABLE;
+
+	/* Treat no active installers as an error. This would occur if no
+	 * images where actually installed during the transaction.
+	 */
+	bool is_error = !fw_store->active_installers;
+
+	/* If there are firmware locations that are unchanged after
+	 * installation, it may be necessary to copy active images to
+	 * the update bank to ensure that the update bank is fully
+	 * populated. This will be needed if an incoming update package
+	 * only contains images for some locations.
+	 */
+	if (!is_error) {
+
+		status = install_unchanged_images(fw_store);
+		is_error = (status != FWU_STATUS_SUCCESS);
+	}
+
+	/* Finalize all active installers - each installer will perform any
+	 * actions needed to make installed images ready for use. For example,
+	 * for a component installer where only a subset of images were updated,
+	 * the finalize step can be used to copy any images that weren't updated
+	 * from the currently active storage volume.
+	 */
+	while (fw_store->active_installers) {
+
+		struct installer *installer = fw_store->active_installers;
+
+		fw_store->active_installers = installer->next;
+
+		if (!is_error) {
+
+			status = installer_finalize(installer);
+			is_error = (status != FWU_STATUS_SUCCESS);
+
+		} else
+			installer_abort(installer);
+	}
+
+	if (is_error) {
+
+		return (status != FWU_STATUS_SUCCESS) ?
+			status : FWU_STATUS_NOT_AVAILABLE;
+	}
+
+	/* All installers finalized successfully so mark update bank as holding
+	 * content and signal to boot loader that the update is ready for a trial
+	 * by promoting the update bank to being the active bank.
+	 */
+	bank_tracker_set_holds_content(
+		&fw_store->bank_tracker,
+		fw_store->update_index);
+
+	status = metadata_manager_update(
+		&fw_store->metadata_manager,
+		fw_store->update_index,
+		fw_store->boot_index,
+		fw_store->fw_directory,
+		&fw_store->bank_tracker);
+
+	return status;
+}
+
+int fw_store_select_installer(
+	struct fw_store *fw_store,
+	const struct image_info *image_info,
+	struct installer **installer)
+{
+	int status = FWU_STATUS_UNKNOWN;
+
+	*installer = NULL;
+
+	struct installer *selected_installer =
+		installer_index_find(image_info->install_type, image_info->location_id);
+
+	if (selected_installer) {
+
+		/* An installer is available to handle the incoming image. An installer
+		 * will potentially handle multiple images as part of an update transaction.
+		 * If this is the first, the installer needs to be activated.
+		 */
+		if (installer_is_active(selected_installer) ||
+			(status = activate_installer(fw_store,
+				selected_installer, image_info->location_id),
+				status == FWU_STATUS_SUCCESS)) {
+
+			status = installer_open(selected_installer, image_info);
+
+			if (status == FWU_STATUS_SUCCESS)
+				*installer = selected_installer;
+		}
+	}
+
+	return status;
+}
+
+int fw_store_write_image(
+	struct fw_store *fw_store,
+	struct installer *installer,
+	const uint8_t *data,
+	size_t data_len)
+{
+	if (!installer_is_active(installer))
+		/* Attempting to write to an inactive installer */
+		return FWU_STATUS_DENIED;
+
+	int status = installer_write(installer, data, data_len);
+
+	return status;
+}
+
+int fw_store_commit_image(
+	struct fw_store *fw_store,
+	struct installer *installer,
+	const struct image_info *image_info,
+	bool accepted)
+{
+	if (!installer_is_active(installer))
+		/* Attempting to commit an inactive installer */
+		return FWU_STATUS_DENIED;
+
+	int status = installer_commit(installer);
+
+	if ((status == FWU_STATUS_SUCCESS) && accepted)
+		bank_tracker_accept(
+			&fw_store->bank_tracker,
+			fw_store->update_index,
+			image_info->image_index);
+
+	return status;
+}
+
+bool fw_store_notify_accepted(
+	struct fw_store *fw_store,
+	const struct image_info *image_info)
+{
+	unsigned int num_images = fw_directory_num_images(fw_store->fw_directory);
+
+	bank_tracker_accept(
+		&fw_store->bank_tracker,
+		fw_store->boot_index,
+		image_info->image_index);
+
+	int status = metadata_manager_update(
+		&fw_store->metadata_manager,
+		fw_store->boot_index,
+		fw_store->update_index,
+		fw_store->fw_directory,
+		&fw_store->bank_tracker);
+
+	return (status == FWU_STATUS_SUCCESS) &&
+		bank_tracker_is_all_accepted(
+			&fw_store->bank_tracker,
+			fw_store->boot_index,
+			num_images);
+}
+
+bool fw_store_is_accepted(
+	const struct fw_store *fw_store,
+	const struct image_info *image_info)
+{
+	return bank_tracker_is_accepted(
+		&fw_store->bank_tracker,
+		fw_store->boot_index,
+		image_info->image_index);
+}
+
+bool fw_store_is_trial(
+	const struct fw_store *fw_store)
+{
+	const struct boot_info *boot_info = fw_directory_get_boot_info(fw_store->fw_directory);
+	unsigned int num_images = fw_directory_num_images(fw_store->fw_directory);
+
+	return
+		(boot_info->boot_index == boot_info->active_index) &&
+		!bank_tracker_is_all_accepted(
+			&fw_store->bank_tracker,
+			fw_store->boot_index,
+			num_images);
+}
+
+int fw_store_commit_to_update(
+	struct fw_store *fw_store)
+{
+	(void)fw_store;
+
+	/* Currently, the final commitment to an update is made by the boot loader
+	 * when anti-rollback counters are advanced after a reboot. For deployments
+	 * where anti-rollback counters are managed by the update agent, the necessary
+	 * management logic should be add here.
+	 */
+	return FWU_STATUS_SUCCESS;
+}
+
+int fw_store_revert_to_previous(
+	struct fw_store *fw_store)
+{
+	uint32_t active_index;
+	uint32_t previous_active_index;
+
+	int status = metadata_manager_get_active_indices(
+		&fw_store->metadata_manager,
+		&active_index,
+		&previous_active_index);
+
+	if (status)
+		return status;
+
+	if (active_index == fw_store->boot_index) {
+
+		/* Update has been activated via a reboot */
+		active_index = previous_active_index;
+		previous_active_index = fw_store->boot_index;
+		fw_store->update_index = bank_scheme_next_index(active_index);
+
+	} else {
+
+		/* Update has not yet been activated */
+		previous_active_index = active_index;
+		active_index = fw_store->boot_index;
+		fw_store->update_index = bank_scheme_next_index(active_index);
+	}
+
+	/* Ensure all images for the new active bank are marked as accepted */
+	bank_tracker_set_holds_accepted_content(
+		&fw_store->bank_tracker,
+		active_index);
+
+	/* Update the FWU metadata to the pre-update state */
+	status = metadata_manager_update(
+		&fw_store->metadata_manager,
+		active_index,
+		previous_active_index,
+		fw_store->fw_directory,
+		&fw_store->bank_tracker);
+
+	return status;
+}
+
+bool fw_store_export(
+	struct fw_store *fw_store,
+	const struct uuid_octets *uuid,
+	const uint8_t **data,
+	size_t *data_len,
+	int *status)
+{
+	struct uuid_octets target_uuid;
+
+	/* Check for request to export the FWU metadata */
+	uuid_guid_octets_from_canonical(&target_uuid, FWU_METADATA_CANONICAL_UUID);
+
+	if (uuid_is_equal(uuid->octets, target_uuid.octets)) {
+
+		bool is_dirty;
+
+		*status = metadata_manager_fetch(
+			&fw_store->metadata_manager,
+			data, data_len,
+			&is_dirty);
+
+		/* is_dirty value is not yet returned when exporting the FWU Metadata but this
+		 * may be added to allow for deployments where metadata is written by the client
+		 * to allow unnecessary writes to be avoided.
+		 */
+
+		return true;
+	}
+
+	return false;
+}
+
diff --git a/components/service/fwu/fw_store/banked/banked_fw_store.h b/components/service/fwu/fw_store/banked/banked_fw_store.h
new file mode 100644
index 0000000..43849ec
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/banked_fw_store.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef BANKED_FW_STORE_H
+#define BANKED_FW_STORE_H
+
+#include <stdint.h>
+#include <service/fwu/fw_store/fw_store.h>
+#include "metadata_manager.h"
+#include "bank_tracker.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct installer;
+struct metadata_serializer;
+
+/**
+ * \brief Banked fw_store structure definition
+ *
+ * A banked fw_store manages updates to an A/B banked firmware store. The
+ * implementation realizes the common fw_store interface. The banked fw_store
+ * can handle both Nwd and Swd image installation.
+ */
+struct fw_store {
+	const struct fw_directory *fw_directory;
+	struct installer *active_installers;
+	struct metadata_manager metadata_manager;
+	struct bank_tracker bank_tracker;
+	uint32_t update_index;
+	uint32_t boot_index;
+};
+
+/**
+ * \brief Initialize a banked fw_store
+ *
+ * Initializes a banked fw_store that manages updates according to the Arm
+ * FWU-A specification. The provided metadata_serializer should have been
+ * selected for compatibility with the bootloader.
+ *
+ * \param[in]  fw_store       The subject fw_store
+ * \param[in]  serializer     The metadata_serializer to use
+ *
+ * \return FWU status code
+ */
+int banked_fw_store_init(
+	struct fw_store *fw_store,
+	const struct metadata_serializer *serializer);
+
+/**
+ * \brief De-initialize a banked_fw_store
+ *
+ * \param[in]  fw_store    The subject fw_store
+ */
+void banked_fw_store_deinit(
+	struct fw_store *fw_store);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BANKED_FW_STORE_H */
diff --git a/components/service/fwu/fw_store/banked/component.cmake b/components/service/fwu/fw_store/banked/component.cmake
new file mode 100644
index 0000000..2eaafce
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/component.cmake
@@ -0,0 +1,15 @@
+#-------------------------------------------------------------------------------
+# 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}/banked_fw_store.c"
+	"${CMAKE_CURRENT_LIST_DIR}/metadata_manager.c"
+	"${CMAKE_CURRENT_LIST_DIR}/bank_tracker.c"
+	)
diff --git a/components/service/fwu/fw_store/banked/metadata_manager.c b/components/service/fwu/fw_store/banked/metadata_manager.c
new file mode 100644
index 0000000..f2d49f8
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/metadata_manager.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <trace.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include <media/volume/index/volume_index.h>
+#include <media/volume/volume.h>
+#include <common/crc32/crc32.h>
+#include <service/fwu/fw_store/banked/metadata_serializer/metadata_serializer.h>
+#include "metadata_manager.h"
+#include "volume_id.h"
+
+static int load_and_check_metadata(
+	struct volume *volume,
+	uint8_t *buf,
+	size_t expected_len)
+{
+	int status = FWU_STATUS_UNKNOWN;
+
+	status = volume_open(volume);
+
+	if (status == 0) {
+
+		size_t len = 0;
+
+		status = volume_read(volume, (uintptr_t)buf, expected_len, &len);
+
+		if (status == 0) {
+
+			uint32_t calc_crc = crc32(0U,
+				buf + sizeof(uint32_t),
+				expected_len - sizeof(uint32_t));
+
+			uint32_t expected_crc = *((uint32_t *)buf);
+
+			status = (calc_crc == expected_crc) ?
+				FWU_STATUS_SUCCESS : FWU_STATUS_UNKNOWN;
+		}
+
+		volume_close(volume);
+	}
+
+	return status;
+}
+
+static int store_metadata(
+	struct volume *volume,
+	const uint8_t *data,
+	size_t data_len)
+{
+	int status = volume_open(volume);
+
+	if (status == 0) {
+
+		status = volume_erase(volume);
+
+		if (status == 0) {
+
+			size_t written_len = 0;
+
+			status = volume_write(
+				volume, (const uintptr_t)data,
+				data_len, &written_len);
+		}
+
+		volume_close(volume);
+	}
+
+	return status;
+}
+
+int metadata_manager_init(
+	struct metadata_manager *subject,
+	const struct metadata_serializer *serializer)
+{
+	assert(subject);
+	assert(serializer);
+
+	subject->serializer = serializer;
+
+	/* Depending on the class of device, the storage volumes that hold FWU
+	 * metadata may or may not be accessible by the metadata_manager. This
+	 * should be reflected by which volumes have been added to the volume_index
+	 * by deployment specific configuration code.
+	 */
+	subject->primary_metadata_volume = NULL;
+	subject->backup_metadata_volume = NULL;
+
+	volume_index_find(BANKED_VOLUME_ID_PRIMARY_METADATA,
+		&subject->primary_metadata_volume);
+	volume_index_find(BANKED_VOLUME_ID_BACKUP_METADATA,
+		&subject->backup_metadata_volume);
+
+	/* A cached copy of the metadata is held in memory. It will not initially
+	 * hold a valid copy until one has been loaded from storage or a fresh
+	 * version has been written.
+	 */
+	subject->is_dirty = false;
+	subject->is_valid = false;
+	subject->stored_crc = 0;
+	subject->metadata_len = 0;
+	subject->metadata_max_len = subject->serializer->max_size();
+	subject->metadata_cache = malloc(subject->metadata_max_len);
+
+	return (subject->metadata_cache) ?
+		FWU_STATUS_SUCCESS : FWU_STATUS_UNKNOWN;
+}
+
+void metadata_manager_deinit(
+	struct metadata_manager *subject)
+{
+	free(subject->metadata_cache);
+}
+
+int metadata_manager_check_and_repair(
+	struct metadata_manager *subject,
+	const struct fw_directory *fw_dir)
+{
+	int primary_status = FWU_STATUS_UNKNOWN;
+	int backup_status = FWU_STATUS_UNKNOWN;
+
+	/* No need to perform operation if valid data is already held.*/
+	if (subject->is_valid)
+		return FWU_STATUS_SUCCESS;
+
+	/* If no storage volume is accessible (e.g. with a single flash configuration),
+	 * the operation can never succeed.
+	 */
+	if (!subject->primary_metadata_volume && !subject->backup_metadata_volume) {
+
+		IMSG("FWU volume not accessible");
+		return FWU_STATUS_UNKNOWN;
+	}
+
+	/* Loaded metadata length should be consistent with the view of firmware held
+	 * by the fw_directory.
+	 */
+	subject->metadata_len = subject->serializer->size(fw_dir);
+
+	if (subject->primary_metadata_volume) {
+
+		primary_status = load_and_check_metadata(subject->primary_metadata_volume,
+			subject->metadata_cache,
+			subject->metadata_len);
+
+		subject->is_valid = (primary_status == FWU_STATUS_SUCCESS);
+	}
+
+	if (subject->backup_metadata_volume) {
+
+		if (subject->is_valid) {
+
+			/* Already successfully loaded the primary copy. Just need to check
+			 * the backup and repair it if necessary. During an update operation,
+			 * the primary metadata is always written first. A hazard exists where
+			 * both primary and backup are valid but they contain different data
+			 * due to a power failure just before writing the backup. This
+			 * condition needs to be checked for.
+			 */
+			uint8_t *backup_buf = malloc(subject->metadata_len);
+
+			if (backup_buf) {
+
+				backup_status = load_and_check_metadata(
+					subject->backup_metadata_volume,
+					backup_buf,
+					subject->metadata_len);
+
+				if ((backup_status == FWU_STATUS_SUCCESS) &&
+					(*(uint32_t *)backup_buf !=
+						*(uint32_t *)subject->metadata_cache)) {
+
+					/* Both copies have valid CRC but CRSs are different. Force
+					 * the backup to be repaired.
+					 */
+					backup_status = FWU_STATUS_UNKNOWN;
+				}
+
+				free(backup_buf);
+			}
+		} else {
+
+			/* Primary must have failed so use the backup copy. */
+			backup_status = load_and_check_metadata(subject->backup_metadata_volume,
+				subject->metadata_cache,
+				subject->metadata_len);
+
+			subject->is_valid = (backup_status == FWU_STATUS_SUCCESS);
+		}
+	}
+
+	/* Attempt a repair if necessary (and possible) */
+	if (subject->is_valid) {
+
+		if ((primary_status != FWU_STATUS_SUCCESS) && subject->primary_metadata_volume) {
+
+			IMSG("Repairing primary FWU metadata");
+
+			primary_status = store_metadata(
+				subject->primary_metadata_volume,
+				subject->metadata_cache,
+				subject->metadata_len);
+		}
+
+		if ((backup_status != FWU_STATUS_SUCCESS) && subject->backup_metadata_volume) {
+
+			IMSG("Repairing backup FWU metadata");
+
+			backup_status = store_metadata(
+				subject->backup_metadata_volume,
+				subject->metadata_cache,
+				subject->metadata_len);
+		}
+	}
+
+	/* Synchronize the view of the stored CRC */
+	if (subject->is_valid)
+		subject->stored_crc = *(uint32_t *)subject->metadata_cache;
+
+	return (subject->is_valid) ? FWU_STATUS_SUCCESS : FWU_STATUS_UNKNOWN;
+}
+
+int metadata_manager_update(
+	struct metadata_manager *subject,
+	uint32_t active_index,
+	uint32_t previous_active_index,
+	const struct fw_directory *fw_dir,
+	const struct bank_tracker *bank_tracker)
+{
+	int primary_status = FWU_STATUS_SUCCESS;
+	int backup_status = FWU_STATUS_SUCCESS;
+
+	subject->metadata_len = subject->serializer->size(fw_dir);
+
+	/* Serialize metadata into metadata cache */
+	subject->serializer->serialize(
+		active_index, previous_active_index,
+		fw_dir, bank_tracker,
+		subject->metadata_cache,
+		subject->metadata_max_len,
+		&subject->metadata_len);
+
+	/* Update cache copy with valid crc */
+	uint32_t calc_crc = crc32(0U,
+		subject->metadata_cache + sizeof(uint32_t),
+		subject->metadata_len - sizeof(uint32_t));
+	*(uint32_t *)subject->metadata_cache = calc_crc;
+
+	bool was_valid = subject->is_valid;
+
+	/* Cache has been updated so it now holds valid data */
+	subject->is_valid = true;
+	subject->is_dirty = true;
+
+	/* To prevent unnecessary flash writes, if after serialization, there
+	 * is no change to the metadata, skip the store operation.
+	 */
+	if (was_valid && (subject->stored_crc == *(uint32_t *)subject->metadata_cache))
+		return FWU_STATUS_SUCCESS;
+
+	/* Update NV storage - order of primary followed by backup is important to
+	 * defend against a power failure after updating the primary but before the backup.
+	 */
+	if (subject->primary_metadata_volume) {
+
+		primary_status = store_metadata(
+			subject->primary_metadata_volume,
+			subject->metadata_cache,
+			subject->metadata_len);
+	}
+
+	if (subject->backup_metadata_volume) {
+
+		backup_status = store_metadata(
+			subject->backup_metadata_volume,
+			subject->metadata_cache,
+			subject->metadata_len);
+	}
+
+	/* Updated the view of the stored data if successfully stored */
+	if ((primary_status == FWU_STATUS_SUCCESS) && (backup_status == FWU_STATUS_SUCCESS))
+		subject->stored_crc = *(uint32_t *)subject->metadata_cache;
+
+	return
+		(primary_status != FWU_STATUS_SUCCESS) ? primary_status :
+		(backup_status != FWU_STATUS_SUCCESS) ? backup_status :
+		FWU_STATUS_SUCCESS;
+}
+
+int metadata_manager_fetch(
+	struct metadata_manager *subject,
+	const uint8_t **data,
+	size_t *data_len,
+	bool *is_dirty)
+{
+	int status = FWU_STATUS_UNKNOWN;
+
+	if (subject->is_valid) {
+
+		*data = subject->metadata_cache;
+		*data_len = subject->metadata_len;
+		*is_dirty = subject->is_dirty;
+
+		subject->is_dirty = false;
+
+		status = FWU_STATUS_SUCCESS;
+	}
+
+	return status;
+}
+
+int metadata_manager_get_active_indices(
+	const struct metadata_manager *subject,
+	uint32_t *active_index,
+	uint32_t *previous_active_index)
+{
+	int status = FWU_STATUS_UNKNOWN;
+
+	if (subject->is_valid) {
+
+		subject->serializer->deserialize_active_indices(
+			active_index, previous_active_index,
+			subject->metadata_cache, subject->metadata_len);
+
+		status = FWU_STATUS_SUCCESS;
+	}
+
+	return status;
+}
+
+void metadata_manager_cache_invalidate(
+	struct metadata_manager *subject)
+{
+	subject->is_valid = false;
+}
+
+void metadata_manager_preload_bank_tracker(
+	const struct metadata_manager *subject,
+	struct bank_tracker *bank_tracker)
+{
+	subject->serializer->deserialize_bank_info(
+		bank_tracker,
+		subject->metadata_cache, subject->metadata_len);
+}
diff --git a/components/service/fwu/fw_store/banked/metadata_manager.h b/components/service/fwu/fw_store/banked/metadata_manager.h
new file mode 100644
index 0000000..67ad602
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/metadata_manager.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef METADATA_MANAGER_H
+#define METADATA_MANAGER_H
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct volume;
+struct fw_directory;
+struct bank_tracker;
+struct metadata_serializer;
+
+/**
+ * \brief metadata_manager structure definition
+ *
+ * Manages the FWU metadata seen by the boot loader.
+ */
+struct metadata_manager {
+
+	/* Volume objects for IO operations to NV storage */
+	struct volume *primary_metadata_volume;
+	struct volume *backup_metadata_volume;
+
+	/* Metadata serializer compatible with bootloader */
+	const struct metadata_serializer *serializer;
+
+	/* Cached copy of metadata */
+	bool is_dirty;
+	bool is_valid;
+	uint32_t stored_crc;
+	size_t metadata_len;
+	size_t metadata_max_len;
+	uint8_t *metadata_cache;
+};
+
+/**
+ * \brief Initialize the metadata_manager
+ *
+ * \param[in] subject       This instance
+ * \param[in] serializer    Metadata serializer to use
+ *
+ * \return Status 0 on success
+ */
+int metadata_manager_init(
+	struct metadata_manager *subject,
+	const struct metadata_serializer *serializer);
+
+/**
+ * \brief De-initialize the metadata_manager
+ *
+ * \param[in] subject      This instance
+ */
+void metadata_manager_deinit(
+	struct metadata_manager *subject);
+
+/**
+ * \brief Check integrity of FWU metadata and repair if necessary
+ *
+ * FWU metadata is vulnerable to corruption due to power failure during a
+ * write to storage. To mitigate this risk, a replica is maintained which
+ * the boot loader will use if necessary. When a corruption occurs, the
+ * corrupted copy is repaired by copying the intact replica. Returns
+ * failure if a repair was not possible.
+ *
+ * \param[in] subject      This instance
+ * \param[in] fw_dir       The fw_directory
+ *
+ * \return Status 0 if intact or if repair was successful
+ */
+int metadata_manager_check_and_repair(
+	struct metadata_manager *subject,
+	const struct fw_directory *fw_dir);
+
+/**
+ * \brief Update the FWU metadata seen by the boot loader
+ *
+ * \param[in]  subject        This instance
+ * \param[in]  active_index   The active bank index
+ * \param[in]  previous_active_index    The previous active bank index
+ * \param[in]  fw_dir         Source firmware directory
+ * \param[in]  bank_tracker   Provides bank state
+ *
+ * \return Status 0 if successful
+ */
+int metadata_manager_update(
+	struct metadata_manager *subject,
+	uint32_t active_index,
+	uint32_t previous_active_index,
+	const struct fw_directory *fw_dir,
+	const struct bank_tracker *bank_tracker);
+
+/**
+ * \brief Get the active index values from the metadata
+ *
+ * \param[in]  subject        This instance
+ * \param[out] active_index   The active bank index
+ * \param[out] previous_active_index   The previous active bank index
+ *
+ * \return Status 0 if successful
+ */
+int metadata_manager_get_active_indices(
+	const struct metadata_manager *subject,
+	uint32_t *active_index,
+	uint32_t *previous_active_index);
+
+/**
+ * \brief Fetch the FWU metadata that should be seen by the boot loader
+ *
+ * In deployments where the metadata manager is unable to update the metadata
+ * seen by the boot loader directly, this function outputs the most recently
+ * updated view of the metadata to enable a Nwd component to perform the necessary
+ * write to storage.
+ *
+ * \param[in]  subject   This instance
+ * \param[out] data      Outputs pointer to data
+ * \param[out] data_len  Length of metadata
+ * \param[out] is_dirty  True if updated since previous call
+ *
+ * \return Status 0 if successful
+ */
+int metadata_manager_fetch(
+	struct metadata_manager *subject,
+	const uint8_t **data,
+	size_t *data_len,
+	bool *is_dirty);
+
+/**
+ * \brief Invalidate the metadata cache
+ *
+ * \param[in]  subject   This instance
+ */
+void metadata_manager_cache_invalidate(
+	struct metadata_manager *subject);
+
+/**
+ * \brief Preload the bank_tracker with NV state from metadata
+ *
+ * \param[in]  subject   This instance
+ * \param[in]  bank_tracker  The bank_tracker to modify
+ *
+ * \return Status 0 if successful
+ */
+void metadata_manager_preload_bank_tracker(
+	const struct metadata_manager *subject,
+	struct bank_tracker *bank_tracker);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METADATA_MANAGER_H */
diff --git a/components/service/fwu/fw_store/banked/metadata_serializer/metadata_serializer.h b/components/service/fwu/fw_store/banked/metadata_serializer/metadata_serializer.h
new file mode 100644
index 0000000..2e411e5
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/metadata_serializer/metadata_serializer.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef METADATA_SERIALIZER_H
+#define METADATA_SERIALIZER_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct fw_directory;
+struct bank_tracker;
+
+/**
+ * \brief FWU metadata serializer interface
+ *
+ * Defines a common interface for FWU serialization operations. Because
+ * updates to the bootloader and update agent are likely to occur at different
+ * times, it is necessary for the update agent to have some agility over the
+ * metadata version that it generates in order to maintain compatibility with
+ * the bootloader. The active serializer is selected based on the version
+ * reported by the bootloader at boot time. Note that the early stage bootloader
+ * that interprets the metadata cannot be updated using the banked update
+ * mechanism so it is not possible to rely on a simultaneous update strategy.
+ * To provide a pathway for transitioning to a new metadata version,
+ * support for runtime selection of the metadata serializer is possible.
+ */
+struct metadata_serializer {
+
+	/**
+	 * \brief Serialize FWU metadata
+	 *
+	 *  Serialize FWU metadata using the presented inputs.
+	 *
+	 * \param[in]  active_index   The active bank index
+	 * \param[in]  previous_active_index    The previous active bank index
+	 * \param[in]  fw_dir         Source firmware directory
+	 * \param[in]  bank_tracker   source bank_tracker
+	 * \param[in]  buf            Serialize into this buffer
+	 * \param[in]  buf_size       Size of buffer
+	 * \param[out] metadata_len   Size of serialized metadata
+	 *
+	 * \return Status
+	 */
+	int (*serialize)(
+		uint32_t active_index,
+		uint32_t previous_active_index,
+		const struct fw_directory *fw_dir,
+		const struct bank_tracker *bank_tracker,
+		uint8_t *buf,
+		size_t buf_size,
+		size_t *metadata_len);
+
+	/**
+	 * \brief Return serialized FWU metadata size
+	 *
+	 * \param[in]  fw_dir         Source information
+	 *
+	 * \return Size in bytes
+	 */
+	size_t (*size)(
+		const struct fw_directory *fw_dir);
+
+	/**
+	 * \brief Return the maximum serialized FWU metadata size
+	 *
+	 * \return Size in bytes
+	 */
+	size_t (*max_size)(void);
+
+	/**
+	 * \brief De-serialize bank info information
+	 *
+	 * \param[in]  bank_tracker Destination bank_tracker
+	 * \param[in]  serialized_metadata  Serialized metadata
+	 * \param[in]  metadata_len   Length of serialized metadata
+	 */
+	void (*deserialize_bank_info)(
+		struct bank_tracker *bank_tracker,
+		const uint8_t *serialized_metadata,
+		size_t metadata_len);
+
+	/**
+	 * \brief De-serialize active indices
+	 *
+	 * \param[out] active_index   active_index value
+	 * \param[out] previous_active_index   previous_active_index value
+	 * \param[in]  serialized_metadata  Serialized metadata
+	 * \param[in]  metadata_len   Length of serialized metadata
+	 */
+	void (*deserialize_active_indices)(
+		uint32_t *active_index,
+		uint32_t *previous_active_index,
+		const uint8_t *serialized_metadata,
+		size_t metadata_len);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METADATA_SERIALIZER_H */
diff --git a/components/service/fwu/fw_store/banked/metadata_serializer/v1/component.cmake b/components/service/fwu/fw_store/banked/metadata_serializer/v1/component.cmake
new file mode 100644
index 0000000..17c50f1
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/metadata_serializer/v1/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 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}/metadata_serializer_v1.c"
+	)
diff --git a/components/service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.c b/components/service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.c
new file mode 100644
index 0000000..39e0fea
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <common/uuid/uuid.h>
+#include <protocols/service/fwu/packed-c/metadata_v1.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include <media/volume/volume.h>
+#include <media/volume/index/volume_index.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <service/fwu/fw_store/banked/bank_tracker.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include <service/fwu/fw_store/banked/metadata_serializer/metadata_serializer.h>
+#include "metadata_serializer_v1.h"
+
+
+static int serialize_image_entries(
+	struct fwu_metadata *metadata,
+	const struct fw_directory *fw_dir,
+	const struct bank_tracker *bank_tracker)
+{
+	size_t image_index = 0;
+
+	do {
+		/* Image entry indices in the metadata correspond to the image index
+		 * of the associate entry in the fw_directory.
+		 */
+		const struct image_info *image_info =
+			fw_directory_get_image_info(fw_dir, image_index);
+
+		if (!image_info)
+			break;
+
+		/* Information about storage for the image is retrieved from the configured
+		 * volume objects that provide access to the banked storage. Both volumes
+		 * are assumed to have the same parent location, identified by the location
+		 * uuid.
+		 */
+		struct uuid_octets location_uuid = {0};
+		struct fwu_image_entry *entry = &metadata->img_entry[image_index];
+
+		/* Serialize bank storage info */
+		for (size_t bank_index = 0;
+			bank_index < BANK_SCHEME_NUM_BANKS; bank_index++) {
+
+			struct uuid_octets img_uuid = {0};
+			struct volume *volume = NULL;
+
+			int status = volume_index_find(
+				banked_volume_id(
+					image_info->location_id,
+					banked_usage_id(bank_index)),
+				&volume);
+
+			if (!status && volume)
+				volume_get_storage_ids(volume, &img_uuid, &location_uuid);
+
+			struct fwu_image_properties *properties = &entry->img_props[bank_index];
+
+			memcpy(properties->img_uuid, img_uuid.octets, OSF_UUID_OCTET_LEN);
+			properties->reserved = 0;
+			properties->accepted = bank_tracker_is_accepted(
+				bank_tracker, bank_index, image_index) ? 1 : 0;
+		}
+
+		/* Serialize per-image UUIDs */
+		memcpy(entry->img_type_uuid,
+			image_info->img_type_uuid.octets, OSF_UUID_OCTET_LEN);
+		memcpy(entry->location_uuid,
+			location_uuid.octets, OSF_UUID_OCTET_LEN);
+
+		++image_index;
+
+	} while (true);
+
+	return FWU_STATUS_SUCCESS;
+}
+
+static size_t metadata_serializer_size(
+	const struct fw_directory *fw_dir)
+{
+	return
+		offsetof(struct fwu_metadata, img_entry) +
+		fw_directory_num_images(fw_dir) * sizeof(struct fwu_image_entry);
+}
+
+static size_t metadata_serializer_max_size(void)
+{
+	return
+		offsetof(struct fwu_metadata, img_entry) +
+		FWU_MAX_FW_DIRECTORY_ENTRIES * sizeof(struct fwu_image_entry);
+}
+
+static int metadata_serializer_serialize(
+	uint32_t active_index,
+	uint32_t previous_active_index,
+	const struct fw_directory *fw_dir,
+	const struct bank_tracker *bank_tracker,
+	uint8_t *buf,
+	size_t buf_size,
+	size_t *metadata_len)
+{
+	int status = FWU_STATUS_UNKNOWN;
+	size_t serialized_size = metadata_serializer_size(fw_dir);
+
+	*metadata_len = 0;
+
+	if (serialized_size <= buf_size) {
+
+		struct fwu_metadata *metadata = (struct fwu_metadata *)buf;
+
+		/* Serialize metadata header */
+		metadata->crc_32 = 0;
+		metadata->version = FWU_METADATA_VERSION;
+		metadata->active_index = active_index;
+		metadata->previous_active_index = previous_active_index;
+
+		/* Serialize image entries */
+		status = serialize_image_entries(metadata, fw_dir, bank_tracker);
+
+		if (status == FWU_STATUS_SUCCESS)
+			*metadata_len = serialized_size;
+	}
+
+	return status;
+}
+
+static void metadata_serializer_deserialize_bank_info(
+	struct bank_tracker *bank_tracker,
+	const uint8_t *serialized_metadata,
+	size_t metadata_len)
+{
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)serialized_metadata;
+
+	/* Assume referenced banks hold content */
+	if (metadata->active_index < BANK_SCHEME_NUM_BANKS)
+		bank_tracker_set_holds_content(bank_tracker, metadata->active_index);
+
+	if (metadata->previous_active_index < BANK_SCHEME_NUM_BANKS)
+		bank_tracker_set_holds_content(bank_tracker, metadata->previous_active_index);
+
+	/* Deserialize image accept state */
+	if (metadata_len >= offsetof(struct fwu_metadata, img_entry)) {
+
+		size_t num_images =
+			(metadata_len - offsetof(struct fwu_metadata, img_entry)) /
+			sizeof(struct fwu_image_entry);
+
+		for (size_t image_index = 0; image_index < num_images; image_index++) {
+
+			const struct fwu_image_entry *image_entry =
+				&metadata->img_entry[image_index];
+
+			for (size_t bank_index = 0;
+				bank_index < BANK_SCHEME_NUM_BANKS; bank_index++) {
+
+				if (image_entry->img_props[bank_index].accepted)
+					bank_tracker_accept(bank_tracker, bank_index, image_index);
+			}
+		}
+	}
+}
+
+static void metadata_serializer_deserialize_active_indices(
+	uint32_t *active_index,
+	uint32_t *previous_active_index,
+	const uint8_t *serialized_metadata,
+	size_t metadata_len)
+{
+	const struct fwu_metadata *metadata = (const struct fwu_metadata *)serialized_metadata;
+
+	assert(metadata_len >= offsetof(struct fwu_metadata, img_entry));
+
+	*active_index = metadata->active_index;
+	*previous_active_index = metadata->previous_active_index;
+}
+
+const struct metadata_serializer *metadata_serializer_v1(void)
+{
+	static const struct metadata_serializer serializer = {
+		metadata_serializer_serialize,
+		metadata_serializer_size,
+		metadata_serializer_max_size,
+		metadata_serializer_deserialize_bank_info,
+		metadata_serializer_deserialize_active_indices
+	};
+
+	return &serializer;
+}
diff --git a/components/service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.h b/components/service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.h
new file mode 100644
index 0000000..e64712b
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef METADATA_SERIALIZER_V1_H
+#define METADATA_SERIALIZER_V1_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct metadata_serializer;
+
+/**
+ * \brief Return a metadata_serializer for version 1 serialization
+ *
+ */
+const struct metadata_serializer *metadata_serializer_v1(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* METADATA_SERIALIZER_V1_H */
diff --git a/components/service/fwu/fw_store/banked/test/component.cmake b/components/service/fwu/fw_store/banked/test/component.cmake
new file mode 100644
index 0000000..4eb11f0
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/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}/metadata_manager_tests.cpp"
+	)
diff --git a/components/service/fwu/fw_store/banked/test/metadata_manager_tests.cpp b/components/service/fwu/fw_store/banked/test/metadata_manager_tests.cpp
new file mode 100644
index 0000000..0bc4310
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/test/metadata_manager_tests.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stdlib.h>
+#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/fwu/fw_store/banked/metadata_manager.h>
+#include <service/fwu/fw_store/banked/metadata_serializer/metadata_serializer.h>
+#include <service/fwu/fw_store/banked/metadata_serializer/v1/metadata_serializer_v1.h>
+#include <service/fwu/fw_store/banked/bank_tracker.h>
+#include <service/fwu/fw_store/banked/volume_id.h>
+#include <service/fwu/agent/fw_directory.h>
+#include <service/fwu/inspector/mock/mock_fw_inspector.h>
+#include <CppUTest/TestHarness.h>
+
+TEST_GROUP(FwuMetadataManagerTests)
+{
+	void setup()
+	{
+		int result;
+		struct uuid_octets partition_guid;
+
+		/* Default to V1 metadata serializer */
+		m_serializer = metadata_serializer_v1();
+
+		/* Construct storage */
+		volume_index_init();
+		m_block_store = ref_ram_gpt_block_store_factory_create();
+
+		/* Construct primary metadata volume */
+		uuid_guid_octets_from_canonical(&partition_guid,
+			DISK_GUID_UNIQUE_PARTITION_PRIMARY_FWU_METADATA);
+
+		result = block_volume_init(&m_primary_block_volume,
+			m_block_store, &partition_guid,
+			&m_primary_volume);
+
+		LONGS_EQUAL(0, result);
+		CHECK_TRUE(m_primary_volume);
+
+		/* Construct backup metadata volume */
+		uuid_guid_octets_from_canonical(&partition_guid,
+			DISK_GUID_UNIQUE_PARTITION_BACKUP_FWU_METADATA);
+
+		result = block_volume_init(&m_backup_block_volume,
+			m_block_store, &partition_guid,
+			&m_backup_volume);
+
+		LONGS_EQUAL(0, result);
+		CHECK_TRUE(m_backup_volume);
+
+		bank_tracker_init(&m_bank_tracker);
+		fw_directory_init(&m_fw_directory);
+
+		result = mock_fw_inspector_inspect(&m_fw_directory, BOOT_INDEX);
+		LONGS_EQUAL(0, result);
+	}
+
+	void teardown()
+	{
+		metadata_manager_deinit(&m_metadata_manager);
+		fw_directory_deinit(&m_fw_directory);
+		bank_tracker_deinit(&m_bank_tracker);
+
+		volume_index_clear();
+		block_volume_deinit(&m_primary_block_volume);
+		block_volume_deinit(&m_backup_block_volume);
+		ref_ram_gpt_block_store_factory_destroy(m_block_store);
+	}
+
+	void corrupt_metadata(struct volume *volume)
+	{
+		int status;
+		size_t metadata_size = m_serializer->size(&m_fw_directory);
+		uint8_t metadata_buf[metadata_size];
+		size_t actual_len = 0;
+
+		status = volume_open(volume);
+		LONGS_EQUAL(0, status);
+
+		status = volume_read(volume,
+			(uintptr_t)metadata_buf, metadata_size, &actual_len);
+		LONGS_EQUAL(0, status);
+		UNSIGNED_LONGS_EQUAL(metadata_size, actual_len);
+
+		/* Corrupt the first byte */
+		metadata_buf[0] ^= 0xff;
+
+		/* Erase contents and write back the corrupted copy */
+		status = volume_erase(volume);
+		LONGS_EQUAL(0, status);
+
+		status = volume_seek(volume, IO_SEEK_SET, 0);
+		LONGS_EQUAL(0, status);
+
+		status = volume_write(volume,
+			(const uintptr_t)metadata_buf, metadata_size, &actual_len);
+		LONGS_EQUAL(0, status);
+		UNSIGNED_LONGS_EQUAL(metadata_size, actual_len);
+
+		volume_close(volume);
+	}
+
+	static const unsigned int BOOT_INDEX = 1;
+
+	struct block_store *m_block_store;
+	struct block_volume m_primary_block_volume;
+	struct block_volume m_backup_block_volume;
+	struct volume *m_primary_volume;
+	struct volume *m_backup_volume;
+	struct metadata_manager m_metadata_manager;
+	struct fw_directory m_fw_directory;
+	struct bank_tracker m_bank_tracker;
+	const struct metadata_serializer *m_serializer;
+};
+
+TEST(FwuMetadataManagerTests, checkAndRepairAccessibleStorage)
+{
+	int result = 0;
+
+	/* Check configuration where metadata storage is accessible. Because neither
+	 * metadata copy has been initialized, initially expect the check and repair
+	 * operation to fail. */
+	result = volume_index_add(BANKED_VOLUME_ID_PRIMARY_METADATA, m_primary_volume);
+	LONGS_EQUAL(0, result);
+	result = volume_index_add(BANKED_VOLUME_ID_BACKUP_METADATA, m_backup_volume);
+	LONGS_EQUAL(0, result);
+
+	result = metadata_manager_init(&m_metadata_manager, m_serializer);
+	LONGS_EQUAL(0, result);
+
+	result = metadata_manager_check_and_repair(&m_metadata_manager, &m_fw_directory);
+	CHECK_TRUE(result != 0);
+
+	/* An update to the metadata should result in both primary and backup copies
+	 * being initialized. */
+	result = metadata_manager_update(&m_metadata_manager, 0, 1,
+		&m_fw_directory, &m_bank_tracker);
+	LONGS_EQUAL(0, result);
+
+	/* If the update was successful, check_and_repair shouldn't have anything to do. */
+	result = metadata_manager_check_and_repair(&m_metadata_manager, &m_fw_directory);
+	LONGS_EQUAL(0, result);
+
+	/* Invalidating the cache should force a reload but expect both volumes to hold
+	 * valid data.*/
+	metadata_manager_cache_invalidate(&m_metadata_manager);
+	result = metadata_manager_check_and_repair(&m_metadata_manager, &m_fw_directory);
+	LONGS_EQUAL(0, result);
+
+	/* Corrupt either copy randomly a few times and expect to always repair */
+	for (size_t i = 0; i < 100; ++i) {
+
+		struct volume *volume = (rand() & 1) ?
+			m_primary_volume :
+			m_backup_volume;
+
+		corrupt_metadata(volume);
+		metadata_manager_cache_invalidate(&m_metadata_manager);
+		result = metadata_manager_check_and_repair(&m_metadata_manager, &m_fw_directory);
+		LONGS_EQUAL(0, result);
+	}
+
+	/* Corrupt both copies - repair should not be possible */
+	corrupt_metadata(m_primary_volume);
+	corrupt_metadata(m_backup_volume);
+	metadata_manager_cache_invalidate(&m_metadata_manager);
+	result = metadata_manager_check_and_repair(&m_metadata_manager, &m_fw_directory);
+	CHECK_TRUE(result != 0);
+}
+
+TEST(FwuMetadataManagerTests, checkAndRepairInaccessibleStorage)
+{
+	int result = 0;
+
+	/* Check configuration where metadata storage is inaccessible (i.e. no
+	 * volumes added to volume_index). Expect the check to fail, indicating
+	 * that an update is required to initialise the cache. */
+	result = metadata_manager_init(&m_metadata_manager, m_serializer);
+	LONGS_EQUAL(0, result);
+
+	result = metadata_manager_check_and_repair(&m_metadata_manager, &m_fw_directory);
+	CHECK_TRUE(result != 0);
+}
\ No newline at end of file
diff --git a/components/service/fwu/fw_store/banked/volume_id.h b/components/service/fwu/fw_store/banked/volume_id.h
new file mode 100644
index 0000000..d29bccf
--- /dev/null
+++ b/components/service/fwu/fw_store/banked/volume_id.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef BANKED_FW_STORE_VOLUME_ID_H
+#define BANKED_FW_STORE_VOLUME_ID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief Storage volume IDs used by a banked_fw_store.
+ *
+ * These IDs are used to obtain deployment specific volume objects from
+ * the volume_index to enable storage IO operations to be performed. Use
+ * of volume IDs allows generic components to be decoupled from deployment
+ * specific code. A set of volume objects will be constructed for a deployment,
+ * each configured to provide access to each area of storage that holds
+ * NV data that will be modified during the FWU process.
+ *
+ * To allow images to be installed into different storage locations (e.g.
+ * different storage partitions), a volume ID numbering scheme is used for
+ * the banked_fw_store that combines a location ID with usage ID.
+ */
+
+/* FWU metadata volume IDs. A single location is assumed. */
+#define BANKED_VOLUME_ID_PRIMARY_METADATA       (0xffff0000)
+#define BANKED_VOLUME_ID_BACKUP_METADATA        (0xffff0001)
+
+/* Per-location usage IDs for the banked_fw store */
+#define BANKED_USAGE_ID_FW_BANK_A        (0)
+#define BANKED_USAGE_ID_FW_BANK_B        (1)
+
+/**
+ * \brief Return a volume id constructed from a usage and location id
+ *
+ * Banked storage may be distributed across multiple locations. This
+ * function creates a volume ID made up of a location ID and a usage ID.
+ * A platform integrator is free define as many location IDs as is
+ * necessary to enable different areas of firmware storage to be
+ * updated. A location could correspond to say a storage partition or
+ * storage managed by a separate MCU.
+ *
+ * \param[in]  location_id    Platform specific location id
+ * \param[in]  usage_id       The requires usage for the volume
+ *
+ * \return volume id
+ */
+static inline unsigned int banked_volume_id(
+	unsigned int location_id,
+	unsigned int usage_id)
+{
+	return (location_id << 16) | (usage_id & 0xffff);
+}
+
+/**
+ * \brief Return the usage id for the specified bank index
+ *
+ * \param[in]  bank_index    The bank index [0..1]
+ *
+ * \return Usage ID
+ */
+static inline unsigned int banked_usage_id(unsigned int bank_index)
+{
+	return (bank_index == 0) ?
+		BANKED_USAGE_ID_FW_BANK_A : BANKED_USAGE_ID_FW_BANK_B;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BANKED_FW_STORE_VOLUME_ID_H */
diff --git a/components/service/fwu/fw_store/fw_store.h b/components/service/fwu/fw_store/fw_store.h
new file mode 100644
index 0000000..b4eeade
--- /dev/null
+++ b/components/service/fwu/fw_store/fw_store.h
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef FW_STORE_H
+#define FW_STORE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Interface dependencies
+ */
+struct fw_store;
+struct fw_directory;
+struct image_info;
+struct installer;
+struct uuid_octets;
+
+/**
+ * \brief Synchronize fw_store state to the active state of the firmware
+ *
+ * Synchronizes the state of the fw_store to reflect the state of the
+ * booted firmware obtained from the firmware directory. The firmware directory
+ * will have been populated with information obtained from trusted
+ * sources such as the attestation service.
+ *
+ * \param[in]  fw_store    The subject fw_store
+ * \param[in]  fw_dir      The firmware directory to synchronize to
+ * \param[in]  boot_index  The boot_index reported by the bootloader
+ *
+ * \return FWU status code
+ */
+int fw_store_synchronize(
+	struct fw_store *fw_store,
+	struct fw_directory *fw_dir,
+	unsigned int boot_index);
+
+/**
+ * \brief Begin to install one or more images
+ *
+ * Should be called prior to installing one or more images into this fw_store.
+ * As long as success is returned, installation operations may follow.
+ *
+ * \param[in]  fw_store  The subject fw_store
+ *
+ * \return FWU status code
+ */
+int fw_store_begin_install(
+	struct fw_store *fw_store);
+
+/**
+ * \brief Cancel any install operation in progress
+ *
+ * \param[in]  fw_store  The subject fw_store
+ */
+void fw_store_cancel_install(
+	struct fw_store *fw_store);
+
+/**
+ * \brief Finalize the installation of a set of images
+ *
+ * Should be called after installing all images. Once finalized, the new
+ * installation may be activated in preparation for a trial of the update.
+ *
+ * \param[in]  fw_store  The subject fw_store
+ *
+ * \return FWU status code
+ */
+int fw_store_finalize_install(
+	struct fw_store *fw_store);
+
+/**
+ * \brief Select an installer
+ *
+ * Select an installer to handle installation of the image described by
+ * the image_info.
+ *
+ * \param[in]  fw_store  The subject fw_store
+ * \param[in]  image_info  Image info that describes the image to install
+ * \param[out] installer   Selected installer
+ *
+ * \return FWU status code
+ */
+int fw_store_select_installer(
+	struct fw_store *fw_store,
+	const struct image_info *image_info,
+	struct installer **installer);
+
+/**
+ * \brief Write image data during image installation
+ *
+ * \param[in]  fw_store  The subject fw_store
+ * \param[in]  installer     The selected installer
+ * \param[in]  data          Pointer to data
+ * \param[in]  data_len      The data length
+ *
+ * \return FWU status code
+ */
+int fw_store_write_image(
+	struct fw_store *fw_store,
+	struct installer *installer,
+	const uint8_t *data,
+	size_t data_len);
+
+/**
+ * \brief Commit image data
+ *
+ * Called after fw_store_write_image to commit all image data written for an image.
+ *
+ * \param[in]  fw_store   The subject fw_store
+ * \param[in]  installer  The selected installer
+ * \param[in]  image_info Info about the image
+ * \param[in]  accepted   Initial accepted state
+ *
+ * \return FWU status code
+ */
+int fw_store_commit_image(
+	struct fw_store *fw_store,
+	struct installer *installer,
+	const struct image_info *image_info,
+	bool accepted);
+
+/**
+ * \brief Notify that an updated image has been accepted
+ *
+ * \param[in]  fw_store    The subject fw_store
+ * \param[in]  image_info  Information about the accepted image
+ *
+ * \return True if all necessary images have been accepted
+ */
+bool fw_store_notify_accepted(
+	struct fw_store *fw_store,
+	const struct image_info *image_info);
+
+/**
+ * \brief Check if image is accepted
+ *
+ * \param[in]  fw_store    The subject fw_store
+ * \param[in]  image_info  Information about the image
+ *
+ * \return True if image has been accepted
+ */
+bool fw_store_is_accepted(
+	const struct fw_store *fw_store,
+	const struct image_info *image_info);
+
+/**
+ * \brief Check if the booted firmware is being trialed
+ *
+ * \param[in]  fw_store    The subject fw_store
+ *
+ * \return True if trialed
+ */
+bool fw_store_is_trial(
+	const struct fw_store *fw_store);
+
+/**
+ * \brief Commit to the complete update
+ *
+ * \param[in]  fw_store  The subject fw_store
+ *
+ * \return FWU status code
+ */
+int fw_store_commit_to_update(
+	struct fw_store *fw_store);
+
+/**
+ * \brief Revert back to the previous good version (if possible)
+ *
+ * \param[in]  fw_store    The subject fw_store
+ *
+ * \return FWU status code
+ */
+int fw_store_revert_to_previous(
+	struct fw_store *fw_store);
+
+/**
+ * \brief Export fw_store specific objects
+ *
+ * Provides a way of exporting objects from a concrete fw_store e.g.
+ * for coordinating with a Nwd client. A concrete fw_store may export
+ * [0..*] types of object.
+ *
+ * \param[in]  fw_store    The subject fw_store
+ * \param[in]  uuid        Identifies the object
+ * \param[out] data		   Pointer to data to the exported object
+ * \param[out] data_len    Length of the exported object
+ * \param[out] status      Status of the export operation
+ *
+ * \return True if UUID identifies an object held by the fw_store
+ */
+bool fw_store_export(
+	struct fw_store *fw_store,
+	const struct uuid_octets *uuid,
+	const uint8_t **data,
+	size_t *data_len,
+	int *status);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FW_STORE_H */
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index bd9e2cb..e6a8cef 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -1,5 +1,5 @@
 #-------------------------------------------------------------------------------
-# Copyright (c) 2020-2022, Arm Limited and Contributors. All rights reserved.
+# Copyright (c) 2020-2023, Arm Limited and Contributors. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
@@ -109,6 +109,9 @@
 		"components/service/block_storage/factory/ref_ram_gpt"
 		"components/service/block_storage/factory/client"
 		"components/service/fwu/agent"
+		"components/service/fwu/fw_store/banked"
+		"components/service/fwu/fw_store/banked/metadata_serializer/v1"
+		"components/service/fwu/fw_store/banked/test"
 		"components/service/fwu/installer"
 		"components/service/fwu/inspector/mock"
 		"components/service/crypto/client/cpp"