feat(el3-spmc): add support for FFA_MEM_PERM_GET and SET ABIs

Secure partition running at SEL0 does not have privilege to modify
translation tables. So it needs SPMC to map the regions for it. Add the
support to request memory map or region info using  FF-A interface.

Signed-off-by: Nishant Sharma <nishant.sharma@arm.com>
Signed-off-by: Achin Gupta <achin.gupta@arm.com>
Change-Id: I04a97899808bbd45eda24edf7bc74eaef96fb2ce
diff --git a/services/std_svc/spm/el3_spmc/spmc_main.c b/services/std_svc/spm/el3_spmc/spmc_main.c
index c4d5835..44d0ecc 100644
--- a/services/std_svc/spm/el3_spmc/spmc_main.c
+++ b/services/std_svc/spm/el3_spmc/spmc_main.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2022-2023, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -30,6 +30,17 @@
 
 #include <platform_def.h>
 
+/* FFA_MEM_PERM_* helpers */
+#define FFA_MEM_PERM_MASK		U(7)
+#define FFA_MEM_PERM_DATA_MASK		U(3)
+#define FFA_MEM_PERM_DATA_SHIFT		U(0)
+#define FFA_MEM_PERM_DATA_NA		U(0)
+#define FFA_MEM_PERM_DATA_RW		U(1)
+#define FFA_MEM_PERM_DATA_RES		U(2)
+#define FFA_MEM_PERM_DATA_RO		U(3)
+#define FFA_MEM_PERM_INST_EXEC          (U(0) << 2)
+#define FFA_MEM_PERM_INST_NON_EXEC      (U(1) << 2)
+
 /* Declare the maximum number of SPs and El3 LPs. */
 #define MAX_SP_LP_PARTITIONS SECURE_PARTITION_COUNT + MAX_EL3_LP_DESCS_COUNT
 
@@ -1505,6 +1516,223 @@
 }
 
 /*******************************************************************************
+ * Permissions are encoded using a different format in the FFA_MEM_PERM_* ABIs
+ * than in the Trusted Firmware, where the mmap_attr_t enum type is used. This
+ * function converts a permission value from the FF-A format to the mmap_attr_t
+ * format by setting MT_RW/MT_RO, MT_USER/MT_PRIVILEGED and
+ * MT_EXECUTE/MT_EXECUTE_NEVER. The other fields are left as 0 because they are
+ * ignored by the function xlat_change_mem_attributes_ctx().
+ ******************************************************************************/
+static unsigned int ffa_perm_to_mmap_perm(unsigned int perms)
+{
+	unsigned int tf_attr = 0U;
+	unsigned int access;
+
+	/* Deal with data access permissions first. */
+	access = (perms & FFA_MEM_PERM_DATA_MASK) >> FFA_MEM_PERM_DATA_SHIFT;
+
+	switch (access) {
+	case FFA_MEM_PERM_DATA_RW:
+		/* Return 0 if the execute is set with RW. */
+		if ((perms & FFA_MEM_PERM_INST_NON_EXEC) != 0) {
+			tf_attr |= MT_RW | MT_USER | MT_EXECUTE_NEVER;
+		}
+		break;
+
+	case FFA_MEM_PERM_DATA_RO:
+		tf_attr |= MT_RO | MT_USER;
+		/* Deal with the instruction access permissions next. */
+		if ((perms & FFA_MEM_PERM_INST_NON_EXEC) == 0) {
+			tf_attr |= MT_EXECUTE;
+		} else {
+			tf_attr |= MT_EXECUTE_NEVER;
+		}
+		break;
+
+	case FFA_MEM_PERM_DATA_NA:
+	default:
+		return tf_attr;
+	}
+
+	return tf_attr;
+}
+
+/*******************************************************************************
+ * Handler to set the permissions of a set of contiguous pages of a S-EL0 SP
+ ******************************************************************************/
+static uint64_t ffa_mem_perm_set_handler(uint32_t smc_fid,
+					 bool secure_origin,
+					 uint64_t x1,
+					 uint64_t x2,
+					 uint64_t x3,
+					 uint64_t x4,
+					 void *cookie,
+					 void *handle,
+					 uint64_t flags)
+{
+	struct secure_partition_desc *sp;
+	unsigned int idx;
+	uintptr_t base_va = (uintptr_t) x1;
+	size_t size = (size_t)(x2 * PAGE_SIZE);
+	uint32_t tf_attr;
+	int ret;
+
+	/* This request cannot originate from the Normal world. */
+	if (!secure_origin) {
+		return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED);
+	}
+
+	if (size == 0) {
+		return spmc_ffa_error_return(handle,
+					     FFA_ERROR_INVALID_PARAMETER);
+	}
+
+	/* Get the context of the current SP. */
+	sp = spmc_get_current_sp_ctx();
+	if (sp == NULL) {
+		return spmc_ffa_error_return(handle,
+					     FFA_ERROR_INVALID_PARAMETER);
+	}
+
+	/* A S-EL1 SP has no business invoking this ABI. */
+	if (sp->runtime_el == S_EL1) {
+		return spmc_ffa_error_return(handle, FFA_ERROR_DENIED);
+	}
+
+	if ((x3 & ~((uint64_t)FFA_MEM_PERM_MASK)) != 0) {
+		return spmc_ffa_error_return(handle,
+					     FFA_ERROR_INVALID_PARAMETER);
+	}
+
+	/* Get the execution context of the calling SP. */
+	idx = get_ec_index(sp);
+
+	/*
+	 * Ensure that the S-EL0 SP is initialising itself. We do not need to
+	 * synchronise this operation through a spinlock since a S-EL0 SP is UP
+	 * and can only be initialising on this cpu.
+	 */
+	if (sp->ec[idx].rt_model != RT_MODEL_INIT) {
+		return spmc_ffa_error_return(handle, FFA_ERROR_DENIED);
+	}
+
+	VERBOSE("Setting memory permissions:\n");
+	VERBOSE("  Start address  : 0x%lx\n", base_va);
+	VERBOSE("  Number of pages: %lu (%zu bytes)\n", x2, size);
+	VERBOSE("  Attributes     : 0x%x\n", (uint32_t)x3);
+
+	/* Convert inbound permissions to TF-A permission attributes */
+	tf_attr = ffa_perm_to_mmap_perm((unsigned int)x3);
+	if (tf_attr == 0U) {
+		return spmc_ffa_error_return(handle,
+					     FFA_ERROR_INVALID_PARAMETER);
+	}
+
+	/* Request the change in permissions */
+	ret = xlat_change_mem_attributes_ctx(sp->xlat_ctx_handle,
+					     base_va, size, tf_attr);
+	if (ret != 0) {
+		return spmc_ffa_error_return(handle,
+					     FFA_ERROR_INVALID_PARAMETER);
+	}
+
+	SMC_RET1(handle, FFA_SUCCESS_SMC32);
+}
+
+/*******************************************************************************
+ * Permissions are encoded using a different format in the FFA_MEM_PERM_* ABIs
+ * than in the Trusted Firmware, where the mmap_attr_t enum type is used. This
+ * function converts a permission value from the mmap_attr_t format to the FF-A
+ * format.
+ ******************************************************************************/
+static unsigned int mmap_perm_to_ffa_perm(unsigned int attr)
+{
+	unsigned int perms = 0U;
+	unsigned int data_access;
+
+	if ((attr & MT_USER) == 0) {
+		/* No access from EL0. */
+		data_access = FFA_MEM_PERM_DATA_NA;
+	} else {
+		if ((attr & MT_RW) != 0) {
+			data_access = FFA_MEM_PERM_DATA_RW;
+		} else {
+			data_access = FFA_MEM_PERM_DATA_RO;
+		}
+	}
+
+	perms |= (data_access & FFA_MEM_PERM_DATA_MASK)
+		<< FFA_MEM_PERM_DATA_SHIFT;
+
+	if ((attr & MT_EXECUTE_NEVER) != 0U) {
+		perms |= FFA_MEM_PERM_INST_NON_EXEC;
+	}
+
+	return perms;
+}
+
+/*******************************************************************************
+ * Handler to get the permissions of a set of contiguous pages of a S-EL0 SP
+ ******************************************************************************/
+static uint64_t ffa_mem_perm_get_handler(uint32_t smc_fid,
+					 bool secure_origin,
+					 uint64_t x1,
+					 uint64_t x2,
+					 uint64_t x3,
+					 uint64_t x4,
+					 void *cookie,
+					 void *handle,
+					 uint64_t flags)
+{
+	struct secure_partition_desc *sp;
+	unsigned int idx;
+	uintptr_t base_va = (uintptr_t)x1;
+	uint32_t tf_attr = 0;
+	int ret;
+
+	/* This request cannot originate from the Normal world. */
+	if (!secure_origin) {
+		return spmc_ffa_error_return(handle, FFA_ERROR_NOT_SUPPORTED);
+	}
+
+	/* Get the context of the current SP. */
+	sp = spmc_get_current_sp_ctx();
+	if (sp == NULL) {
+		return spmc_ffa_error_return(handle,
+					     FFA_ERROR_INVALID_PARAMETER);
+	}
+
+	/* A S-EL1 SP has no business invoking this ABI. */
+	if (sp->runtime_el == S_EL1) {
+		return spmc_ffa_error_return(handle, FFA_ERROR_DENIED);
+	}
+
+	/* Get the execution context of the calling SP. */
+	idx = get_ec_index(sp);
+
+	/*
+	 * Ensure that the S-EL0 SP is initialising itself. We do not need to
+	 * synchronise this operation through a spinlock since a S-EL0 SP is UP
+	 * and can only be initialising on this cpu.
+	 */
+	if (sp->ec[idx].rt_model != RT_MODEL_INIT) {
+		return spmc_ffa_error_return(handle, FFA_ERROR_DENIED);
+	}
+
+	/* Request the permissions */
+	ret = xlat_get_mem_attributes_ctx(sp->xlat_ctx_handle, base_va, &tf_attr);
+	if (ret != 0) {
+		return spmc_ffa_error_return(handle,
+					     FFA_ERROR_INVALID_PARAMETER);
+	}
+
+	/* Convert TF-A permission to FF-A permissions attributes. */
+	x2 = mmap_perm_to_ffa_perm(tf_attr);
+
+	SMC_RET3(handle, FFA_SUCCESS_SMC32, 0, x2);
+}
+
+/*******************************************************************************
  * This function will parse the Secure Partition Manifest. From manifest, it
  * will fetch details for preparing Secure partition image context and secure
  * partition image boot arguments if any.
@@ -2075,6 +2303,14 @@
 		return spmc_ffa_mem_reclaim(smc_fid, secure_origin, x1, x2, x3,
 					    x4, cookie, handle, flags);
 
+	case FFA_MEM_PERM_GET:
+		return ffa_mem_perm_get_handler(smc_fid, secure_origin, x1, x2,
+						x3, x4, cookie, handle, flags);
+
+	case FFA_MEM_PERM_SET:
+		return ffa_mem_perm_set_handler(smc_fid, secure_origin, x1, x2,
+						x3, x4, cookie, handle, flags);
+
 	default:
 		WARN("Unsupported FF-A call 0x%08x.\n", smc_fid);
 		break;