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 */