diff mbox series

[RFC,2/3] firmware: qcom: implement memory object support for TEE

Message ID 20240702-qcom-tee-object-and-ioctls-v1-2-633c3ddf57ee@quicinc.com
State New
Headers show
Series Implement Qualcomm TEE IPC and ioctl calls | expand

Commit Message

Amirreza Zarrabi July 3, 2024, 5:57 a.m. UTC
Allocating and sharing memory with TEE can happen using different
methods. To allocate a memory, a client may try to use part of its
address space, use a dma-heap to allocate a buffer, use a pre-defined
pool of memory that has already been shared with TEE, or if it is a
kernel client, it can allocate memory in kernel. To share the memory,
it can use FFA or SHM bridge (in case of Qualcomm TEE).

Using qcom_tee_object we implemented a nonsecure service as an extension
that is used to share dma-buf with TEE based on Qualcomm SHM bridge.
Any other form of memory allocation and sharing can be later on added
using separate extensions.

Signed-off-by: Amirreza Zarrabi <quic_azarrabi@quicinc.com>
---
 drivers/firmware/qcom/Kconfig                      |  10 +
 drivers/firmware/qcom/qcom_object_invoke/Makefile  |   5 +
 .../qcom/qcom_object_invoke/xts/mem_object.c       | 406 +++++++++++++++++++++
 3 files changed, 421 insertions(+)
diff mbox series

Patch

diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig
index 103ab82bae9f..f16fb7997595 100644
--- a/drivers/firmware/qcom/Kconfig
+++ b/drivers/firmware/qcom/Kconfig
@@ -98,4 +98,14 @@  config QCOM_OBJECT_INVOKE_CORE
 
 	  Select Y here to provide access to TEE.
 
+config QCOM_OBJECT_INVOKE_MEM_OBJECT
+	bool "Add support for memory object"
+	depends on QCOM_OBJECT_INVOKE_CORE
+	help
+	  This provide an interface to export or sharing memory with TEE.
+	  It allows kernel clients to create memory object and do the necessary
+	  mapping and unmapping using TZMEM allocator.
+
+	  Select Y here Enable support for memory object.
+
 endmenu
diff --git a/drivers/firmware/qcom/qcom_object_invoke/Makefile b/drivers/firmware/qcom/qcom_object_invoke/Makefile
index 6ef4d54891a5..1f7d43fa38db 100644
--- a/drivers/firmware/qcom/qcom_object_invoke/Makefile
+++ b/drivers/firmware/qcom/qcom_object_invoke/Makefile
@@ -2,3 +2,8 @@ 
 
 obj-$(CONFIG_QCOM_OBJECT_INVOKE_CORE) += object-invoke-core.o
 object-invoke-core-objs := qcom_scm_invoke.o release_wq.o async.o core.o
+
+# Add extenstions here.
+
+obj-$(CONFIG_QCOM_OBJECT_INVOKE_MEM_OBJECT) += mem-object.o
+mem-object-objs := xts/mem_object.o
diff --git a/drivers/firmware/qcom/qcom_object_invoke/xts/mem_object.c b/drivers/firmware/qcom/qcom_object_invoke/xts/mem_object.c
new file mode 100644
index 000000000000..5193f95536eb
--- /dev/null
+++ b/drivers/firmware/qcom/qcom_object_invoke/xts/mem_object.c
@@ -0,0 +1,406 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#define pr_fmt(fmt) "qcom-object-invoke-mo: %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/dma-buf.h>
+#include <linux/of_platform.h>
+
+#include <linux/firmware/qcom/qcom_object_invoke.h>
+
+/* Memory object operations. */
+/* ... */
+
+/* 'Primordial Object' operations related to memory object. */
+#define QCOM_TEE_OBJECT_OP_MAP_REGION	0
+
+static struct platform_device *mem_object_pdev;
+
+static struct qcom_tee_object primordial_object;
+
+struct mem_object {
+	struct qcom_tee_object object;
+
+	struct dma_buf *dma_buf;
+
+	union {
+		/* SHMBridge information. */
+		struct {
+			struct map {
+				struct dma_buf_attachment *buf_attach;
+				struct sg_table *sgt;
+
+				/* 'lock' to protect concurrent request from TEE and prepare. */
+				struct mutex lock;
+			} map;
+
+			/* Use SHMBridge, hence the handle. */
+			u64 shm_bridge_handle;
+
+			struct mapping_info {
+				phys_addr_t p_addr;
+				size_t p_addr_len;
+			} mapping_info;
+		};
+
+		/* XXX information. */
+		/* struct { ... } */
+	};
+
+	struct list_head node;
+
+	/* Private pointer passed for callbacks. */
+	void *private;
+
+	void (*release)(void *private);
+};
+
+#define to_mem_object(o) container_of((o), struct mem_object, object)
+
+/* List of memory objects. */
+static LIST_HEAD(mo_list);
+static DEFINE_MUTEX(mo_list_mutex);
+
+/* mo_notify and mo_dispatch are shared by all types of memory objects. */
+/* Depending on how we share memory with TEE (e.g. using QCOM SHMBridge or FFA),
+ * the mem_ops.release will be selected in the mem_object_probe.
+ */
+
+static void mo_notify(unsigned int context_id, struct qcom_tee_object *object, int status) {}
+static int mo_dispatch(unsigned int context_id, struct qcom_tee_object *object,
+	unsigned long op, struct qcom_tee_arg args[])
+{
+	return 0;
+}
+
+static struct qcom_tee_object_operations mem_ops = {
+	.notify = mo_notify,
+	.dispatch = mo_dispatch
+};
+
+static int is_mem_object(struct qcom_tee_object *object)
+{
+	return (typeof_qcom_tee_object(object) == QCOM_TEE_OBJECT_TYPE_CB_OBJECT) &&
+		(object->ops == &mem_ops);
+}
+
+/** Support for 'SHMBridge'. **/
+
+/* make_shm_bridge_single only support single continuous memory. */
+static int make_shm_bridge_single(struct mem_object *mo)
+{
+	/* 'sgt' should have one mapped entry. **/
+	if (mo->map.sgt->nents != 1)
+		return -EINVAL;
+
+	mo->mapping_info.p_addr = sg_dma_address(mo->map.sgt->sgl);
+	mo->mapping_info.p_addr_len = sg_dma_len(mo->map.sgt->sgl);
+
+	/* TODO. Use SHMBridge to establish the shered memory. */
+
+	return 0;
+}
+
+static void rm_shm_bridge(struct mem_object *mo)
+{
+	/* TODO. Use SHMBridge to release the shered memory. */
+}
+
+static void detach_dma_buf(struct mem_object *mo)
+{
+	if (mo->map.sgt) {
+		dma_buf_unmap_attachment_unlocked(mo->map.buf_attach,
+			mo->map.sgt, DMA_BIDIRECTIONAL);
+	}
+
+	if (mo->map.buf_attach)
+		dma_buf_detach(mo->dma_buf, mo->map.buf_attach);
+}
+
+/* init_tz_shared_memory is called while holding the map.lock mutex. */
+static int init_tz_shared_memory(struct mem_object *mo)
+{
+	int ret;
+	struct dma_buf_attachment *buf_attach;
+	struct sg_table *sgt;
+
+	mo->map.buf_attach = NULL;
+	mo->map.sgt = NULL;
+
+	buf_attach = dma_buf_attach(mo->dma_buf, &mem_object_pdev->dev);
+	if (IS_ERR(buf_attach))
+		return PTR_ERR(buf_attach);
+
+	mo->map.buf_attach = buf_attach;
+
+	sgt = dma_buf_map_attachment_unlocked(buf_attach, DMA_BIDIRECTIONAL);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+
+		goto out_failed;
+	}
+
+	mo->map.sgt = sgt;
+
+	ret = make_shm_bridge_single(mo);
+	if (ret)
+		goto out_failed;
+
+	return 0;
+
+out_failed:
+	detach_dma_buf(mo);
+
+	return ret;
+}
+
+static int map_memory_obj(struct mem_object *mo)
+{
+	int ret;
+
+	if (mo->mapping_info.p_addr == 0) {
+		/* 'mo' has not been mapped before. Do it now. */
+		ret = init_tz_shared_memory(mo);
+	} else {
+		/* 'mo' is already mapped. Just return. */
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void release_memory_obj(struct mem_object *mo)
+{
+	rm_shm_bridge(mo);
+
+	detach_dma_buf(mo);
+}
+
+static void mo_shm_bridge_release(struct qcom_tee_object *object)
+{
+	struct mem_object *mo = to_mem_object(object);
+
+	release_memory_obj(mo);
+
+	if (mo->release)
+		mo->release(mo->private);
+
+	/* Put a dam-buf copy obtained in init_si_mem_object_user.*/
+	dma_buf_put(mo->dma_buf);
+
+	mutex_lock(&mo_list_mutex);
+	list_del(&mo->node);
+	mutex_unlock(&mo_list_mutex);
+
+	kfree(mo);
+}
+
+/* Primordial object for SHMBridge. */
+
+static int shm_bridge__po_dispatch(unsigned int context_id,
+	struct qcom_tee_object *unused, unsigned long op, struct qcom_tee_arg args[])
+{
+	int ret;
+
+	struct qcom_tee_object *object;
+	struct mem_object *mo;
+
+	switch (op) {
+	case QCOM_TEE_OBJECT_OP_MAP_REGION: {
+		/* Format of response as expected by TZ. */
+		struct {
+			u64 p_addr;
+			u64 len;
+			u32 perms;
+		} *mi;
+
+		if (size_of_arg(args) != 3 ||
+			args[0].type != QCOM_TEE_ARG_TYPE_OB  ||
+			args[1].type != QCOM_TEE_ARG_TYPE_IO  ||
+			args[2].type != QCOM_TEE_ARG_TYPE_OO) {
+			pr_err("mapping of a memory object with invalid message format.\n");
+
+			return -EINVAL;
+		}
+
+		object = args[1].o;
+
+		if (!is_mem_object(object)) {
+			pr_err("mapping of a non-memory object.\n");
+			put_qcom_tee_object(object);
+
+			return -EINVAL;
+		}
+
+		mo = to_mem_object(object);
+
+		mutex_lock(&mo->map.lock);
+		ret = map_memory_obj(mo);
+		mutex_unlock(&mo->map.lock);
+
+		if (!ret) {
+			/* 'object' has been mapped. Share it. */
+			args[2].o = object;
+
+			mi = (typeof(mi))args[0].b.addr;
+			mi->p_addr = mo->mapping_info.p_addr;
+			mi->len = mo->mapping_info.p_addr_len;
+			mi->perms = 6; /* RW Permission. */
+		} else {
+			pr_err("mapping memory object %s failed.\n", qcom_tee_object_name(object));
+
+			put_qcom_tee_object(object);
+		}
+	}
+
+		break;
+	default: /* The operation is not supported! */
+		ret = -EINVAL;
+
+		break;
+	}
+
+	return ret;
+}
+
+static int op_supported(unsigned long op)
+{
+	switch (op) {
+	case QCOM_TEE_OBJECT_OP_MAP_REGION:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static struct qcom_tee_object_operations shm_bridge__po_ops = {
+	.op_supported = op_supported,
+	.dispatch = shm_bridge__po_dispatch
+};
+
+/* Memory Object Extension API. */
+
+struct qcom_tee_object *qcom_tee_mem_object_init(struct dma_buf *dma_buf,
+	void (*release)(void *), void *private)
+{
+	struct mem_object *mo;
+
+	if (!mem_ops.release) {
+		pr_err("memory object type is unknown.\n");
+
+		return NULL_QCOM_TEE_OBJECT;
+	}
+
+	mo = kzalloc(sizeof(*mo), GFP_KERNEL);
+	if (!mo)
+		return NULL_QCOM_TEE_OBJECT;
+
+	mutex_init(&mo->map.lock);
+
+	/* Get a copy of dma-buf. */
+	get_dma_buf(dma_buf);
+
+	mo->dma_buf = dma_buf;
+	mo->private = private;
+	mo->release = release;
+
+	init_qcom_tee_object_user(&mo->object, QCOM_TEE_OBJECT_TYPE_CB_OBJECT,
+		&mem_ops, "mem-object");
+
+	mutex_lock(&mo_list_mutex);
+	list_add_tail(&mo->node, &mo_list);
+	mutex_unlock(&mo_list_mutex);
+
+	return &mo->object;
+}
+EXPORT_SYMBOL_GPL(qcom_tee_mem_object_init);
+
+struct dma_buf *qcom_tee_mem_object_to_dma_buf(struct qcom_tee_object *object)
+{
+	if (is_mem_object(object))
+		return to_mem_object(object)->dma_buf;
+
+	return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(qcom_tee_mem_object_to_dma_buf);
+
+static ssize_t mem_objects_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	size_t len = 0;
+	struct mem_object *mo;
+
+	mutex_lock(&mo_list_mutex);
+	list_for_each_entry(mo, &mo_list, node) {
+		len += scnprintf(buf + len, PAGE_SIZE - len, "%s refs: %u (%llx %zx)\n",
+			qcom_tee_object_name(&mo->object), kref_read(&mo->object.refcount),
+			mo->mapping_info.p_addr, mo->mapping_info.p_addr_len);
+	}
+
+	mutex_unlock(&mo_list_mutex);
+
+	return len;
+}
+
+/* 'struct device_attribute dev_attr_mem_objects'. */
+/* Use device attribute rather than driver attribute in case we want to support
+ * multiple types of memory objects as different devices.
+ */
+
+static DEVICE_ATTR_RO(mem_objects);
+
+static struct attribute *attrs[] = {
+	&dev_attr_mem_objects.attr,
+	NULL
+};
+
+static struct attribute_group attr_group = {
+	.attrs = attrs,
+};
+
+static const struct attribute_group *attr_groups[] = {
+	&attr_group,
+	NULL
+};
+
+static int mem_object_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	if (ret)
+		return ret;
+
+	/* Select memory object type: default to SHMBridge. */
+	mem_ops.release = mo_shm_bridge_release;
+
+	init_qcom_tee_object_user(&primordial_object,
+		QCOM_TEE_OBJECT_TYPE_ROOT, &shm_bridge__po_ops, "po_in_mem_object");
+
+	mem_object_pdev = pdev;
+
+	return 0;
+}
+
+static const struct of_device_id mem_object_match[] = {
+	{ .compatible = "qcom,mem-object", }, {}
+};
+
+static struct platform_driver mem_object_plat_driver = {
+	.probe = mem_object_probe,
+	.driver = {
+		.name = "mem-object",
+		.dev_groups = attr_groups,
+		.of_match_table = mem_object_match,
+	},
+};
+
+module_platform_driver(mem_object_plat_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Memory object driver");
+MODULE_IMPORT_NS(DMA_BUF);