summaryrefslogtreecommitdiff
path: root/drivers/tee/tee_heap.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tee/tee_heap.c')
-rw-r--r--drivers/tee/tee_heap.c500
1 files changed, 500 insertions, 0 deletions
diff --git a/drivers/tee/tee_heap.c b/drivers/tee/tee_heap.c
new file mode 100644
index 000000000000..d8d7735cdffb
--- /dev/null
+++ b/drivers/tee/tee_heap.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025, Linaro Limited
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/dma-heap.h>
+#include <linux/genalloc.h>
+#include <linux/module.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/tee_core.h>
+#include <linux/xarray.h>
+
+#include "tee_private.h"
+
+struct tee_dma_heap {
+ struct dma_heap *heap;
+ enum tee_dma_heap_id id;
+ struct kref kref;
+ struct tee_protmem_pool *pool;
+ struct tee_device *teedev;
+ bool shutting_down;
+ /* Protects pool, teedev, and shutting_down above */
+ struct mutex mu;
+};
+
+struct tee_heap_buffer {
+ struct tee_dma_heap *heap;
+ size_t size;
+ size_t offs;
+ struct sg_table table;
+};
+
+struct tee_heap_attachment {
+ struct sg_table table;
+ struct device *dev;
+};
+
+struct tee_protmem_static_pool {
+ struct tee_protmem_pool pool;
+ struct gen_pool *gen_pool;
+ phys_addr_t pa_base;
+};
+
+#if IS_ENABLED(CONFIG_TEE_DMABUF_HEAPS)
+static DEFINE_XARRAY_ALLOC(tee_dma_heap);
+
+static void tee_heap_release(struct kref *kref)
+{
+ struct tee_dma_heap *h = container_of(kref, struct tee_dma_heap, kref);
+
+ h->pool->ops->destroy_pool(h->pool);
+ tee_device_put(h->teedev);
+ h->pool = NULL;
+ h->teedev = NULL;
+}
+
+static void put_tee_heap(struct tee_dma_heap *h)
+{
+ kref_put(&h->kref, tee_heap_release);
+}
+
+static void get_tee_heap(struct tee_dma_heap *h)
+{
+ kref_get(&h->kref);
+}
+
+static int copy_sg_table(struct sg_table *dst, struct sg_table *src)
+{
+ struct scatterlist *dst_sg;
+ struct scatterlist *src_sg;
+ int ret;
+ int i;
+
+ ret = sg_alloc_table(dst, src->orig_nents, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ dst_sg = dst->sgl;
+ for_each_sgtable_sg(src, src_sg, i) {
+ sg_set_page(dst_sg, sg_page(src_sg), src_sg->length,
+ src_sg->offset);
+ dst_sg = sg_next(dst_sg);
+ }
+
+ return 0;
+}
+
+static int tee_heap_attach(struct dma_buf *dmabuf,
+ struct dma_buf_attachment *attachment)
+{
+ struct tee_heap_buffer *buf = dmabuf->priv;
+ struct tee_heap_attachment *a;
+ int ret;
+
+ a = kzalloc(sizeof(*a), GFP_KERNEL);
+ if (!a)
+ return -ENOMEM;
+
+ ret = copy_sg_table(&a->table, &buf->table);
+ if (ret) {
+ kfree(a);
+ return ret;
+ }
+
+ a->dev = attachment->dev;
+ attachment->priv = a;
+
+ return 0;
+}
+
+static void tee_heap_detach(struct dma_buf *dmabuf,
+ struct dma_buf_attachment *attachment)
+{
+ struct tee_heap_attachment *a = attachment->priv;
+
+ sg_free_table(&a->table);
+ kfree(a);
+}
+
+static struct sg_table *
+tee_heap_map_dma_buf(struct dma_buf_attachment *attachment,
+ enum dma_data_direction direction)
+{
+ struct tee_heap_attachment *a = attachment->priv;
+ int ret;
+
+ ret = dma_map_sgtable(attachment->dev, &a->table, direction,
+ DMA_ATTR_SKIP_CPU_SYNC);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &a->table;
+}
+
+static void tee_heap_unmap_dma_buf(struct dma_buf_attachment *attachment,
+ struct sg_table *table,
+ enum dma_data_direction direction)
+{
+ struct tee_heap_attachment *a = attachment->priv;
+
+ WARN_ON(&a->table != table);
+
+ dma_unmap_sgtable(attachment->dev, table, direction,
+ DMA_ATTR_SKIP_CPU_SYNC);
+}
+
+static void tee_heap_buf_free(struct dma_buf *dmabuf)
+{
+ struct tee_heap_buffer *buf = dmabuf->priv;
+
+ buf->heap->pool->ops->free(buf->heap->pool, &buf->table);
+ mutex_lock(&buf->heap->mu);
+ put_tee_heap(buf->heap);
+ mutex_unlock(&buf->heap->mu);
+ kfree(buf);
+}
+
+static const struct dma_buf_ops tee_heap_buf_ops = {
+ .attach = tee_heap_attach,
+ .detach = tee_heap_detach,
+ .map_dma_buf = tee_heap_map_dma_buf,
+ .unmap_dma_buf = tee_heap_unmap_dma_buf,
+ .release = tee_heap_buf_free,
+};
+
+static struct dma_buf *tee_dma_heap_alloc(struct dma_heap *heap,
+ unsigned long len, u32 fd_flags,
+ u64 heap_flags)
+{
+ struct tee_dma_heap *h = dma_heap_get_drvdata(heap);
+ DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
+ struct tee_device *teedev = NULL;
+ struct tee_heap_buffer *buf;
+ struct tee_protmem_pool *pool;
+ struct dma_buf *dmabuf;
+ int rc;
+
+ mutex_lock(&h->mu);
+ if (h->teedev) {
+ teedev = h->teedev;
+ pool = h->pool;
+ get_tee_heap(h);
+ }
+ mutex_unlock(&h->mu);
+
+ if (!teedev)
+ return ERR_PTR(-EINVAL);
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf) {
+ dmabuf = ERR_PTR(-ENOMEM);
+ goto err;
+ }
+ buf->size = len;
+ buf->heap = h;
+
+ rc = pool->ops->alloc(pool, &buf->table, len, &buf->offs);
+ if (rc) {
+ dmabuf = ERR_PTR(rc);
+ goto err_kfree;
+ }
+
+ exp_info.ops = &tee_heap_buf_ops;
+ exp_info.size = len;
+ exp_info.priv = buf;
+ exp_info.flags = fd_flags;
+ dmabuf = dma_buf_export(&exp_info);
+ if (IS_ERR(dmabuf))
+ goto err_protmem_free;
+
+ return dmabuf;
+
+err_protmem_free:
+ pool->ops->free(pool, &buf->table);
+err_kfree:
+ kfree(buf);
+err:
+ mutex_lock(&h->mu);
+ put_tee_heap(h);
+ mutex_unlock(&h->mu);
+ return dmabuf;
+}
+
+static const struct dma_heap_ops tee_dma_heap_ops = {
+ .allocate = tee_dma_heap_alloc,
+};
+
+static const char *heap_id_2_name(enum tee_dma_heap_id id)
+{
+ switch (id) {
+ case TEE_DMA_HEAP_SECURE_VIDEO_PLAY:
+ return "protected,secure-video";
+ case TEE_DMA_HEAP_TRUSTED_UI:
+ return "protected,trusted-ui";
+ case TEE_DMA_HEAP_SECURE_VIDEO_RECORD:
+ return "protected,secure-video-record";
+ default:
+ return NULL;
+ }
+}
+
+static int alloc_dma_heap(struct tee_device *teedev, enum tee_dma_heap_id id,
+ struct tee_protmem_pool *pool)
+{
+ struct dma_heap_export_info exp_info = {
+ .ops = &tee_dma_heap_ops,
+ .name = heap_id_2_name(id),
+ };
+ struct tee_dma_heap *h;
+ int rc;
+
+ if (!exp_info.name)
+ return -EINVAL;
+
+ if (xa_reserve(&tee_dma_heap, id, GFP_KERNEL)) {
+ if (!xa_load(&tee_dma_heap, id))
+ return -EEXIST;
+ return -ENOMEM;
+ }
+
+ h = kzalloc(sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return -ENOMEM;
+ h->id = id;
+ kref_init(&h->kref);
+ h->teedev = teedev;
+ h->pool = pool;
+ mutex_init(&h->mu);
+
+ exp_info.priv = h;
+ h->heap = dma_heap_add(&exp_info);
+ if (IS_ERR(h->heap)) {
+ rc = PTR_ERR(h->heap);
+ kfree(h);
+
+ return rc;
+ }
+
+ /* "can't fail" due to the call to xa_reserve() above */
+ return WARN_ON(xa_is_err(xa_store(&tee_dma_heap, id, h, GFP_KERNEL)));
+}
+
+int tee_device_register_dma_heap(struct tee_device *teedev,
+ enum tee_dma_heap_id id,
+ struct tee_protmem_pool *pool)
+{
+ struct tee_dma_heap *h;
+ int rc;
+
+ if (!tee_device_get(teedev))
+ return -EINVAL;
+
+ h = xa_load(&tee_dma_heap, id);
+ if (h) {
+ mutex_lock(&h->mu);
+ if (h->teedev) {
+ rc = -EBUSY;
+ } else {
+ kref_init(&h->kref);
+ h->shutting_down = false;
+ h->teedev = teedev;
+ h->pool = pool;
+ rc = 0;
+ }
+ mutex_unlock(&h->mu);
+ } else {
+ rc = alloc_dma_heap(teedev, id, pool);
+ }
+
+ if (rc) {
+ tee_device_put(teedev);
+ dev_err(&teedev->dev, "can't register DMA heap id %d (%s)\n",
+ id, heap_id_2_name(id));
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(tee_device_register_dma_heap);
+
+void tee_device_put_all_dma_heaps(struct tee_device *teedev)
+{
+ struct tee_dma_heap *h;
+ u_long i;
+
+ xa_for_each(&tee_dma_heap, i, h) {
+ if (h) {
+ mutex_lock(&h->mu);
+ if (h->teedev == teedev && !h->shutting_down) {
+ h->shutting_down = true;
+ put_tee_heap(h);
+ }
+ mutex_unlock(&h->mu);
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(tee_device_put_all_dma_heaps);
+
+int tee_heap_update_from_dma_buf(struct tee_device *teedev,
+ struct dma_buf *dmabuf, size_t *offset,
+ struct tee_shm *shm,
+ struct tee_shm **parent_shm)
+{
+ struct tee_heap_buffer *buf;
+ int rc;
+
+ /* The DMA-buf must be from our heap */
+ if (dmabuf->ops != &tee_heap_buf_ops)
+ return -EINVAL;
+
+ buf = dmabuf->priv;
+ /* The buffer must be from the same teedev */
+ if (buf->heap->teedev != teedev)
+ return -EINVAL;
+
+ shm->size = buf->size;
+
+ rc = buf->heap->pool->ops->update_shm(buf->heap->pool, &buf->table,
+ buf->offs, shm, parent_shm);
+ if (!rc && *parent_shm)
+ *offset = buf->offs;
+
+ return rc;
+}
+#else
+int tee_device_register_dma_heap(struct tee_device *teedev __always_unused,
+ enum tee_dma_heap_id id __always_unused,
+ struct tee_protmem_pool *pool __always_unused)
+{
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(tee_device_register_dma_heap);
+
+void
+tee_device_put_all_dma_heaps(struct tee_device *teedev __always_unused)
+{
+}
+EXPORT_SYMBOL_GPL(tee_device_put_all_dma_heaps);
+
+int tee_heap_update_from_dma_buf(struct tee_device *teedev __always_unused,
+ struct dma_buf *dmabuf __always_unused,
+ size_t *offset __always_unused,
+ struct tee_shm *shm __always_unused,
+ struct tee_shm **parent_shm __always_unused)
+{
+ return -EINVAL;
+}
+#endif
+
+static struct tee_protmem_static_pool *
+to_protmem_static_pool(struct tee_protmem_pool *pool)
+{
+ return container_of(pool, struct tee_protmem_static_pool, pool);
+}
+
+static int protmem_pool_op_static_alloc(struct tee_protmem_pool *pool,
+ struct sg_table *sgt, size_t size,
+ size_t *offs)
+{
+ struct tee_protmem_static_pool *stp = to_protmem_static_pool(pool);
+ phys_addr_t pa;
+ int ret;
+
+ pa = gen_pool_alloc(stp->gen_pool, size);
+ if (!pa)
+ return -ENOMEM;
+
+ ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+ if (ret) {
+ gen_pool_free(stp->gen_pool, pa, size);
+ return ret;
+ }
+
+ sg_set_page(sgt->sgl, phys_to_page(pa), size, 0);
+ *offs = pa - stp->pa_base;
+
+ return 0;
+}
+
+static void protmem_pool_op_static_free(struct tee_protmem_pool *pool,
+ struct sg_table *sgt)
+{
+ struct tee_protmem_static_pool *stp = to_protmem_static_pool(pool);
+ struct scatterlist *sg;
+ int i;
+
+ for_each_sgtable_sg(sgt, sg, i)
+ gen_pool_free(stp->gen_pool, sg_phys(sg), sg->length);
+ sg_free_table(sgt);
+}
+
+static int protmem_pool_op_static_update_shm(struct tee_protmem_pool *pool,
+ struct sg_table *sgt, size_t offs,
+ struct tee_shm *shm,
+ struct tee_shm **parent_shm)
+{
+ struct tee_protmem_static_pool *stp = to_protmem_static_pool(pool);
+
+ shm->paddr = stp->pa_base + offs;
+ *parent_shm = NULL;
+
+ return 0;
+}
+
+static void protmem_pool_op_static_destroy_pool(struct tee_protmem_pool *pool)
+{
+ struct tee_protmem_static_pool *stp = to_protmem_static_pool(pool);
+
+ gen_pool_destroy(stp->gen_pool);
+ kfree(stp);
+}
+
+static struct tee_protmem_pool_ops protmem_pool_ops_static = {
+ .alloc = protmem_pool_op_static_alloc,
+ .free = protmem_pool_op_static_free,
+ .update_shm = protmem_pool_op_static_update_shm,
+ .destroy_pool = protmem_pool_op_static_destroy_pool,
+};
+
+struct tee_protmem_pool *tee_protmem_static_pool_alloc(phys_addr_t paddr,
+ size_t size)
+{
+ const size_t page_mask = PAGE_SIZE - 1;
+ struct tee_protmem_static_pool *stp;
+ int rc;
+
+ /* Check it's page aligned */
+ if ((paddr | size) & page_mask)
+ return ERR_PTR(-EINVAL);
+
+ if (!pfn_valid(PHYS_PFN(paddr)))
+ return ERR_PTR(-EINVAL);
+
+ stp = kzalloc(sizeof(*stp), GFP_KERNEL);
+ if (!stp)
+ return ERR_PTR(-ENOMEM);
+
+ stp->gen_pool = gen_pool_create(PAGE_SHIFT, -1);
+ if (!stp->gen_pool) {
+ rc = -ENOMEM;
+ goto err_free;
+ }
+
+ rc = gen_pool_add(stp->gen_pool, paddr, size, -1);
+ if (rc)
+ goto err_free_pool;
+
+ stp->pool.ops = &protmem_pool_ops_static;
+ stp->pa_base = paddr;
+ return &stp->pool;
+
+err_free_pool:
+ gen_pool_destroy(stp->gen_pool);
+err_free:
+ kfree(stp);
+
+ return ERR_PTR(rc);
+}
+EXPORT_SYMBOL_GPL(tee_protmem_static_pool_alloc);