| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 |
- // 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_obj(*a);
- 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_obj(*buf);
- 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_obj(*h);
- 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_obj(*stp);
- 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);
|