Add basic uefi_variable_store implementation

Adds an implementation of the uefi_variable_store that forms
the backend for the smm_variable service provider. Orphan
variable index clean-up is not yet implemented.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I0924475a8819f488703065900d8efd01b48d42cf
diff --git a/components/service/smm_variable/backend/component.cmake b/components/service/smm_variable/backend/component.cmake
new file mode 100644
index 0000000..707d983
--- /dev/null
+++ b/components/service/smm_variable/backend/component.cmake
@@ -0,0 +1,14 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021, 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}/uefi_variable_store.c"
+	"${CMAKE_CURRENT_LIST_DIR}/variable_index.c"
+	)
diff --git a/components/service/smm_variable/backend/uefi_variable_store.c b/components/service/smm_variable/backend/uefi_variable_store.c
new file mode 100644
index 0000000..abcaf2c
--- /dev/null
+++ b/components/service/smm_variable/backend/uefi_variable_store.c
@@ -0,0 +1,463 @@
+/*
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "uefi_variable_store.h"
+
+/* Private functions */
+static void load_variable_index(
+	struct uefi_variable_store *context);
+
+static efi_status_t sync_variable_index(
+	struct uefi_variable_store *context);
+
+static efi_status_t check_access_permitted(
+	const struct uefi_variable_store *context,
+	const struct variable_info *info);
+
+static efi_status_t store_variable_data(
+	struct uefi_variable_store *context,
+	const struct variable_info *info,
+	const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var);
+
+static efi_status_t load_variable_data(
+	struct uefi_variable_store *context,
+	const struct variable_info *info,
+	SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var,
+	size_t max_data_len);
+
+static void purge_orphan_index_entries(
+	struct uefi_variable_store *context);
+
+static efi_status_t psa_to_efi_storage_status(
+	psa_status_t psa_status);
+
+static efi_status_t check_name_terminator(
+	const int16_t *name,
+	size_t name_size);
+
+/* Private UID for storing the variable index */
+#define VARIABLE_INDEX_STORAGE_UID			(1)
+
+
+efi_status_t uefi_variable_store_init(
+	struct uefi_variable_store *context,
+	uint32_t owner_id,
+	size_t max_variables,
+	struct storage_backend *persistent_store,
+	struct storage_backend *volatile_store)
+{
+	efi_status_t status = EFI_SUCCESS;
+
+	context->persistent_store = persistent_store;
+	context->volatile_store = volatile_store;
+
+	context->owner_id = owner_id;
+	context->is_boot_service = true;
+
+	status = variable_index_init(&context->variable_index, max_variables);
+
+	if (status == EFI_SUCCESS) {
+
+		/* Allocate a buffer for synchronizing the variable index with the persistent store */
+		context->index_sync_buffer_size = variable_index_max_dump_size(&context->variable_index);
+		context->index_sync_buffer = NULL;
+
+		if (context->index_sync_buffer_size) {
+
+			context->index_sync_buffer = malloc(context->index_sync_buffer_size);
+			status = (context->index_sync_buffer) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
+		}
+
+		/* Load the variable index with NV variable info from the persistent store */
+		if (context->index_sync_buffer) {
+
+			load_variable_index(context);
+			purge_orphan_index_entries(context);
+		}
+	}
+
+	return status;
+}
+
+void uefi_variable_store_deinit(
+	struct uefi_variable_store *context)
+{
+	variable_index_deinit(&context->variable_index);
+
+	free(context->index_sync_buffer);
+	context->index_sync_buffer = NULL;
+}
+
+efi_status_t uefi_variable_store_set_variable(
+	struct uefi_variable_store *context,
+	const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var)
+{
+	efi_status_t status = check_name_terminator(var->Name, var->NameSize);
+	if (status != EFI_SUCCESS) return status;
+
+	bool should_sync_index = false;
+
+	/* Find in index */
+	const struct variable_info *info = variable_index_find(
+		&context->variable_index,
+		&var->Guid,
+		var->NameSize,
+		var->Name);
+
+	if (info) {
+
+		/* Variable already exists */
+		status = check_access_permitted(context, info);
+
+		if (!status) {
+
+			variable_index_update_attributes(
+				&context->variable_index,
+				info,
+				var->Attributes);
+		}
+		else {
+
+			/* Access forbidden */
+			info = NULL;
+		}
+	}
+	else {
+
+		/* It's a new variable */
+		info = variable_index_add(
+			&context->variable_index,
+			&var->Guid,
+			var->NameSize,
+			var->Name,
+			var->Attributes);
+	}
+
+	/* Store variable data */
+	if (info) {
+
+		status = 0;
+
+		/* The order of these operations is important.  The variable
+		 * index is always synchronized to NV storage first, then the
+		 * variable data is stored.  If the data store operation fails
+		 * or doesn't happen due to a power failure, the inconistency
+		 * between the variable index and the persistent store is
+		 * detectable and may be corrected by purging the corresponding
+		 * index entry.
+		 */
+		if (info->attributes & EFI_VARIABLE_NON_VOLATILE) {
+
+			status = sync_variable_index(context);
+		}
+
+		if (!status) {
+
+			status = store_variable_data(context, info, var);
+		}
+	}
+
+	return status;
+}
+
+efi_status_t uefi_variable_store_get_variable(
+	struct uefi_variable_store *context,
+	SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var,
+	size_t max_data_len,
+	size_t *total_length)
+{
+	efi_status_t status = check_name_terminator(var->Name, var->NameSize);
+	if (status != EFI_SUCCESS) return status;
+
+	status = EFI_NOT_FOUND;
+	*total_length = 0;
+
+	const struct variable_info *info = variable_index_find(
+		&context->variable_index,
+		&var->Guid,
+		var->NameSize,
+		var->Name);
+
+	if (info) {
+
+		/* Variable already exists */
+		status = check_access_permitted(context, info);
+
+		if (status == EFI_SUCCESS) {
+
+			status = load_variable_data(context, info, var, max_data_len);
+			var->Attributes = info->attributes;
+			*total_length = SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_TOTAL_SIZE(var);
+		}
+	}
+
+	return status;
+}
+
+efi_status_t uefi_variable_store_get_next_variable_name(
+	struct uefi_variable_store *context,
+	SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME *cur,
+	size_t max_name_len,
+	size_t *total_length)
+{
+	efi_status_t status = check_name_terminator(cur->Name, cur->NameSize);
+	if (status != EFI_SUCCESS) return status;
+
+	status = EFI_NOT_FOUND;
+	*total_length = 0;
+
+	const struct variable_info *info = variable_index_find_next(
+		&context->variable_index,
+		&cur->Guid,
+		cur->NameSize,
+		cur->Name);
+
+	if (info && (info->name_size <= max_name_len)) {
+
+		cur->Guid = info->guid;
+		cur->NameSize = info->name_size;
+		memcpy(cur->Name, info->name, info->name_size);
+
+		*total_length = SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME_TOTAL_SIZE(cur);
+
+		status = EFI_SUCCESS;
+	}
+
+	return status;
+}
+
+efi_status_t uefi_variable_store_query_variable_info(
+	struct uefi_variable_store *context,
+	SMM_VARIABLE_COMMUNICATE_QUERY_VARIABLE_INFO *cur)
+{
+	efi_status_t status = EFI_UNSUPPORTED;
+
+
+	return status;
+}
+
+efi_status_t uefi_variable_store_exit_boot_service(
+	struct uefi_variable_store *context)
+{
+	context->is_boot_service = false;
+	return EFI_SUCCESS;
+}
+
+static void load_variable_index(
+	struct uefi_variable_store *context)
+{
+	struct storage_backend *persistent_store = context->persistent_store;
+
+	if (persistent_store) {
+
+		size_t data_len = 0;
+
+		psa_status_t psa_status = persistent_store->interface->get(
+			persistent_store->context,
+			context->owner_id,
+			VARIABLE_INDEX_STORAGE_UID,
+			0,
+			context->index_sync_buffer_size,
+			context->index_sync_buffer,
+			&data_len);
+
+		if (psa_status == PSA_SUCCESS) {
+
+			variable_index_restore(&context->variable_index, data_len, context->index_sync_buffer);
+		}
+	}
+}
+
+static efi_status_t sync_variable_index(
+	struct uefi_variable_store *context)
+{
+	efi_status_t status = EFI_SUCCESS;
+
+	/* Sync the varibale index to storage if anything is dirty */
+	size_t data_len = 0;
+
+	bool is_dirty = variable_index_dump(
+		&context->variable_index,
+		context->index_sync_buffer_size,
+		context->index_sync_buffer,
+		&data_len);
+
+	if (is_dirty) {
+
+		struct storage_backend *persistent_store = context->persistent_store;
+
+		if (persistent_store) {
+
+			psa_status_t psa_status = persistent_store->interface->set(
+				persistent_store->context,
+				context->owner_id,
+				VARIABLE_INDEX_STORAGE_UID,
+				data_len,
+				context->index_sync_buffer,
+				PSA_STORAGE_FLAG_NONE);
+
+			status = psa_to_efi_storage_status(psa_status);
+		}
+	}
+
+	return status;
+}
+
+static efi_status_t check_access_permitted(
+	const struct uefi_variable_store *context,
+	const struct variable_info *info)
+{
+	efi_status_t status = EFI_SUCCESS;
+
+	if (info->attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS) {
+
+		if (!context->is_boot_service) status = EFI_ACCESS_DENIED;
+	}
+	else if (info->attributes & EFI_VARIABLE_RUNTIME_ACCESS) {
+
+		if (context->is_boot_service) status = EFI_ACCESS_DENIED;
+	}
+
+	return status;
+}
+
+static efi_status_t store_variable_data(
+	struct uefi_variable_store *context,
+	const struct variable_info *info,
+	const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var)
+{
+	psa_status_t psa_status = PSA_SUCCESS;
+	size_t data_len = var->DataSize;
+	const uint8_t *data = (const uint8_t*)var +
+		SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_DATA_OFFSET(var);
+
+	bool is_nv = (info->attributes & EFI_VARIABLE_NON_VOLATILE);
+
+	struct storage_backend *storage_backend = (is_nv) ?
+		context->persistent_store :
+		context->volatile_store;
+
+	if (storage_backend) {
+
+		psa_status = storage_backend->interface->set(
+			storage_backend->context,
+			context->owner_id,
+			info->uid,
+			data_len,
+			data,
+			PSA_STORAGE_FLAG_NONE);
+	}
+
+	if ((psa_status != PSA_SUCCESS) && is_nv) {
+
+		/* A storage failure has occurred so attempt to fix any
+		* mismatch between the variable index and stored NV variables.
+		*/
+		purge_orphan_index_entries(context);
+	}
+
+	return psa_to_efi_storage_status(psa_status);
+}
+
+static efi_status_t load_variable_data(
+	struct uefi_variable_store *context,
+	const struct variable_info *info,
+	SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var,
+	size_t max_data_len)
+{
+	psa_status_t psa_status = PSA_SUCCESS;
+	size_t data_len = 0;
+	uint8_t *data = (uint8_t*)var +
+		SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_DATA_OFFSET(var);
+
+	bool is_nv = (info->attributes & EFI_VARIABLE_NON_VOLATILE);
+
+	struct storage_backend *storage_backend = (is_nv) ?
+		context->persistent_store :
+		context->volatile_store;
+
+	if (storage_backend) {
+
+		psa_status = storage_backend->interface->get(
+			storage_backend->context,
+			context->owner_id,
+			info->uid,
+			0,
+			max_data_len,
+			data,
+			&data_len);
+
+		var->DataSize = data_len;
+	}
+
+	return psa_to_efi_storage_status(psa_status);
+}
+
+static void purge_orphan_index_entries(
+	struct uefi_variable_store *context)
+{
+
+}
+
+static efi_status_t psa_to_efi_storage_status(
+	psa_status_t psa_status)
+{
+	efi_status_t efi_status = EFI_DEVICE_ERROR;
+
+	switch(psa_status)
+	{
+		case PSA_SUCCESS:
+			efi_status = EFI_SUCCESS;
+			break;
+		case PSA_ERROR_NOT_PERMITTED:
+			efi_status = EFI_ACCESS_DENIED;
+			break;
+		case PSA_ERROR_INVALID_ARGUMENT:
+			efi_status = EFI_INVALID_PARAMETER;
+			break;
+		case PSA_ERROR_BAD_STATE:
+			efi_status = EFI_NOT_READY;
+			break;
+		case PSA_ERROR_BUFFER_TOO_SMALL:
+			efi_status = EFI_BUFFER_TOO_SMALL;
+			break;
+		case PSA_ERROR_DOES_NOT_EXIST:
+			efi_status = EFI_NOT_FOUND;
+			break;
+		case PSA_ERROR_INSUFFICIENT_MEMORY:
+			efi_status = EFI_OUT_OF_RESOURCES;
+			break;
+		case PSA_ERROR_INSUFFICIENT_STORAGE:
+			efi_status = EFI_OUT_OF_RESOURCES;
+			break;
+		case PSA_ERROR_STORAGE_FAILURE:
+			efi_status = EFI_DEVICE_ERROR;
+			break;
+		case PSA_STATUS_HARDWARE_FAILURE:
+			efi_status = EFI_DEVICE_ERROR;
+			break;
+		default:
+			break;
+	}
+
+	return efi_status;
+}
+
+static efi_status_t check_name_terminator(
+	const int16_t *name,
+	size_t name_size)
+{
+	/* Variable names must be null terminated */
+	if (name_size < sizeof(int16_t) || name[name_size/sizeof (int16_t) - 1] != L'\0') {
+
+		return EFI_INVALID_PARAMETER;
+	}
+
+	return EFI_SUCCESS;
+}
diff --git a/components/service/smm_variable/backend/uefi_variable_store.h b/components/service/smm_variable/backend/uefi_variable_store.h
new file mode 100644
index 0000000..ea25918
--- /dev/null
+++ b/components/service/smm_variable/backend/uefi_variable_store.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef UEFI_VARIABLE_STORE_H
+#define UEFI_VARIABLE_STORE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <protocols/common/efi/efi_status.h>
+#include <protocols/service/smm_variable/smm_variable_proto.h>
+#include <service/secure_storage/backend/storage_backend.h>
+#include "variable_index.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief uefi_variable_store structure definition
+ *
+ * A uefi_variable_store provides a variable store using a persistent and a
+ * volatile storage backend.  The persistent storage backend may be realized
+ * by another trusted service such as the ptotected storage or internal trusted
+ * storage service.
+ */
+struct uefi_variable_store
+{
+	bool is_boot_service;
+	uint32_t owner_id;
+	uint8_t *index_sync_buffer;
+	size_t index_sync_buffer_size;
+	struct variable_index variable_index;
+	struct storage_backend *persistent_store;
+	struct storage_backend *volatile_store;
+};
+
+/**
+ * @brief      Initialises a uefi_variable_store
+ *
+ * Initializes an instance of the uefi_variable_store.  The provided storage backends
+ * are used to store variables and to store persistent metadata from the variable
+ * register.
+ *
+ * @param[in]  context uefi_variable_store instance
+ * @param[in]  owner_id The id of the owning security domain (e.g. partition id)
+ * @param[in]  max_variables The maximum number of stored variables
+ * @param[in]  persistent_store The persistent storage backend to use
+ * @param[in]  volatile_store The volatile storage backend to use
+ *
+ * @return     EFI_SUCCESS if initialized successfully
+ */
+efi_status_t uefi_variable_store_init(
+	struct uefi_variable_store *context,
+	uint32_t owner_id,
+	size_t max_variables,
+	struct storage_backend *persistent_store,
+	struct storage_backend *volatile_store);
+
+/**
+ * @brief      De-initialises a uefi_variable_store
+ *
+ * @param[in]  context uefi_variable_store instance
+ */
+void uefi_variable_store_deinit(
+	struct uefi_variable_store *context);
+
+/**
+ * @brief      Set variable
+ *
+ * Corresponds to the SetVariable UEFI operation
+ *
+ * @param[in]  context uefi_variable_store instance
+ * @param[in]  var The 'access variable' structure
+ *
+ * @return     EFI_SUCCESS if succesful
+ */
+efi_status_t uefi_variable_store_set_variable(
+	struct uefi_variable_store *context,
+	const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var);
+
+/**
+ * @brief      Get variable
+ *
+ * Corresponds to the GetVariable UEFI operation
+ *
+ * @param[in]  context uefi_variable_store instance
+ * @param[out] var The 'access variable' structure
+ * @param[in]  max_data_len The maximum variable data length
+ * @param[out] total_len The total length of the header + data
+ *
+ * @return     EFI_SUCCESS if succesful
+ */
+efi_status_t uefi_variable_store_get_variable(
+	struct uefi_variable_store *context,
+	SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *var,
+	size_t max_data_len,
+	size_t *total_length);
+
+/**
+ * @brief      Get next variable name
+ *
+ * Used for enumerating the store contents
+ *
+ * @param[in]  context uefi_variable_store instance
+ * @param[out] cur Current variable name
+ * @param[in]  max_name_len The maximum variable name length
+ * @param[out] total_len The total length of the output
+ *
+ * @return     EFI_SUCCESS if succesful
+ */
+efi_status_t uefi_variable_store_get_next_variable_name(
+	struct uefi_variable_store *context,
+	SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME *cur,
+	size_t max_name_len,
+	size_t *total_length);
+
+/**
+ * @brief      Query for variable info
+ *
+ * @param[in]  context uefi_variable_store instance
+ * @param[out] info Returns info
+ *
+ * @return     EFI_SUCCESS if succesful
+ */
+efi_status_t uefi_variable_store_query_variable_info(
+	struct uefi_variable_store *context,
+	SMM_VARIABLE_COMMUNICATE_QUERY_VARIABLE_INFO *cur);
+
+/**
+ * @brief      Exit boot service
+ *
+ * Called when the UEFI boot phase is complete.  Used for boot only
+ * access control.
+ *
+ * @param[in]  context uefi_variable_store instance
+  *
+ * @return     EFI_SUCCESS if succesful
+ */
+efi_status_t uefi_variable_store_exit_boot_service(
+	struct uefi_variable_store *context);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UEFI_VARIABLE_STORE_H */
diff --git a/components/service/smm_variable/backend/variable_index.c b/components/service/smm_variable/backend/variable_index.c
new file mode 100644
index 0000000..a9484dc
--- /dev/null
+++ b/components/service/smm_variable/backend/variable_index.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "variable_index.h"
+
+/* Private functions */
+static uint64_t name_hash(
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name)
+{
+	/* Using djb2 hash by Dan Bernstein */
+	uint64_t hash = 5381;
+
+	/* Calculate hash over GUID */
+	hash = ((hash << 5) + hash) + guid->Data1;
+	hash = ((hash << 5) + hash) + guid->Data2;
+	hash = ((hash << 5) + hash) + guid->Data3;
+
+	for (int i = 0; i < 8; ++i) {
+
+		hash = ((hash << 5) + hash) + guid->Data4[i];
+	}
+
+	/* Extend to cover name up to but not including null terminator */
+	for (int i = 0; i < name_size / sizeof(int16_t); ++i) {
+
+		if (!name[i]) break;
+		hash = ((hash << 5) + hash) + name[i];
+	}
+
+	return hash;
+}
+
+static uint64_t generate_uid(
+	const struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name)
+{
+	uint64_t uid = name_hash(guid, name_size, name);
+
+	/* todo - handle collision  */
+	(void)context;
+
+	return uid;
+}
+
+static int find_variable(
+	const struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name)
+{
+	int found_pos = -1;
+	uint64_t uid = name_hash(guid, name_size, name);
+
+	for (int pos = 0; pos < context->max_variables; pos++) {
+
+		if ((context->entries[pos].in_use) &&
+			(uid == context->entries[pos].info.uid)) {
+
+			found_pos = pos;
+			break;
+		}
+	}
+
+	return found_pos;
+}
+
+static int find_free(
+	const struct variable_index *context)
+{
+	int free_pos = -1;
+
+	for (int pos = 0; pos < context->max_variables; pos++) {
+
+		if (!context->entries[pos].in_use) {
+
+			free_pos = pos;
+			break;
+		}
+	}
+
+	return free_pos;
+}
+
+static void mark_dirty(struct variable_entry *entry)
+{
+	if (entry->info.attributes & EFI_VARIABLE_NON_VOLATILE)
+		entry->dirty = true;
+}
+
+/* Public functions */
+efi_status_t variable_index_init(
+	struct variable_index *context,
+	size_t max_variables)
+{
+	context->max_variables = max_variables;
+	context->entries = (struct variable_entry*)
+		malloc(sizeof(struct variable_entry) * max_variables);
+
+	if (context->entries) {
+		memset(context->entries, 0, sizeof(struct variable_entry) * max_variables);
+	}
+
+	return (context->entries) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
+}
+
+void variable_index_deinit(
+	struct variable_index *context)
+{
+	free(context->entries);
+}
+
+size_t variable_index_max_dump_size(
+	struct variable_index *context)
+{
+	return sizeof(struct variable_info) * context->max_variables;
+}
+
+const struct variable_info *variable_index_find(
+	const struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name)
+{
+	const struct variable_info *result = NULL;
+	int pos = find_variable(context, guid, name_size, name);
+
+	if (pos >= 0) {
+
+		result = &context->entries[pos].info;
+	}
+
+	return result;
+}
+
+const struct variable_info *variable_index_find_next(
+	const struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name)
+{
+	const struct variable_info *result = NULL;
+
+	if (name_size >= sizeof(int16_t)) {
+
+		/*
+		 * Name must be at least one character long to accommodate
+		 * the mandatory null terminator.
+		 */
+		if (name[0] != 0) {
+
+			/* Find next from current name */
+			int pos = find_variable(context, guid, name_size, name);
+
+			if (pos >= 0) {
+
+				/* Iterate to next used entry */
+				++pos;
+				while (pos < context->max_variables) {
+
+					if (context->entries[pos].in_use) {
+
+						result = &context->entries[pos].info;
+						break;
+					}
+
+					++pos;
+				}
+			}
+		}
+		else {
+
+			/* Find first */
+			int pos = 0;
+
+			while (pos < context->max_variables) {
+
+				if (context->entries[pos].in_use) {
+
+					result = &context->entries[pos].info;
+					break;
+				}
+
+				++pos;
+			}
+		}
+	}
+
+	return result;
+}
+
+static void set_variable_name(
+	struct variable_info *info,
+	size_t name_size,
+	const int16_t *name)
+{
+	size_t trimmed_size = 0;
+
+	/* Trim the saved name to only include a single null terminator.
+	 * Any additional terminators included in the client-set name size
+	 * are discarded.
+	 */
+	for (size_t i = 0; i < name_size; i++) {
+
+		++trimmed_size;
+		info->name[i] = name[i];
+
+		if (!name[i]) break;
+	}
+
+	info->name_size = trimmed_size * sizeof(int16_t);
+}
+
+const struct variable_info *variable_index_add(
+	struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name,
+	uint32_t attributes)
+{
+	struct variable_info *info = NULL;
+
+	if (name_size <= (VARIABLE_INDEX_MAX_NAME_SIZE * sizeof(int16_t))) {
+
+		int pos = find_free(context);
+
+		if (pos >= 0) {
+
+			struct variable_entry *entry = &context->entries[pos];
+
+			info = &entry->info;
+			info->uid = generate_uid(context, guid, name_size, name);
+			info->guid = *guid;
+			info->attributes = attributes;
+			set_variable_name(info, name_size, name);
+
+			mark_dirty(entry);
+			entry->in_use = true;
+		}
+	}
+
+	return info;
+}
+
+void variable_index_remove(
+	struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name)
+{
+	int pos = find_variable(context, guid, name_size, name);
+
+	if (pos >= 0) {
+
+		struct variable_entry *entry = &context->entries[pos];
+		mark_dirty(entry);
+		entry->in_use = false;
+
+		memset(&entry->info, 0, sizeof(struct variable_info));
+	}
+}
+
+void variable_index_update_attributes(
+	struct variable_index *context,
+	const struct variable_info *info,
+	uint32_t attributes)
+{
+	if (info) {
+
+		struct variable_info *modified_info = (struct variable_info*)info;
+
+		size_t info_offset = offsetof(struct variable_entry, info);
+		struct variable_entry *entry = (struct variable_entry*)((uint8_t*)modified_info - info_offset);
+
+		if (attributes != modified_info->attributes) {
+
+			modified_info->attributes = attributes;
+			mark_dirty(entry);
+		}
+	}
+}
+
+bool variable_index_dump(
+	struct variable_index *context,
+	size_t buffer_size,
+	uint8_t *buffer,
+	size_t *data_len)
+{
+	bool any_dirty = false;
+	uint8_t *dump_pos = buffer;
+	size_t bytes_dumped = 0;
+
+	for (int pos = 0; pos < context->max_variables; pos++) {
+
+		struct variable_entry *entry = &context->entries[pos];
+		struct variable_info *info = &entry->info;
+
+		if (entry->in_use &&
+			(info->attributes & EFI_VARIABLE_NON_VOLATILE) &&
+			((bytes_dumped + sizeof(struct variable_info)) <= buffer_size)) {
+
+			memcpy(dump_pos, info, sizeof(struct variable_info));
+			bytes_dumped += sizeof(struct variable_info);
+			dump_pos += sizeof(struct variable_info);
+		}
+
+		any_dirty |= entry->dirty;
+		entry->dirty = false;
+	}
+
+	*data_len = bytes_dumped;
+
+	return any_dirty;
+}
+
+size_t variable_index_restore(
+	const struct variable_index *context,
+	size_t data_len,
+	const uint8_t *buffer)
+{
+	size_t bytes_loaded = 0;
+	const uint8_t *load_pos = buffer;
+	int pos = 0;
+
+	while (bytes_loaded < data_len) {
+
+		if ((data_len - bytes_loaded) >= sizeof(struct variable_info)) {
+
+			struct variable_entry *entry = &context->entries[pos];
+			struct variable_info *info = &entry->info;
+
+			memcpy(info, load_pos, sizeof(struct variable_info));
+
+			entry->in_use = true;
+
+			bytes_loaded += sizeof(struct variable_info);
+			load_pos += sizeof(struct variable_info);
+
+			++pos;
+		}
+		else {
+
+			/* Not a whole number of variable_info structs! */
+			break;
+		}
+	}
+
+	return bytes_loaded;
+}
diff --git a/components/service/smm_variable/backend/variable_index.h b/components/service/smm_variable/backend/variable_index.h
new file mode 100644
index 0000000..85064a7
--- /dev/null
+++ b/components/service/smm_variable/backend/variable_index.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef VARIABLE_INDEX_H
+#define VARIABLE_INDEX_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <protocols/common/efi/efi_status.h>
+#include <protocols/service/smm_variable/smm_variable_proto.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Implementation limits
+ */
+#define VARIABLE_INDEX_MAX_NAME_SIZE		(32)
+
+/**
+ * \brief variable_info structure definition
+ *
+ * Holds information about a stored variable.
+ */
+struct variable_info
+{
+	EFI_GUID	guid;
+	size_t		name_size;
+	int16_t		name[VARIABLE_INDEX_MAX_NAME_SIZE];
+	uint32_t	attributes;
+	uint64_t	uid;
+};
+
+/**
+ * \brief An entry in the index
+ *
+ * Represents a store variable in the variable index.
+ */
+struct variable_entry
+{
+	struct 	variable_info info;
+
+	bool	in_use;
+	bool	dirty;
+};
+
+/**
+ * \brief variable_index structure definition
+ *
+ * Provides an index of stored variables to allow the uefi variable store
+ * contents to be enumerated.
+ */
+struct variable_index
+{
+	size_t max_variables;
+	struct variable_entry *entries;
+};
+
+/**
+ * @brief      Initialises a variable_index
+ *
+ * @param[in]  context variable_index
+ * @param[in]  max_variables The maximum number of stored variables
+ *
+ * @return     EFI_SUCCESS if initialized successfully
+ */
+efi_status_t variable_index_init(
+	struct variable_index *context,
+	size_t max_variables);
+
+/**
+ * @brief      De-initialises a variable_index
+ *
+ * @param[in]  context variable_index
+ */
+void variable_index_deinit(
+	struct variable_index *context);
+
+/**
+ * @brief      Returns the maximum dump size
+ *
+ * For a given maximum index size, returns the size of the
+ * buffer that is needed to hold all serialized variable_info
+ * objects.
+ *
+ * @param[in]  context variable_index
+ */
+size_t variable_index_max_dump_size(
+	struct variable_index *context);
+
+/**
+ * @brief      Find info about a variable
+ *
+ * @param[in]  context variable_index
+ * @param[in]  guid The variable's guid
+ * @param[in]  name_size The name parameter's size
+ * @param[in]  name The variable's name
+ *
+ * @return     Pointer to variable_info or NULL
+ */
+const struct variable_info *variable_index_find(
+	const struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name);
+
+/**
+ * @brief      Find the next variable in the index
+ *
+ * @param[in]  context variable_index
+ * @param[in]  guid The variable's guid
+ * @param[in]  name_size The name parameter's size
+ * @param[in]  name The variable's name
+ *
+ * @return     Pointer to variable_info or NULL
+ */
+const struct variable_info *variable_index_find_next(
+	const struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name);
+
+/**
+ * @brief      Add a new variable to the index
+ *
+ * @param[in]  context variable_index
+ * @param[in]  guid The variable's guid
+ * @param[in]  name_size The name parameter's size
+ * @param[in]  name The variable's name
+ * @param[in]  attributes The variable's attributes
+ *
+ * @return     Pointer to variable_info or NULL
+ */
+const struct variable_info *variable_index_add(
+	struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name,
+	uint32_t attributes);
+
+/**
+ * @brief      Remove a variable from the index
+ *
+ * Removes a variable from the index if it exists.
+ *
+ * @param[in]  context variable_index
+ * @param[in]  guid The variable's guid
+ * @param[in]  name_size The name parameter's size
+ * @param[in]  name The variable's name
+ */
+void variable_index_remove(
+	struct variable_index *context,
+	const EFI_GUID *guid,
+	size_t name_size,
+	const int16_t *name);
+
+/**
+ * @brief      Update variable attributes
+ *
+ * @param[in]  context variable_index
+ * @param[in]  info variable info
+ * @param[in]  attributes The variable's attributes
+ */
+void variable_index_update_attributes(
+	struct variable_index *context,
+	const struct variable_info *info,
+	uint32_t attributes);
+
+/**
+ * @brief      Dump the serialized index contents for persistent backup
+ *
+ * @param[in]  context variable_index
+ * @param[in]  buffer_size Size of destination buffer
+ * @param[in]  buffer Dump to this buffer
+ * @param[out] data_len Length of serialized data
+ *
+ * @return     True if there is unsaved data
+ */
+bool variable_index_dump(
+	struct variable_index *context,
+	size_t buffer_size,
+	uint8_t *buffer,
+	size_t *data_len);
+
+/**
+ * @brief      Restore the serialized index contents
+ *
+ * Should be called straight after the variable index is initialized to
+ * restore any NV variable info from persistent storage.
+ *
+ * @param[in]  context variable_index
+ * @param[in]  data_len The length of the data to load
+ * @param[in]  buffer Load from this buffer
+ *
+ * @return     Number of bytes loaded
+ */
+size_t variable_index_restore(
+	const struct variable_index *context,
+	size_t data_len,
+	const uint8_t *buffer);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* VARIABLE_INDEX_H */