feat(rmmd): add RMM_ALLOCATE_MEMORY SMC handler

At the moment any memory required by an R-EL2 hypervisor (RMM) needs to
be known at compile time: that sets the size of the .data and .bss
segments. Some resources depend on the particular machine this will be
running on, the prime example is TF-RMM's granule array, which needs to
know the maximum memory supported beforehand. Other data structures
might depend on the number of CPU cores.

To provide more flexibility, but keep the memory footprint as small as
possible, let's introduce some (limited) memory allocation SMC. Any RMM
implementation can ask EL3 for some memory, and would get the physical
address of a usable chunk of memory back. This is supposed to happen at
RMM boot time, so before the RMM concluded the boot phase with the
RMM_BOOT_COMPLETE SMC call. Also there is no provision to free memory
again, this would not be needed for the use case of sizing platform
resources, and avoids the complexity of a full-fledged memory allocator.

Another important motivation for this feature is Live Firmware
Activation, which would require to keep some state alive between
different instances of the RMM. This is achieved by adding an ID tag to
memory allocations. Subsequent requests using a previously used tag
would return the very same memory location, with the existing content.
TF-RMM would use that for the granule array and the VMID array, for a
start.

Add the new RMM_ALLOCATE_MEMORY command to the realm-EL3 SMC interface,
both in code and documentation. The actual memory allocator is very
simple: it will just pick the next matching chunk of memory from the top
end of the RMM carveout, keeping track of the tags on the way.
This way the memory allocation will grow down from the end of the
carveout, in a stack-like fashion, until it reaches the end of the RMM
payload, located at the beginning of the carveout.

This patch just provides the call, at this point there is no obligation
to use the feature, although future TF-RMM versions would rely on it.

Change-Id: I096ac8870ee38f44e18850779fcae829a43a8fd1
Signed-off-by: Andre Przywara <andre.przywara@arm.com>
diff --git a/docs/components/rmm-el3-comms-spec.rst b/docs/components/rmm-el3-comms-spec.rst
index 2693e58..fed313f 100644
--- a/docs/components/rmm-el3-comms-spec.rst
+++ b/docs/components/rmm-el3-comms-spec.rst
@@ -52,7 +52,7 @@
   - ``RES0``: Bit 31 of the version number is reserved 0 as to maintain
     consistency with the versioning schemes used in other parts of RMM.
 
-This document specifies the 0.5 version of Boot Interface ABI and RMM-EL3
+This document specifies the 0.6 version of Boot Interface ABI and RMM-EL3
 services specification and the 0.5 version of the Boot Manifest.
 
 .. _rmm_el3_boot_interface:
@@ -261,6 +261,8 @@
    0xC40001B3,``RMM_ATTEST_GET_PLAT_TOKEN``
    0xC40001B4,``RMM_EL3_FEATURES``
    0xC40001B5,``RMM_EL3_TOKEN_SIGN``
+   0xC40001B6,``RMM_MECID_KEY_UPDATE``
+   0xC40001B7,``RMM_ALLOCATE_MEMORY``
 
 RMM_RMI_REQ_COMPLETE command
 ============================
@@ -719,6 +721,62 @@
    ``E_RMM_OK``,No errors detected
 
 
+RMM_ALLOCATE_MEMORY command
+===========================
+
+This command is used to allocate memory for the RMM, during RMM boot time.
+This is not a fully featured dynamic memory allocator, since allocations cannot
+be freed again, and they must happen during the boot state of RMM.
+However it allows to size data structures in RMM based on runtime decisions,
+for instance depending on the number of cores or the amount of memory installed.
+
+FID
+---
+
+``0xC40001B7``
+
+Input values
+------------
+
+.. csv-table:: Input values for RMM_ALLOCATE_MEMORY
+   :header: "Name", "Register", "Field", "Type", "Description"
+   :widths: 1 1 1 1 5
+
+   fid,x0,[63:0],UInt64,Command FID
+   size,x1,[63:0],Size,"required size of the memory region, in bytes"
+   args,x2,[31:24],UInt64,"alignment requirement, in bits. A value of 16 would return a 64 KB aligned base address."
+   args,x2,[15:0],UInt64,"region identifier. An initial call with a certain ID
+   would return newly allocated memory, but subsequent calls would return the
+   pointer to this previously allocated memory region. An ID value of 0 will
+   always return newly allocated memory."
+
+Output values
+-------------
+
+.. csv-table:: Output values for RMM_ALLOCATE_MEMORY
+   :header: "Name", "Register", "Field", "Type", "Description"
+   :widths: 1 1 1 1 5
+
+   Result,x0,[63:0],Error Code,Command return status.
+   address,x1,[63:0],Address, "Physical address of the allocated memory area."
+
+
+Failure conditions
+------------------
+
+The table below shows all the possible error codes returned in ``Result`` upon
+a failure. The errors are ordered by condition check.
+
+.. csv-table:: Failure conditions for RMM_ALLOCATE_MEMORY
+   :header: "ID", "Condition"
+   :widths: 1 5
+
+   ``E_RMM_INVAL``,"region ID field too big"
+   ``E_RMM_UNK``,"if the SMC is not present, if interface version is <0.5"
+   ``E_RMM_NOMEM``,"size of region is larger than previously requested one,
+                   or requested size is larger than available memory"
+   ``E_RMM_OK``,No errors detected
+
 RMM-EL3 world switch register save restore convention
 _____________________________________________________
 
diff --git a/include/services/rmmd_svc.h b/include/services/rmmd_svc.h
index 2d24531..eb5513b 100644
--- a/include/services/rmmd_svc.h
+++ b/include/services/rmmd_svc.h
@@ -180,6 +180,19 @@
 #define EL3_TOKEN_SIGN_HASH_ALG_SHA384		U(1)
 
 /*
+ * Allocate memory for the RMM.
+ * The arguments to this SMC are :
+ *    arg0 - Function ID.
+ *    arg1 - Size of memory to be allocated (in bytes).
+ *    arg2 - Identifier of the type of memory to allocate, 0 for generic.
+ * The return arguments are :
+ *    ret0 - Status / error.
+ *    ret1 - Physical address of the allocated memory area.
+ */
+					/* 0x1B7 */
+#define RMM_ALLOCATE_MEMORY		SMC64_RMMD_EL3_FID(U(7))
+
+/*
  * RMM_BOOT_COMPLETE originates on RMM when the boot finishes (either cold
  * or warm boot). This is handled by the RMM-EL3 interface SMC handler.
  *
@@ -200,7 +213,7 @@
  * Increase this when a bug is fixed, or a feature is added without
  * breaking compatibility.
  */
-#define RMM_EL3_IFC_VERSION_MINOR	(U(5))
+#define RMM_EL3_IFC_VERSION_MINOR	(U(6))
 
 #define RMM_EL3_INTERFACE_VERSION				\
 	(((RMM_EL3_IFC_VERSION_MAJOR << 16) & 0x7FFFF) |	\
diff --git a/plat/arm/board/fvp/include/platform_def.h b/plat/arm/board/fvp/include/platform_def.h
index f5be8f2..59d9d74 100644
--- a/plat/arm/board/fvp/include/platform_def.h
+++ b/plat/arm/board/fvp/include/platform_def.h
@@ -55,6 +55,8 @@
 #define PLAT_ARM_RMM_BASE		(RMM_BASE)
 #define PLAT_ARM_RMM_SIZE		(RMM_LIMIT - RMM_BASE)
 
+#define PLAT_ARM_RMM_PAYLOAD_SIZE	UL(0x800000)	/* 2 * 4MB */
+
 /* Protected physical address size */
 #define PLAT_ARM_PPS			(SZ_1T)
 #endif /* ENABLE_RME */
diff --git a/plat/qemu/qemu/include/platform_def.h b/plat/qemu/qemu/include/platform_def.h
index 7dd7dcd..8f3e42a 100644
--- a/plat/qemu/qemu/include/platform_def.h
+++ b/plat/qemu/qemu/include/platform_def.h
@@ -368,6 +368,7 @@
 
 #define RMM_BASE			(REALM_DRAM_BASE)
 #define RMM_LIMIT			(RMM_BASE + PLAT_QEMU_RMM_SIZE)
+#define RMM_PAYLOAD_LIMIT		(RMM_BASE + UL(0x800000))
 #define RMM_SHARED_BASE			(RMM_LIMIT)
 #define RMM_SHARED_SIZE			PLAT_QEMU_RMM_SHARED_SIZE
 
diff --git a/plat/qemu/qemu_sbsa/include/platform_def.h b/plat/qemu/qemu_sbsa/include/platform_def.h
index 85bd233..c6e238a 100644
--- a/plat/qemu/qemu_sbsa/include/platform_def.h
+++ b/plat/qemu/qemu_sbsa/include/platform_def.h
@@ -447,6 +447,8 @@
 
 #define RMM_BASE			(REALM_DRAM_BASE)
 #define RMM_LIMIT			(RMM_BASE + PLAT_QEMU_RMM_SIZE)
+#define RMM_PAYLOAD_LIMIT		(RMM_BASE + UL(0x800000))
+
 #define RMM_SHARED_BASE			(RMM_LIMIT)
 #define RMM_SHARED_SIZE			PLAT_QEMU_RMM_SHARED_SIZE
 
diff --git a/services/std_svc/rmmd/rmmd.mk b/services/std_svc/rmmd/rmmd.mk
index eae5031..52072b5 100644
--- a/services/std_svc/rmmd/rmmd.mk
+++ b/services/std_svc/rmmd/rmmd.mk
@@ -16,7 +16,8 @@
 RMMD_SOURCES	+=	$(addprefix services/std_svc/rmmd/,	\
 			${ARCH}/rmmd_helpers.S			\
 			rmmd_main.c				\
-			rmmd_attest.c)
+			rmmd_attest.c				\
+			rmmd_mem.c)
 
 # Let the top-level Makefile know that we intend to include RMM image
 NEED_RMM	:=	yes
diff --git a/services/std_svc/rmmd/rmmd_main.c b/services/std_svc/rmmd/rmmd_main.c
index 1ad12f8..9bb6082 100644
--- a/services/std_svc/rmmd/rmmd_main.c
+++ b/services/std_svc/rmmd/rmmd_main.c
@@ -586,6 +586,11 @@
 	case RMM_EL3_TOKEN_SIGN:
 		return rmmd_el3_token_sign(handle, x1, x2, x3, x4);
 #endif
+
+	case RMM_ALLOCATE_MEMORY:
+		ret = rmmd_allocate_memory(x1, &x2);
+		SMC_RET2(handle, ret, x2);
+
 	case RMM_BOOT_COMPLETE:
 		VERBOSE("RMMD: running rmmd_rmm_sync_exit\n");
 		rmmd_rmm_sync_exit(x1);
diff --git a/services/std_svc/rmmd/rmmd_mem.c b/services/std_svc/rmmd/rmmd_mem.c
new file mode 100644
index 0000000..ac6358b
--- /dev/null
+++ b/services/std_svc/rmmd/rmmd_mem.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <common/debug.h>
+
+#include <lib/xlat_tables/xlat_tables_v2.h>
+#include <services/rmmd_svc.h>
+
+static uintptr_t top_mem = RMM_LIMIT;
+
+#define MEM_SLOT_ZEROED	0
+#define NR_MEM_SLOTS	4
+
+struct mem_slot {
+	uintptr_t addr;
+	size_t size;
+} mem_slots[NR_MEM_SLOTS] = {};
+
+static uintptr_t allocate_memory(size_t size, unsigned long alignment)
+{
+	uint64_t align_mask = alignment - 1;
+	uintptr_t addr;
+
+	addr = (top_mem - size) & ~align_mask;
+	if (addr < RMM_PAYLOAD_LIMIT) {
+		return 0;
+	}
+	top_mem = addr;
+
+	return addr;
+}
+
+int rmmd_allocate_memory(size_t size, uint64_t *arg)
+{
+	uint64_t alignment = 1UL << ((*arg >> 24) & 0xff);
+	int id = *arg & 0xffff;
+	uintptr_t addr;
+	int ret = 0;
+
+	INFO("%s(%ld, 0x%lx);\n", __func__, size, *arg);
+
+	if (id >= NR_MEM_SLOTS) {
+		return E_RMM_INVAL;
+	}
+
+	if (id == MEM_SLOT_ZEROED) {
+		addr = allocate_memory(size, alignment);
+		if (addr == 0) {
+			return E_RMM_NOMEM;
+		}
+
+		ret = 1;
+	} else {
+		if (mem_slots[id].size == 0) {
+			addr = allocate_memory(size, alignment);
+			if (addr == 0) {
+				return E_RMM_NOMEM;
+			}
+			mem_slots[id].addr = addr;
+			mem_slots[id].size = size;
+			ret = 1;
+		} else {
+			if (mem_slots[id].size < size)
+				return E_RMM_NOMEM;
+			addr = mem_slots[id].addr;
+		}
+	}
+
+	*arg = addr;
+
+	return ret;
+}
diff --git a/services/std_svc/rmmd/rmmd_private.h b/services/std_svc/rmmd/rmmd_private.h
index 0ce104d..548599b 100644
--- a/services/std_svc/rmmd/rmmd_private.h
+++ b/services/std_svc/rmmd/rmmd_private.h
@@ -54,6 +54,9 @@
 uint64_t rmmd_el3_token_sign(void *handle, uint64_t x1, uint64_t x2,
 				    uint64_t x3, uint64_t x4);
 
+/* Memory allocation for RMM */
+int rmmd_allocate_memory(size_t size, uint64_t *identifier);
+
 /* Assembly helpers */
 uint64_t rmmd_rmm_enter(uint64_t *c_rt_ctx);
 void __dead2 rmmd_rmm_exit(uint64_t c_rt_ctx, uint64_t ret);