| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2011,2020 Google LLC |
| */ |
| #include <linux/spinlock.h> |
| #include <linux/dma-map-ops.h> |
| #include <linux/err.h> |
| #include <linux/genalloc.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/samsung-dma-heap.h> |
| #include <linux/scatterlist.h> |
| #include <linux/slab.h> |
| #include <linux/vmalloc.h> |
| |
| #include <asm/cacheflush.h> |
| |
| #include "ion_physical_heap.h" |
| |
| #define ION_PHYSICAL_ALLOCATE_FAIL -1 |
| |
| struct ion_physical_heap { |
| struct gen_pool *pool; |
| phys_addr_t base; |
| size_t size; |
| |
| ion_physical_heap_allocate_callback *allocate_cb; |
| void *allocate_ctx; |
| |
| ion_physical_heap_free_callback *free_cb; |
| void *free_ctx; |
| }; |
| |
| static int _clear_pages(struct page **pages, int num, pgprot_t pgprot) |
| { |
| void *addr = vmap(pages, num, VM_MAP, pgprot); |
| |
| if (!addr) |
| return -ENOMEM; |
| memset(addr, 0, PAGE_SIZE * num); |
| vunmap(addr); |
| |
| return 0; |
| } |
| |
| static int _sglist_zero(struct scatterlist *sgl, unsigned int nents, |
| pgprot_t pgprot) |
| { |
| int p = 0; |
| int ret = 0; |
| struct sg_page_iter piter; |
| struct page *pages[32]; |
| |
| for_each_sg_page(sgl, &piter, nents, 0) { |
| pages[p++] = sg_page_iter_page(&piter); |
| if (p == ARRAY_SIZE(pages)) { |
| ret = _clear_pages(pages, p, pgprot); |
| if (ret) |
| return ret; |
| p = 0; |
| } |
| } |
| if (p) |
| ret = _clear_pages(pages, p, pgprot); |
| |
| return ret; |
| } |
| |
| static int _buffer_zero(struct samsung_dma_buffer *buffer) |
| { |
| pgprot_t pgprot = pgprot_writecombine(PAGE_KERNEL); |
| |
| return _sglist_zero(buffer->sg_table.sgl, buffer->sg_table.orig_nents, pgprot); |
| } |
| |
| static int _pages_zero(struct page *page, size_t size, pgprot_t pgprot) |
| { |
| struct scatterlist sg; |
| |
| sg_init_table(&sg, 1); |
| sg_set_page(&sg, page, size, 0); |
| return _sglist_zero(&sg, 1, pgprot); |
| } |
| |
| static int ion_physical_heap_do_allocate(struct dma_heap *heap, |
| struct samsung_dma_buffer *buffer, |
| unsigned long size) |
| { |
| struct samsung_dma_heap *samsung_dma_heap = dma_heap_get_drvdata(heap); |
| struct ion_physical_heap *physical_heap = samsung_dma_heap->priv; |
| unsigned long aligned_size = ALIGN(size, samsung_dma_heap->alignment); |
| phys_addr_t paddr; |
| |
| paddr = gen_pool_alloc(physical_heap->pool, aligned_size); |
| if (!paddr) { |
| pr_err("%s: failed to allocate from AOC physical heap size %lu", |
| __func__, size); |
| return -ENOMEM; |
| } |
| |
| sg_set_page(buffer->sg_table.sgl, pfn_to_page(PFN_DOWN(paddr)), size, 0); |
| buffer->priv = (void *)(uintptr_t)hash_long(paddr, 32); |
| |
| if (physical_heap->allocate_cb) |
| physical_heap->allocate_cb(buffer, physical_heap->allocate_ctx); |
| |
| return 0; |
| } |
| |
| static void ion_physical_heap_buffer_free(struct samsung_dma_buffer *buffer) |
| { |
| struct ion_physical_heap *physical_heap = buffer->heap->priv; |
| struct page *page; |
| phys_addr_t paddr; |
| |
| page = sg_page(buffer->sg_table.sgl); |
| paddr = PFN_PHYS(page_to_pfn(page)); |
| |
| if (physical_heap->free_cb) |
| physical_heap->free_cb(buffer, physical_heap->free_ctx); |
| |
| _buffer_zero(buffer); |
| if (paddr) |
| gen_pool_free(physical_heap->pool, paddr, |
| ALIGN(buffer->len, buffer->heap->alignment)); |
| |
| samsung_dma_buffer_free(buffer); |
| } |
| |
| static struct dma_buf *ion_physical_heap_allocate(struct dma_heap *heap, |
| unsigned long len, |
| unsigned long fd_flags, |
| unsigned long heap_flags) |
| { |
| int ret; |
| struct dma_buf *dmabuf; |
| struct samsung_dma_buffer *buffer; |
| struct samsung_dma_heap *samsung_dma_heap = dma_heap_get_drvdata(heap); |
| |
| buffer = samsung_dma_buffer_alloc(samsung_dma_heap, len, 1); |
| if (IS_ERR(buffer)) |
| return ERR_CAST(buffer); |
| |
| ret = ion_physical_heap_do_allocate(heap, buffer, len); |
| if (ret) { |
| samsung_dma_buffer_free(buffer); |
| return ERR_PTR(ret); |
| } |
| |
| dmabuf = samsung_export_dmabuf(buffer, fd_flags); |
| if (IS_ERR(dmabuf)) |
| ion_physical_heap_buffer_free(buffer); |
| |
| return dmabuf; |
| } |
| |
| static long ion_physical_get_pool_size(struct dma_heap *heap) |
| { |
| struct samsung_dma_heap *samsung_dma_heap = dma_heap_get_drvdata(heap); |
| struct ion_physical_heap *physical_heap = samsung_dma_heap->priv; |
| |
| return physical_heap->size / PAGE_SIZE; |
| } |
| |
| static struct dma_heap_ops physical_heap_ops = { |
| .allocate = ion_physical_heap_allocate, |
| .get_pool_size = ion_physical_get_pool_size, |
| }; |
| |
| static struct dma_heap *samsung_dma_heap_create_helper(const char *name, |
| size_t align, |
| void *priv) |
| { |
| struct samsung_dma_heap *samsung_dma_heap; |
| struct dma_heap *heap; |
| struct dma_heap_export_info exp_info; |
| |
| samsung_dma_heap = kzalloc(sizeof(*samsung_dma_heap), GFP_KERNEL); |
| if (!samsung_dma_heap) |
| return ERR_PTR(-ENOMEM); |
| |
| samsung_dma_heap->name = name; |
| samsung_dma_heap->release = ion_physical_heap_buffer_free; |
| samsung_dma_heap->priv = priv; |
| samsung_dma_heap->flags = DMA_HEAP_FLAG_UNCACHED; |
| samsung_dma_heap->alignment = align; |
| |
| exp_info.name = name; |
| exp_info.ops = &physical_heap_ops; |
| exp_info.priv = samsung_dma_heap; |
| |
| heap = dma_heap_add(&exp_info); |
| if (IS_ERR(heap)) |
| kfree(samsung_dma_heap); |
| |
| return heap; |
| } |
| |
| struct dma_heap *ion_physical_heap_create(phys_addr_t base, size_t size, |
| size_t align, const char *name, |
| ion_physical_heap_allocate_callback alloc_cb, |
| ion_physical_heap_free_callback free_cb, |
| void *ctx) |
| { |
| struct ion_physical_heap *physical_heap; |
| int ret; |
| struct page *page; |
| struct dma_heap *physical_dmabuf_heap; |
| |
| page = pfn_to_page(PFN_DOWN(base)); |
| |
| ret = _pages_zero(page, size, pgprot_writecombine(PAGE_KERNEL)); |
| if (ret) |
| return ERR_PTR(ret); |
| |
| physical_heap = kzalloc(sizeof(*physical_heap), GFP_KERNEL); |
| if (!physical_heap) |
| return ERR_PTR(-ENOMEM); |
| |
| physical_heap->pool = gen_pool_create(get_order(align) + PAGE_SHIFT, -1); |
| if (!physical_heap->pool) { |
| kfree(physical_heap); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| physical_heap->base = base; |
| gen_pool_add(physical_heap->pool, physical_heap->base, size, -1); |
| |
| physical_heap->size = size; |
| |
| physical_heap->allocate_cb = alloc_cb; |
| physical_heap->allocate_ctx = ctx; |
| |
| physical_heap->free_cb = free_cb; |
| physical_heap->free_ctx = ctx; |
| |
| physical_dmabuf_heap = samsung_dma_heap_create_helper(name, align, |
| (void *)physical_heap); |
| if (IS_ERR(physical_dmabuf_heap)) { |
| gen_pool_destroy(physical_heap->pool); |
| kfree(physical_heap); |
| } |
| |
| return physical_dmabuf_heap; |
| } |