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/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;
+}