@@ -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
@@ -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
new file mode 100644
@@ -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);
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(+)