debugfs: add SMC channel

Provide an SMC interface to the 9p filesystem. This permits
accessing firmware drivers through a common interface, using
standardized read/write/control operations.

Signed-off-by: Ambroise Vincent <ambroise.vincent@arm.com>
Signed-off-by: Olivier Deprez <olivier.deprez@arm.com>
Change-Id: I9314662314bb060f6bc02714476574da158b2a7d
diff --git a/lib/debugfs/debugfs_smc.c b/lib/debugfs/debugfs_smc.c
new file mode 100644
index 0000000..400c166
--- /dev/null
+++ b/lib/debugfs/debugfs_smc.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2019, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <lib/debugfs.h>
+#include <lib/smccc.h>
+#include <lib/spinlock.h>
+#include <lib/xlat_tables/xlat_tables_v2.h>
+#include <smccc_helpers.h>
+
+#define MAX_PATH_LEN	256
+
+#define MOUNT		0
+#define CREATE		1
+#define OPEN		2
+#define CLOSE		3
+#define READ		4
+#define WRITE		5
+#define SEEK		6
+#define BIND		7
+#define STAT		8
+#define INIT		10
+#define VERSION		11
+
+/* This is the virtual address to which we map the NS shared buffer */
+#define DEBUGFS_SHARED_BUF_VIRT		((void *)0x81000000U)
+
+static union debugfs_parms {
+	struct {
+		char fname[MAX_PATH_LEN];
+	} open;
+
+	struct {
+		char srv[MAX_PATH_LEN];
+		char where[MAX_PATH_LEN];
+		char spec[MAX_PATH_LEN];
+	} mount;
+
+	struct {
+		char path[MAX_PATH_LEN];
+		dir_t dir;
+	} stat;
+
+	struct {
+		char oldpath[MAX_PATH_LEN];
+		char newpath[MAX_PATH_LEN];
+	} bind;
+} parms;
+
+/* debugfs_access_lock protects shared buffer and internal */
+/* FS functions from concurrent acccesses.                 */
+static spinlock_t debugfs_access_lock;
+
+static bool debugfs_initialized;
+
+uintptr_t debugfs_smc_handler(unsigned int smc_fid,
+			      u_register_t cmd,
+			      u_register_t arg2,
+			      u_register_t arg3,
+			      u_register_t arg4,
+			      void *cookie,
+			      void *handle,
+			      u_register_t flags)
+{
+	int64_t smc_ret = DEBUGFS_E_INVALID_PARAMS, smc_resp = 0;
+	int ret;
+
+	/* Allow calls from non-secure only */
+	if (is_caller_secure(flags)) {
+		SMC_RET1(handle, DEBUGFS_E_DENIED);
+	}
+
+	/* Expect a SiP service fast call */
+	if ((GET_SMC_TYPE(smc_fid) != SMC_TYPE_FAST) ||
+		(GET_SMC_OEN(smc_fid) != OEN_SIP_START)) {
+		SMC_RET1(handle, SMC_UNK);
+	}
+
+	/* Truncate parameters if 32b SMC convention call */
+	if (GET_SMC_CC(smc_fid) == SMC_32) {
+		arg2 &= 0xffffffff;
+		arg3 &= 0xffffffff;
+		arg4 &= 0xffffffff;
+	}
+
+	spin_lock(&debugfs_access_lock);
+
+	if (debugfs_initialized == true) {
+		/* Copy NS shared buffer to internal secure location */
+		memcpy(&parms, (void *)DEBUGFS_SHARED_BUF_VIRT,
+		       sizeof(union debugfs_parms));
+	}
+
+	switch (cmd) {
+	case INIT:
+		if (debugfs_initialized == false) {
+			/* TODO: check PA validity e.g. whether */
+			/* it is an NS region.                  */
+			ret = mmap_add_dynamic_region(arg2,
+				(uintptr_t)DEBUGFS_SHARED_BUF_VIRT,
+				PAGE_SIZE_4KB,
+				MT_MEMORY | MT_RW | MT_NS);
+			if (ret == 0) {
+				debugfs_initialized = true;
+				smc_ret = SMC_OK;
+				smc_resp = 0;
+			}
+		}
+		break;
+
+	case VERSION:
+		smc_ret = SMC_OK;
+		smc_resp = DEBUGFS_VERSION;
+		break;
+
+	case MOUNT:
+		ret = mount(parms.mount.srv,
+			    parms.mount.where,
+			    parms.mount.spec);
+		if (ret == 0) {
+			smc_ret = SMC_OK;
+			smc_resp = 0;
+		}
+		break;
+
+	case OPEN:
+		ret = open(parms.open.fname, arg2);
+		if (ret >= 0) {
+			smc_ret = SMC_OK;
+			smc_resp = ret;
+		}
+		break;
+
+	case CLOSE:
+		ret = close(arg2);
+		if (ret == 0) {
+			smc_ret = SMC_OK;
+			smc_resp = 0;
+		}
+		break;
+
+	case READ:
+		ret = read(arg2, DEBUGFS_SHARED_BUF_VIRT, arg3);
+		if (ret >= 0) {
+			smc_ret = SMC_OK;
+			smc_resp = ret;
+		}
+		break;
+
+	case SEEK:
+		ret = seek(arg2, arg3, arg4);
+		if (ret == 0) {
+			smc_ret = SMC_OK;
+			smc_resp = 0;
+		}
+		break;
+
+	case BIND:
+		ret = bind(parms.bind.oldpath, parms.bind.newpath);
+		if (ret == 0) {
+			smc_ret = SMC_OK;
+			smc_resp = 0;
+		}
+		break;
+
+	case STAT:
+		ret = stat(parms.stat.path, &parms.stat.dir);
+		if (ret == 0) {
+			memcpy((void *)DEBUGFS_SHARED_BUF_VIRT, &parms,
+			       sizeof(union debugfs_parms));
+			smc_ret = SMC_OK;
+			smc_resp = 0;
+		}
+		break;
+
+	/* Not implemented */
+	case CREATE:
+		/* Intentional fall-through */
+
+	/* Not implemented */
+	case WRITE:
+		/* Intentional fall-through */
+
+	default:
+		smc_ret = SMC_UNK;
+		smc_resp = 0;
+	}
+
+	spin_unlock(&debugfs_access_lock);
+
+	SMC_RET2(handle, smc_ret, smc_resp);
+
+	/* Not reached */
+	return smc_ret;
+}
+
+int debugfs_smc_setup(void)
+{
+	debugfs_initialized = false;
+	debugfs_access_lock.lock = 0;
+
+	return 0;
+}