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