Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Jens Wiklander 2213 99.55% 9 64.29%
Sumit Garg 5 0.22% 2 14.29%
Volodymyr Babchuk 2 0.09% 1 7.14%
Jann Horn 2 0.09% 1 7.14%
Thomas Gleixner 1 0.04% 1 7.14%
Total 2223 14


// 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);