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_azarr...@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 --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);

-- 
2.34.1

Reply via email to