Add smm_variable service provider

Adds an implementation of a service provider for the smm_variable
service. Accepts uefi variable requests and uses the
uefi_variable_store as the backend for storage.

Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: I050fc26c03b4d0098ccc303a293b8faeab488419
diff --git a/components/service/smm_variable/provider/component.cmake b/components/service/smm_variable/provider/component.cmake
new file mode 100644
index 0000000..f20d18f
--- /dev/null
+++ b/components/service/smm_variable/provider/component.cmake
@@ -0,0 +1,13 @@
+#-------------------------------------------------------------------------------
+# 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}/smm_variable_provider.c"
+	)
diff --git a/components/service/smm_variable/provider/smm_variable_provider.c b/components/service/smm_variable/provider/smm_variable_provider.c
new file mode 100644
index 0000000..2de02a0
--- /dev/null
+++ b/components/service/smm_variable/provider/smm_variable_provider.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <protocols/service/smm_variable/smm_variable_proto.h>
+#include <protocols/rpc/common/packed-c/status.h>
+#include "smm_variable_provider.h"
+
+/* Service request handlers */
+static rpc_status_t get_variable_handler(void *context, struct call_req *req);
+static rpc_status_t get_next_variable_name_handler(void *context, struct call_req *req);
+static rpc_status_t set_variable_handler(void *context, struct call_req *req);
+static rpc_status_t query_variable_info_handler(void *context, struct call_req *req);
+static rpc_status_t exit_boot_service_handler(void *context, struct call_req *req);
+
+/* Handler mapping table for service */
+static const struct service_handler handler_table[] = {
+	{SMM_VARIABLE_FUNCTION_GET_VARIABLE,			get_variable_handler},
+	{SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME,	get_next_variable_name_handler},
+	{SMM_VARIABLE_FUNCTION_SET_VARIABLE,			set_variable_handler},
+	{SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO,		query_variable_info_handler},
+	{SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE,		exit_boot_service_handler}
+};
+
+struct rpc_interface *smm_variable_provider_init(
+	struct smm_variable_provider *context,
+ 	uint32_t owner_id,
+	size_t max_variables,
+	struct storage_backend *persistent_store,
+	struct storage_backend *volatile_store)
+{
+	struct rpc_interface *rpc_interface = NULL;
+
+	if (context) {
+
+		service_provider_init(&context->base_provider, context,
+					handler_table, sizeof(handler_table)/sizeof(struct service_handler));
+
+		if (uefi_variable_store_init(
+				&context->variable_store,
+				owner_id,
+				max_variables,
+				persistent_store,
+				volatile_store) == EFI_SUCCESS) {
+
+			rpc_interface = service_provider_get_rpc_interface(&context->base_provider);
+		}
+	}
+
+	return rpc_interface;
+}
+
+void smm_variable_provider_deinit(struct smm_variable_provider *context)
+{
+	uefi_variable_store_deinit(&context->variable_store);
+}
+
+static size_t sanitize_access_variable_param(struct call_req *req)
+{
+	size_t param_len = 0;
+	const struct call_param_buf *req_buf = call_req_get_req_buf(req);
+
+	if (req_buf->data_len >= SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_NAME_OFFSET) {
+
+		const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *param =
+			(const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE*)req_buf->data;
+		size_t length_with_name =
+			SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_DATA_OFFSET(param);
+
+		if (length_with_name <= req_buf->data_len) {
+
+			param_len = length_with_name;
+		}
+	}
+
+	return param_len;
+}
+
+static size_t sanitize_get_next_var_name_param(struct call_req *req)
+{
+	size_t param_len = 0;
+	const struct call_param_buf *req_buf = call_req_get_req_buf(req);
+
+	if (req_buf->data_len >= SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME_NAME_OFFSET) {
+
+		const SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME *param =
+			(const SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME*)req_buf->data;
+		size_t length_with_name =
+			SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME_TOTAL_SIZE(param);
+
+		if (length_with_name <= req_buf->data_len) {
+
+			param_len = length_with_name;
+		}
+	}
+
+	return param_len;
+}
+
+static rpc_status_t get_variable_handler(void *context, struct call_req *req)
+{
+	struct smm_variable_provider *this_instance = (struct smm_variable_provider*)context;
+
+	rpc_status_t rpc_status = TS_RPC_ERROR_INVALID_REQ_BODY;
+	size_t param_len = sanitize_access_variable_param(req);
+
+	if (param_len) {
+
+		/* Valid access variable header parameter */
+		rpc_status = TS_RPC_ERROR_INVALID_RESP_BODY;
+		struct call_param_buf *resp_buf = call_req_get_resp_buf(req);
+
+		if (resp_buf->size >= param_len) {
+
+			struct call_param_buf *req_buf = call_req_get_req_buf(req);
+			size_t max_data_len = resp_buf->size - param_len;
+
+			memmove(resp_buf->data, req_buf->data, param_len);
+
+			efi_status_t efi_status = uefi_variable_store_get_variable(
+				&this_instance->variable_store,
+				(SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE*)resp_buf->data,
+				max_data_len,
+				&resp_buf->data_len);
+
+			rpc_status = TS_RPC_CALL_ACCEPTED;
+			call_req_set_opstatus(req, efi_status);
+		}
+	}
+
+	return rpc_status;
+}
+
+static rpc_status_t get_next_variable_name_handler(void *context, struct call_req* req)
+{
+	struct smm_variable_provider *this_instance = (struct smm_variable_provider*)context;
+
+	rpc_status_t rpc_status = TS_RPC_ERROR_INVALID_REQ_BODY;
+	size_t param_len = sanitize_get_next_var_name_param(req);
+
+	if (param_len) {
+
+		/* Valid get next variable name header */
+		rpc_status = TS_RPC_ERROR_INVALID_RESP_BODY;
+		struct call_param_buf *resp_buf = call_req_get_resp_buf(req);
+
+		if (resp_buf->size >= param_len) {
+
+			struct call_param_buf *req_buf = call_req_get_req_buf(req);
+			size_t max_name_len = resp_buf->size -
+				SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME_NAME_OFFSET;
+
+			memmove(resp_buf->data, req_buf->data, param_len);
+
+			efi_status_t efi_status = uefi_variable_store_get_next_variable_name(
+				&this_instance->variable_store,
+				(SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME*)resp_buf->data,
+				max_name_len,
+				&resp_buf->data_len);
+
+			rpc_status = TS_RPC_CALL_ACCEPTED;
+			call_req_set_opstatus(req, efi_status);
+		}
+	}
+
+	return rpc_status;
+}
+
+static rpc_status_t set_variable_handler(void *context, struct call_req* req)
+{
+	struct smm_variable_provider *this_instance = (struct smm_variable_provider*)context;
+
+	rpc_status_t rpc_status = TS_RPC_ERROR_INVALID_REQ_BODY;
+	size_t param_len = sanitize_access_variable_param(req);
+
+	if (param_len) {
+
+		/* Access variable header is whole.  Check that buffer length can
+		 * accommodate the data.
+		 */
+		struct call_param_buf *req_buf = call_req_get_req_buf(req);
+		const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE *access_var =
+			(const SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE*)req_buf->data;
+
+		if (SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE_TOTAL_SIZE(access_var) <= req_buf->data_len) {
+
+			efi_status_t efi_status = uefi_variable_store_set_variable(
+				&this_instance->variable_store,
+				access_var);
+
+			rpc_status = TS_RPC_CALL_ACCEPTED;
+			call_req_set_opstatus(req, efi_status);
+		}
+	}
+
+	return rpc_status;
+}
+
+static rpc_status_t query_variable_info_handler(void *context, struct call_req* req)
+{
+	struct smm_variable_provider *this_instance = (struct smm_variable_provider*)context;
+	rpc_status_t rpc_status = TS_RPC_ERROR_NOT_READY;
+
+	/* todo */
+
+	return rpc_status;
+}
+
+static rpc_status_t exit_boot_service_handler(void *context, struct call_req* req)
+{
+	struct smm_variable_provider *this_instance = (struct smm_variable_provider*)context;
+	rpc_status_t rpc_status = TS_RPC_CALL_ACCEPTED;
+
+	efi_status_t efi_status = uefi_variable_store_exit_boot_service(&this_instance->variable_store);
+	call_req_set_opstatus(req, efi_status);
+
+	return rpc_status;
+}
diff --git a/components/service/smm_variable/provider/smm_variable_provider.h b/components/service/smm_variable/provider/smm_variable_provider.h
new file mode 100644
index 0000000..c679397
--- /dev/null
+++ b/components/service/smm_variable/provider/smm_variable_provider.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef SMM_VARIABLE_PROVIDER_H
+#define SMM_VARIABLE_PROVIDER_H
+
+#include <rpc/common/endpoint/rpc_interface.h>
+#include <service/common/provider/service_provider.h>
+#include <service/smm_variable/backend/uefi_variable_store.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The smm_variable_provider is a service provider that implements an RPC interface
+ * for an instance of the smm_variable service.
+ */
+struct smm_variable_provider
+{
+	struct service_provider base_provider;
+	struct uefi_variable_store variable_store;
+};
+
+/**
+ * \brief Initialize an instance of the smm_variable service provider
+ *
+ * Initializes an smm_variable service provider.  Returns an rpc_interface that should
+ * be associated with a suitable rpc endpoint.  Storage backends for NV and volatile
+ * stores are assumed to be deployment specific and are passed as initialization
+ * parameters.
+ *
+ * @param[in] context The instance to initialize
+ * @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 An rpc_interface or NULL on failure
+ */
+struct rpc_interface *smm_variable_provider_init(
+	struct smm_variable_provider *context,
+ 	uint32_t owner_id,
+	size_t max_variables,
+	struct storage_backend *persistent_store,
+	struct storage_backend *volatile_store);
+
+/**
+ * \brief Cleans up when the instance is no longer needed
+ *
+ * \param[in] context   The instance to de-initialize
+ */
+void smm_variable_provider_deinit(
+	struct smm_variable_provider *context);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SMM_VARIABLE_PROVIDER_H */
diff --git a/deployments/component-test/component-test.cmake b/deployments/component-test/component-test.cmake
index 1ffcb69..2fdc857 100644
--- a/deployments/component-test/component-test.cmake
+++ b/deployments/component-test/component-test.cmake
@@ -118,6 +118,7 @@
 		"components/service/test_runner/provider"
 		"components/service/test_runner/provider/serializer/packed-c"
 		"components/service/test_runner/provider/backend/null"
+		"components/service/smm_variable/provider"
 		"components/service/smm_variable/backend"
 		"components/service/smm_variable/backend/test"
 		"protocols/rpc/common/protobuf"