feat(drtm): add a few DRTM DMA protection APIs

Added DRTM DMA protections APIs, and called them during
the DLME launch and DRTM SMC handling.

Change-Id: I29e7238c04e2ca9f26600276c5c05bff5387789e
Signed-off-by: Manish V Badarkhe <Manish.Badarkhe@arm.com>
diff --git a/services/std_svc/drtm/drtm_dma_prot.c b/services/std_svc/drtm/drtm_dma_prot.c
index 9d014a0..48317fd 100644
--- a/services/std_svc/drtm/drtm_dma_prot.c
+++ b/services/std_svc/drtm/drtm_dma_prot.c
@@ -14,9 +14,30 @@
 #include <string.h>
 
 #include <common/debug.h>
-
+#include <drivers/arm/smmu_v3.h>
 #include "drtm_dma_prot.h"
+#include "drtm_main.h"
+#include "drtm_remediation.h"
 #include <plat/common/platform.h>
+#include <smccc_helpers.h>
+
+/*
+ *  ________________________  LAUNCH success        ________________________
+ * |        Initial         | -------------------> |      Prot engaged      |
+ * |````````````````````````|                      |````````````````````````|
+ * |  request.type == NONE  |                      |  request.type != NONE  |
+ * |                        | <------------------- |                        |
+ * `________________________'        UNPROTECT_MEM `________________________'
+ *
+ * Transitions that are not shown correspond to ABI calls that do not change
+ * state and result in an error being returned to the caller.
+ */
+static struct dma_prot active_prot = {
+	.type = PROTECT_NONE,
+};
+
+/* Version-independent type. */
+typedef struct drtm_dl_dma_prot_args_v1 struct_drtm_dl_dma_prot_args;
 
 /*
  * This function checks that platform supports complete DMA protection.
@@ -59,3 +80,184 @@
 
 	return must_init_fail;
 }
+
+/*
+ * Checks that the DMA protection arguments are valid and that the given
+ * protected regions are covered by DMA protection.
+ */
+enum drtm_retc drtm_dma_prot_check_args(const struct_drtm_dl_dma_prot_args *a,
+					int a_dma_prot_type,
+					drtm_mem_region_t p)
+{
+	switch ((enum dma_prot_type)a_dma_prot_type) {
+	case PROTECT_MEM_ALL:
+		if (a->dma_prot_table_paddr || a->dma_prot_table_size) {
+			ERROR("DRTM: invalid launch due to inconsistent"
+			      " DMA protection arguments\n");
+			return MEM_PROTECT_INVALID;
+		}
+		/*
+		 * Full DMA protection ought to ensure that the DLME and NWd
+		 * DCE regions are protected, no further checks required.
+		 */
+		return SUCCESS;
+
+	default:
+		ERROR("DRTM: invalid launch due to unsupported DMA protection type\n");
+		return MEM_PROTECT_INVALID;
+	}
+}
+
+enum drtm_retc drtm_dma_prot_engage(const struct_drtm_dl_dma_prot_args *a,
+				    int a_dma_prot_type)
+{
+	const uintptr_t *smmus;
+	size_t num_smmus = 0;
+
+	if (active_prot.type != PROTECT_NONE) {
+		ERROR("DRTM: launch denied as previous DMA protection"
+		      " is still engaged\n");
+		return DENIED;
+	}
+
+	if (a_dma_prot_type == PROTECT_NONE) {
+		return SUCCESS;
+		/* Only PROTECT_MEM_ALL is supported currently. */
+	} else if (a_dma_prot_type != PROTECT_MEM_ALL) {
+		ERROR("%s(): unimplemented DMA protection type\n", __func__);
+		panic();
+	}
+
+	/*
+	 * Engage SMMUs in accordance with the request we have previously received.
+	 * Only PROTECT_MEM_ALL is implemented currently.
+	 */
+	plat_enumerate_smmus(&smmus, &num_smmus);
+	for (const uintptr_t *smmu = smmus; smmu < smmus+num_smmus; smmu++) {
+		/*
+		 * TODO: Invalidate SMMU's Stage-1 and Stage-2 TLB entries.  This ensures
+		 * that any outstanding device transactions are completed, see Section
+		 * 3.21.1, specification IHI_0070_C_a for an approximate reference.
+		 */
+		int rc = smmuv3_ns_set_abort_all(*smmu);
+		if (rc != 0) {
+			ERROR("DRTM: SMMU at PA 0x%lx failed to engage DMA protection"
+			      " rc=%d\n", *smmu, rc);
+			return INTERNAL_ERROR;
+		}
+	}
+
+	/*
+	 * TODO: Restrict DMA from the GIC.
+	 *
+	 * Full DMA protection may be achieved as follows:
+	 *
+	 * With a GICv3:
+	 * - Set GICR_CTLR.EnableLPIs to 0, for each GICR;
+	 *   GICR_CTLR.RWP == 0 must be the case before finishing, for each GICR.
+	 * - Set GITS_CTLR.Enabled to 0;
+	 *   GITS_CTLR.Quiescent == 1 must be the case before finishing.
+	 *
+	 * In addition, with a GICv4:
+	 * - Set GICR_VPENDBASER.Valid to 0, for each GICR;
+	 *   GICR_CTLR.RWP == 0 must be the case before finishing, for each GICR.
+	 *
+	 * Alternatively, e.g. if some bit values cannot be changed at runtime,
+	 * this procedure should return an error if the LPI Pending and
+	 * Configuration tables overlap the regions being protected.
+	 */
+
+	active_prot.type = a_dma_prot_type;
+
+	return SUCCESS;
+}
+
+/*
+ * Undo what has previously been done in drtm_dma_prot_engage(), or enter
+ * remediation if it is not possible.
+ */
+enum drtm_retc drtm_dma_prot_disengage(void)
+{
+	const uintptr_t *smmus;
+	size_t num_smmus = 0;
+	const char *err_str = "cannot undo PROTECT_MEM_ALL SMMU config";
+
+	if (active_prot.type == PROTECT_NONE) {
+		return SUCCESS;
+		/* Only PROTECT_MEM_ALL is supported currently. */
+	} else if (active_prot.type != PROTECT_MEM_ALL) {
+		ERROR("%s(): unimplemented DMA protection type\n", __func__);
+		panic();
+	}
+
+	/*
+	 * For PROTECT_MEM_ALL, undo the SMMU configuration for "abort all" mode
+	 * done during engage().
+	 */
+	/* Simply enter remediation for now. */
+	(void)smmus;
+	(void)num_smmus;
+	drtm_enter_remediation(1ULL, err_str);
+
+	/* TODO: Undo GIC DMA restrictions. */
+
+	active_prot.type = PROTECT_NONE;
+
+	return SUCCESS;
+}
+
+uint64_t drtm_unprotect_mem(void *ctx)
+{
+	enum drtm_retc ret;
+
+	switch (active_prot.type) {
+	case PROTECT_NONE:
+		ERROR("DRTM: invalid UNPROTECT_MEM, no DMA protection has"
+		      " previously been engaged\n");
+		ret = DENIED;
+		break;
+
+	case PROTECT_MEM_ALL:
+		/*
+		 * UNPROTECT_MEM is a no-op for PROTECT_MEM_ALL:  DRTM must not touch
+		 * the NS SMMU as it is expected that the DLME has configured it.
+		 */
+		active_prot.type = PROTECT_NONE;
+
+		ret = SUCCESS;
+		break;
+
+	default:
+		ret = drtm_dma_prot_disengage();
+		break;
+	}
+
+	SMC_RET1(ctx, ret);
+}
+
+void drtm_dma_prot_serialise_table(uint8_t *dst, size_t *size_out)
+{
+	if (active_prot.type == PROTECT_NONE) {
+		return;
+	} else if (active_prot.type != PROTECT_MEM_ALL) {
+		ERROR("%s(): unimplemented DMA protection type\n", __func__);
+		panic();
+	}
+
+	struct __packed descr_table_1 {
+		drtm_memory_region_descriptor_table_t header;
+		drtm_mem_region_t regions[1];
+	} prot_table = {
+		.header = {
+			.revision = 1,
+			.num_regions = sizeof(((struct descr_table_1 *)NULL)->regions) /
+				sizeof(((struct descr_table_1 *)NULL)->regions[0])
+		},
+		.regions = {
+			{.region_address = 0, PAGES_AND_TYPE(UINT64_MAX, 0x3)},
+		}
+	};
+
+	memcpy(dst, &prot_table, sizeof(prot_table));
+	*size_out = sizeof(prot_table);
+}
diff --git a/services/std_svc/drtm/drtm_dma_prot.h b/services/std_svc/drtm/drtm_dma_prot.h
index e9b1fe9..79dc9cb 100644
--- a/services/std_svc/drtm/drtm_dma_prot.h
+++ b/services/std_svc/drtm/drtm_dma_prot.h
@@ -8,15 +8,43 @@
 #define DRTM_DMA_PROT_H
 
 #include <stdint.h>
+#include <plat/common/platform.h>
+#include <services/drtm_svc.h>
 
 struct __packed drtm_dl_dma_prot_args_v1 {
 	uint64_t dma_prot_table_paddr;
 	uint64_t dma_prot_table_size;
 };
 
+/* Values for DRTM_PROTECT_MEMORY */
+enum dma_prot_type {
+	PROTECT_NONE    = -1,
+	PROTECT_MEM_ALL = 0,
+	PROTECT_MEM_REGION = 2,
+};
+
+struct dma_prot {
+	enum dma_prot_type type;
+};
+
+#define DRTM_MEM_REGION_PAGES_AND_TYPE(pages, type) \
+	(((uint64_t)(pages) & (((uint64_t)1 << 52) - 1)) \
+	 | (((uint64_t)(type) & 0x7) << 52))
+
+#define PAGES_AND_TYPE(pages, type) \
+		.region_size_type = DRTM_MEM_REGION_PAGES_AND_TYPE(pages, type)
+
 /* Opaque / encapsulated type. */
 typedef struct drtm_dl_dma_prot_args_v1 drtm_dl_dma_prot_args_v1_t;
 
 bool drtm_dma_prot_init(void);
+enum drtm_retc drtm_dma_prot_check_args(const drtm_dl_dma_prot_args_v1_t *a,
+					int a_dma_prot_type,
+					drtm_mem_region_t p);
+enum drtm_retc drtm_dma_prot_engage(const drtm_dl_dma_prot_args_v1_t *a,
+				    int a_dma_prot_type);
+enum drtm_retc drtm_dma_prot_disengage(void);
+uint64_t drtm_unprotect_mem(void *ctx);
+void drtm_dma_prot_serialise_table(uint8_t *dst, size_t *size_out);
 
 #endif /* DRTM_DMA_PROT_H */
diff --git a/services/std_svc/drtm/drtm_main.c b/services/std_svc/drtm/drtm_main.c
index 9878887..53aafeb 100644
--- a/services/std_svc/drtm/drtm_main.c
+++ b/services/std_svc/drtm/drtm_main.c
@@ -358,6 +358,7 @@
 static uint64_t drtm_dynamic_launch(uint64_t x1, void *handle)
 {
 	enum drtm_retc ret = SUCCESS;
+	enum drtm_retc dma_prot_ret;
 	struct_drtm_dl_args args;
 
 	/* Ensure that only boot PE is powered on */
@@ -380,6 +381,17 @@
 		SMC_RET1(handle, ret);
 	}
 
+	/*
+	 * Engage the DMA protections.  The launch cannot proceed without the DMA
+	 * protections due to potential TOC/TOU vulnerabilities w.r.t. the DLME
+	 * region (and to the NWd DCE region).
+	 */
+	ret = drtm_dma_prot_engage(&args.dma_prot_args,
+				   DL_ARGS_GET_DMA_PROT_TYPE(&args));
+	if (ret != SUCCESS) {
+		SMC_RET1(handle, ret);
+	}
+
 	SMC_RET1(handle, ret);
 }
 
@@ -497,7 +509,7 @@
 
 	case ARM_DRTM_SVC_UNPROTECT_MEM:
 		INFO("DRTM service handler: unprotect mem\n");
-		SMC_RET1(handle, SMC_OK);
+		return drtm_unprotect_mem(handle);
 		break;	/* not reached */
 
 	case ARM_DRTM_SVC_DYNAMIC_LAUNCH:
diff --git a/services/std_svc/drtm/drtm_main.h b/services/std_svc/drtm/drtm_main.h
index cccc14f..9bf5400 100644
--- a/services/std_svc/drtm/drtm_main.h
+++ b/services/std_svc/drtm/drtm_main.h
@@ -31,6 +31,7 @@
 #define DRTM_PAGE_SIZE		(4 * (1 << 10))
 #define DRTM_PAGE_SIZE_STR	"4-KiB"
 
+#define DL_ARGS_GET_DMA_PROT_TYPE(a)    (((a)->features >> 3) & 0x7U)
 #define DL_ARGS_GET_PCR_SCHEMA(a)	(((a)->features >> 1) & 0x3U)
 #define DL_ARGS_GET_DLME_ENTRY_POINT(a)	\
 		(((a)->dlme_paddr + (a)->dlme_img_off + (a)->dlme_img_ep_off))