BACKPORT: dma-buf: heaps: add dma-heaps

Change-Id: Idf952c4f923a570f97da47e0d8d490d6427dd5d3
Signed-off-by: Jianqun Xu <jay.xu@rock-chips.com>
This commit is contained in:
Jianqun Xu
2022-09-13 15:09:24 +08:00
committed by Tao Huang
parent 43eba68e77
commit a414b9416b
13 changed files with 2672 additions and 0 deletions

View File

@ -35,9 +35,21 @@ config SW_SYNC
WARNING: improper use of this can result in deadlocking kernel
drivers from userspace. Intended for test and debug only.
menuconfig DMABUF_HEAPS
bool "DMA-BUF Userland Memory Heaps"
select DMA_SHARED_BUFFER
help
Choose this option to enable the DMA-BUF userland memory heaps.
This options creates per heap chardevs in /dev/dma_heap/ which
allows userspace to allocate dma-bufs that can be shared
between drivers.
config DMABUF_PAGE_POOL
bool "DMA-BUF page-pool library"
depends on !DMABUF_HEAPS
help
Choose this option to enable the DMA-BUF page-pool library.
source "drivers/dma-buf/heaps/Kconfig"
endmenu

View File

@ -1,4 +1,6 @@
obj-y := dma-buf.o dma-fence.o dma-fence-array.o reservation.o seqno-fence.o
obj-$(CONFIG_DMABUF_HEAPS) += dma-heap.o
obj-$(CONFIG_DMABUF_HEAPS) += heaps/
obj-$(CONFIG_DMABUF_CACHE) += dma-buf-cache.o
obj-$(CONFIG_DMABUF_PAGE_POOL) += dmabuf_page_pool.o
obj-$(CONFIG_SYNC_FILE) += sync_file.o

494
drivers/dma-buf/dma-heap.c Normal file
View File

@ -0,0 +1,494 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Framework for userspace DMA-BUF allocations
*
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2019 Linaro Ltd.
*/
#include <linux/cdev.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/dma-buf.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/nospec.h>
#include <linux/uaccess.h>
#include <linux/syscalls.h>
#include <linux/dma-heap.h>
#include <uapi/linux/dma-heap.h>
#define DEVNAME "dma_heap"
#define NUM_HEAP_MINORS 128
/**
* struct dma_heap - represents a dmabuf heap in the system
* @name: used for debugging/device-node name
* @ops: ops struct for this heap
* @heap_devt heap device node
* @list list head connecting to list of heaps
* @heap_cdev heap char device
* @heap_dev heap device struct
*
* Represents a heap of memory from which buffers can be made.
*/
struct dma_heap {
const char *name;
const struct dma_heap_ops *ops;
void *priv;
dev_t heap_devt;
struct list_head list;
struct cdev heap_cdev;
struct kref refcount;
struct device *heap_dev;
};
static LIST_HEAD(heap_list);
static DEFINE_MUTEX(heap_list_lock);
static dev_t dma_heap_devt;
static struct class *dma_heap_class;
static DEFINE_MUTEX(heap_devnode_lock);
static DECLARE_BITMAP(heap_devnode_nums, NUM_HEAP_MINORS);
struct dma_heap *dma_heap_find(const char *name)
{
struct dma_heap *h;
mutex_lock(&heap_list_lock);
list_for_each_entry(h, &heap_list, list) {
if (!strcmp(h->name, name)) {
kref_get(&h->refcount);
mutex_unlock(&heap_list_lock);
return h;
}
}
mutex_unlock(&heap_list_lock);
return NULL;
}
EXPORT_SYMBOL_GPL(dma_heap_find);
void dma_heap_buffer_free(struct dma_buf *dmabuf)
{
dma_buf_put(dmabuf);
}
EXPORT_SYMBOL_GPL(dma_heap_buffer_free);
struct dma_buf *dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
unsigned int fd_flags,
unsigned int heap_flags)
{
if (fd_flags & ~DMA_HEAP_VALID_FD_FLAGS)
return ERR_PTR(-EINVAL);
if (heap_flags & ~DMA_HEAP_VALID_HEAP_FLAGS)
return ERR_PTR(-EINVAL);
/*
* Allocations from all heaps have to begin
* and end on page boundaries.
*/
len = PAGE_ALIGN(len);
if (!len)
return ERR_PTR(-EINVAL);
return heap->ops->allocate(heap, len, fd_flags, heap_flags);
}
EXPORT_SYMBOL_GPL(dma_heap_buffer_alloc);
int dma_heap_bufferfd_alloc(struct dma_heap *heap, size_t len,
unsigned int fd_flags,
unsigned int heap_flags)
{
struct dma_buf *dmabuf;
int fd;
dmabuf = dma_heap_buffer_alloc(heap, len, fd_flags, heap_flags);
if (IS_ERR(dmabuf))
return PTR_ERR(dmabuf);
fd = dma_buf_fd(dmabuf, fd_flags);
if (fd < 0) {
dma_buf_put(dmabuf);
/* just return, as put will call release and that will free */
}
return fd;
}
EXPORT_SYMBOL_GPL(dma_heap_bufferfd_alloc);
static int dma_heap_open(struct inode *inode, struct file *file)
{
struct dma_heap *heap;
mutex_lock(&heap_devnode_lock);
heap = container_of(inode->i_cdev, struct dma_heap, heap_cdev);
if (!heap) {
pr_err("dma_heap: minor %d unknown.\n", iminor(inode));
mutex_unlock(&heap_devnode_lock);
return -ENODEV;
}
mutex_unlock(&heap_devnode_lock);
/* instance data as context */
file->private_data = heap;
nonseekable_open(inode, file);
return 0;
}
static long dma_heap_ioctl_allocate(struct file *file, void *data)
{
struct dma_heap_allocation_data *heap_allocation = data;
struct dma_heap *heap = file->private_data;
int fd;
if (heap_allocation->fd)
return -EINVAL;
fd = dma_heap_bufferfd_alloc(heap, heap_allocation->len,
heap_allocation->fd_flags,
heap_allocation->heap_flags);
if (fd < 0)
return fd;
heap_allocation->fd = fd;
return 0;
}
static int dma_heap_ioctl_get_phys(struct file *file, void *data)
{
#if IS_ENABLED(CONFIG_NO_GKI)
struct dma_heap *heap = file->private_data;
struct dma_heap_phys_data *phys = data;
if (heap->ops->get_phys)
return heap->ops->get_phys(heap, phys);
#endif
return -EINVAL;
}
static unsigned int dma_heap_ioctl_cmds[] = {
DMA_HEAP_IOCTL_ALLOC,
DMA_HEAP_IOCTL_GET_PHYS,
};
static long dma_heap_ioctl(struct file *file, unsigned int ucmd,
unsigned long arg)
{
char stack_kdata[128];
char *kdata = stack_kdata;
unsigned int kcmd;
unsigned int in_size, out_size, drv_size, ksize;
int nr = _IOC_NR(ucmd);
int ret = 0;
if (nr >= ARRAY_SIZE(dma_heap_ioctl_cmds))
return -EINVAL;
nr = array_index_nospec(nr, ARRAY_SIZE(dma_heap_ioctl_cmds));
/* Get the kernel ioctl cmd that matches */
kcmd = dma_heap_ioctl_cmds[nr];
/* Figure out the delta between user cmd size and kernel cmd size */
drv_size = _IOC_SIZE(kcmd);
out_size = _IOC_SIZE(ucmd);
in_size = out_size;
if ((ucmd & kcmd & IOC_IN) == 0)
in_size = 0;
if ((ucmd & kcmd & IOC_OUT) == 0)
out_size = 0;
ksize = max(max(in_size, out_size), drv_size);
/* If necessary, allocate buffer for ioctl argument */
if (ksize > sizeof(stack_kdata)) {
kdata = kmalloc(ksize, GFP_KERNEL);
if (!kdata)
return -ENOMEM;
}
if (copy_from_user(kdata, (void __user *)arg, in_size) != 0) {
ret = -EFAULT;
goto err;
}
/* zero out any difference between the kernel/user structure size */
if (ksize > in_size)
memset(kdata + in_size, 0, ksize - in_size);
switch (kcmd) {
case DMA_HEAP_IOCTL_ALLOC:
ret = dma_heap_ioctl_allocate(file, kdata);
break;
case DMA_HEAP_IOCTL_GET_PHYS:
ret = dma_heap_ioctl_get_phys(file, kdata);
break;
default:
ret = -ENOTTY;
goto err;
}
if (copy_to_user((void __user *)arg, kdata, out_size) != 0)
ret = -EFAULT;
err:
if (kdata != stack_kdata)
kfree(kdata);
return ret;
}
static const struct file_operations dma_heap_fops = {
.owner = THIS_MODULE,
.open = dma_heap_open,
.unlocked_ioctl = dma_heap_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = dma_heap_ioctl,
#endif
};
/**
* dma_heap_get_drvdata() - get per-subdriver data for the heap
* @heap: DMA-Heap to retrieve private data for
*
* Returns:
* The per-subdriver data for the heap.
*/
void *dma_heap_get_drvdata(struct dma_heap *heap)
{
return heap->priv;
}
EXPORT_SYMBOL_GPL(dma_heap_get_drvdata);
static void dma_heap_release(struct kref *ref)
{
struct dma_heap *heap = container_of(ref, struct dma_heap, refcount);
int minor = MINOR(heap->heap_devt);
/* Note, we already holding the heap_list_lock here */
list_del(&heap->list);
device_destroy(dma_heap_class, heap->heap_devt);
cdev_del(&heap->heap_cdev);
mutex_lock(&heap_devnode_lock);
clear_bit(minor, heap_devnode_nums);
mutex_unlock(&heap_devnode_lock);
kfree(heap);
}
void dma_heap_put(struct dma_heap *h)
{
/*
* Take the heap_list_lock now to avoid racing with code
* scanning the list and then taking a kref.
*/
mutex_lock(&heap_list_lock);
kref_put(&h->refcount, dma_heap_release);
mutex_unlock(&heap_list_lock);
}
EXPORT_SYMBOL_GPL(dma_heap_put);
/**
* dma_heap_get_dev() - get device struct for the heap
* @heap: DMA-Heap to retrieve device struct from
*
* Returns:
* The device struct for the heap.
*/
struct device *dma_heap_get_dev(struct dma_heap *heap)
{
return heap->heap_dev;
}
EXPORT_SYMBOL_GPL(dma_heap_get_dev);
/**
* dma_heap_get_name() - get heap name
* @heap: DMA-Heap to retrieve private data for
*
* Returns:
* The char* for the heap name.
*/
const char *dma_heap_get_name(struct dma_heap *heap)
{
return heap->name;
}
EXPORT_SYMBOL_GPL(dma_heap_get_name);
struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info)
{
struct dma_heap *heap, *err_ret;
unsigned int minor;
int ret;
if (!exp_info->name || !strcmp(exp_info->name, "")) {
pr_err("dma_heap: Cannot add heap without a name\n");
return ERR_PTR(-EINVAL);
}
if (!exp_info->ops || !exp_info->ops->allocate) {
pr_err("dma_heap: Cannot add heap with invalid ops struct\n");
return ERR_PTR(-EINVAL);
}
/* check the name is unique */
heap = dma_heap_find(exp_info->name);
if (heap) {
pr_err("dma_heap: Already registered heap named %s\n",
exp_info->name);
dma_heap_put(heap);
return ERR_PTR(-EINVAL);
}
heap = kzalloc(sizeof(*heap), GFP_KERNEL);
if (!heap)
return ERR_PTR(-ENOMEM);
kref_init(&heap->refcount);
heap->name = exp_info->name;
heap->ops = exp_info->ops;
heap->priv = exp_info->priv;
/* Part 1: Find a free minor number */
mutex_lock(&heap_devnode_lock);
minor = find_next_zero_bit(heap_devnode_nums, NUM_HEAP_MINORS, 0);
if (minor == NUM_HEAP_MINORS) {
mutex_unlock(&heap_devnode_lock);
pr_err("dma_heap: Unable to get minor number for heap\n");
err_ret = ERR_PTR(-ENFILE);
goto err0;
}
set_bit(minor, heap_devnode_nums);
mutex_unlock(&heap_devnode_lock);
/* Create device */
heap->heap_devt = MKDEV(MAJOR(dma_heap_devt), minor);
cdev_init(&heap->heap_cdev, &dma_heap_fops);
ret = cdev_add(&heap->heap_cdev, heap->heap_devt, 1);
if (ret < 0) {
pr_err("dma_heap: Unable to add char device\n");
err_ret = ERR_PTR(ret);
goto err1;
}
heap->heap_dev = device_create(dma_heap_class,
NULL,
heap->heap_devt,
NULL,
heap->name);
if (IS_ERR(heap->heap_dev)) {
pr_err("dma_heap: Unable to create device\n");
err_ret = ERR_CAST(heap->heap_dev);
goto err2;
}
/* Make sure it doesn't disappear on us */
heap->heap_dev = get_device(heap->heap_dev);
/* Add heap to the list */
mutex_lock(&heap_list_lock);
list_add(&heap->list, &heap_list);
mutex_unlock(&heap_list_lock);
return heap;
err2:
cdev_del(&heap->heap_cdev);
err1:
mutex_lock(&heap_devnode_lock);
clear_bit(minor, heap_devnode_nums);
mutex_unlock(&heap_devnode_lock);
err0:
kfree(heap);
return err_ret;
}
EXPORT_SYMBOL_GPL(dma_heap_add);
static char *dma_heap_devnode(struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "dma_heap/%s", dev_name(dev));
}
static ssize_t total_pools_kb_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct dma_heap *heap;
u64 total_pool_size = 0;
mutex_lock(&heap_list_lock);
list_for_each_entry(heap, &heap_list, list) {
if (heap->ops->get_pool_size)
total_pool_size += heap->ops->get_pool_size(heap);
}
mutex_unlock(&heap_list_lock);
return sysfs_emit(buf, "%llu\n", total_pool_size / 1024);
}
static struct kobj_attribute total_pools_kb_attr =
__ATTR_RO(total_pools_kb);
static struct attribute *dma_heap_sysfs_attrs[] = {
&total_pools_kb_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(dma_heap_sysfs);
static struct kobject *dma_heap_kobject;
static int dma_heap_sysfs_setup(void)
{
int ret;
dma_heap_kobject = kobject_create_and_add("dma_heap", kernel_kobj);
if (!dma_heap_kobject)
return -ENOMEM;
ret = sysfs_create_groups(dma_heap_kobject, dma_heap_sysfs_groups);
if (ret) {
kobject_put(dma_heap_kobject);
return ret;
}
return 0;
}
static void dma_heap_sysfs_teardown(void)
{
kobject_put(dma_heap_kobject);
}
static int dma_heap_init(void)
{
int ret;
ret = dma_heap_sysfs_setup();
if (ret)
return ret;
ret = alloc_chrdev_region(&dma_heap_devt, 0, NUM_HEAP_MINORS, DEVNAME);
if (ret)
goto err_chrdev;
dma_heap_class = class_create(THIS_MODULE, DEVNAME);
if (IS_ERR(dma_heap_class)) {
ret = PTR_ERR(dma_heap_class);
goto err_class;
}
dma_heap_class->devnode = dma_heap_devnode;
return 0;
err_class:
unregister_chrdev_region(dma_heap_devt, NUM_HEAP_MINORS);
err_chrdev:
dma_heap_sysfs_teardown();
return ret;
}
subsys_initcall(dma_heap_init);

View File

@ -0,0 +1,24 @@
menuconfig DMABUF_HEAPS_DEFERRED_FREE
bool "DMA-BUF heaps deferred-free library"
help
Choose this option to enable the DMA-BUF heaps deferred-free library.
menuconfig DMABUF_HEAPS_PAGE_POOL
bool "DMA-BUF heaps page-pool library"
help
Choose this option to enable the DMA-BUF heaps page-pool library.
config DMABUF_HEAPS_SYSTEM
tristate "DMA-BUF System Heap"
depends on DMABUF_HEAPS && DMABUF_HEAPS_DEFERRED_FREE && DMABUF_HEAPS_PAGE_POOL
help
Choose this option to enable the system dmabuf heap. The system heap
is backed by pages from the buddy allocator. If in doubt, say Y.
config DMABUF_HEAPS_CMA
tristate "DMA-BUF CMA Heap"
depends on DMABUF_HEAPS && DMA_CMA
help
Choose this option to enable dma-buf CMA heap. This heap is backed
by the Contiguous Memory Allocator (CMA). If your system has these
regions, you should say Y here.

View File

@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DMABUF_HEAPS_DEFERRED_FREE) += deferred-free-helper.o
obj-$(CONFIG_DMABUF_HEAPS_PAGE_POOL) += page_pool.o
obj-$(CONFIG_DMABUF_HEAPS_SYSTEM) += rk_system_heap.o
obj-$(CONFIG_DMABUF_HEAPS_CMA) += cma_heap.o

View File

@ -0,0 +1,604 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DMABUF CMA heap exporter
*
* Copyright (C) 2012, 2019, 2020 Linaro Ltd.
* Author: <benjamin.gaignard@linaro.org> for ST-Ericsson.
*
* Also utilizing parts of Andrew Davis' SRAM heap:
* Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
* Andrew F. Davis <afd@ti.com>
*/
#include <linux/cma.h>
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#include <linux/dma-contiguous.h>
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <uapi/linux/dma-heap.h>
struct cma_heap {
struct dma_heap *heap;
struct cma *cma;
};
struct cma_heap_buffer {
struct cma_heap *heap;
struct list_head attachments;
struct mutex lock;
unsigned long len;
struct page *cma_pages;
struct page **pages;
pgoff_t pagecount;
int vmap_cnt;
void *vaddr;
bool uncached;
};
struct dma_heap_attachment {
struct device *dev;
struct sg_table table;
struct list_head list;
bool mapped;
bool uncached;
};
static int cma_heap_attach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
int ret;
a = kzalloc(sizeof(*a), GFP_KERNEL);
if (!a)
return -ENOMEM;
ret = sg_alloc_table_from_pages(&a->table, buffer->pages,
buffer->pagecount, 0,
buffer->pagecount << PAGE_SHIFT,
GFP_KERNEL);
if (ret) {
kfree(a);
return ret;
}
a->dev = attachment->dev;
INIT_LIST_HEAD(&a->list);
a->mapped = false;
a->uncached = buffer->uncached;
attachment->priv = a;
mutex_lock(&buffer->lock);
list_add(&a->list, &buffer->attachments);
mutex_unlock(&buffer->lock);
return 0;
}
static void cma_heap_detach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a = attachment->priv;
mutex_lock(&buffer->lock);
list_del(&a->list);
mutex_unlock(&buffer->lock);
sg_free_table(&a->table);
kfree(a);
}
static struct sg_table *cma_heap_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
struct sg_table *table = &a->table;
int attrs = attachment->dma_map_attrs;
int ret;
if (a->uncached)
attrs |= DMA_ATTR_SKIP_CPU_SYNC;
ret = dma_map_sgtable(attachment->dev, table, direction, attrs);
if (ret)
return ERR_PTR(-ENOMEM);
a->mapped = true;
return table;
}
static void cma_heap_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
int attrs = attachment->dma_map_attrs;
a->mapped = false;
if (a->uncached)
attrs |= DMA_ATTR_SKIP_CPU_SYNC;
dma_unmap_sgtable(attachment->dev, table, direction, attrs);
}
static int
cma_heap_dma_buf_begin_cpu_access_partial(struct dma_buf *dmabuf,
enum dma_data_direction direction,
unsigned int offset,
unsigned int len)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
if (buffer->vmap_cnt)
invalidate_kernel_vmap_range(buffer->vaddr, buffer->len);
if (buffer->uncached)
return 0;
mutex_lock(&buffer->lock);
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_cpu(a->dev, &a->table, direction);
}
if (list_empty(&buffer->attachments)) {
phys_addr_t phys = page_to_phys(buffer->cma_pages);
dma_sync_single_for_cpu(dma_heap_get_dev(buffer->heap->heap),
phys + offset,
len,
direction);
}
mutex_unlock(&buffer->lock);
return 0;
}
static int
cma_heap_dma_buf_end_cpu_access_partial(struct dma_buf *dmabuf,
enum dma_data_direction direction,
unsigned int offset,
unsigned int len)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
if (buffer->vmap_cnt)
flush_kernel_vmap_range(buffer->vaddr, buffer->len);
if (buffer->uncached)
return 0;
mutex_lock(&buffer->lock);
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_device(a->dev, &a->table, direction);
}
if (list_empty(&buffer->attachments)) {
phys_addr_t phys = page_to_phys(buffer->cma_pages);
dma_sync_single_for_device(dma_heap_get_dev(buffer->heap->heap),
phys + offset,
len,
direction);
}
mutex_unlock(&buffer->lock);
return 0;
}
static int cma_heap_dma_buf_begin_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction dir)
{
return cma_heap_dma_buf_begin_cpu_access_partial(dmabuf, dir, 0,
dmabuf->size);
}
static int cma_heap_dma_buf_end_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction dir)
{
return cma_heap_dma_buf_end_cpu_access_partial(dmabuf, dir, 0,
dmabuf->size);
}
static vm_fault_t cma_heap_vm_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
struct cma_heap_buffer *buffer = vma->vm_private_data;
if (vmf->pgoff > buffer->pagecount)
return VM_FAULT_SIGBUS;
vmf->page = buffer->pages[vmf->pgoff];
get_page(vmf->page);
return 0;
}
static const struct vm_operations_struct dma_heap_vm_ops = {
.fault = cma_heap_vm_fault,
};
static int cma_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) == 0)
return -EINVAL;
if (buffer->uncached)
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
vma->vm_ops = &dma_heap_vm_ops;
vma->vm_private_data = buffer;
return 0;
}
static void *cma_heap_do_vmap(struct cma_heap_buffer *buffer)
{
void *vaddr;
pgprot_t pgprot = PAGE_KERNEL;
if (buffer->uncached)
pgprot = pgprot_writecombine(PAGE_KERNEL);
vaddr = vmap(buffer->pages, buffer->pagecount, VM_MAP, pgprot);
if (!vaddr)
return ERR_PTR(-ENOMEM);
return vaddr;
}
static void *cma_heap_vmap(struct dma_buf *dmabuf)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
void *vaddr;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt) {
buffer->vmap_cnt++;
vaddr = buffer->vaddr;
goto out;
}
vaddr = cma_heap_do_vmap(buffer);
if (IS_ERR(vaddr))
goto out;
buffer->vaddr = vaddr;
buffer->vmap_cnt++;
out:
mutex_unlock(&buffer->lock);
return vaddr;
}
static void cma_heap_vunmap(struct dma_buf *dmabuf, void *vaddr)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
mutex_lock(&buffer->lock);
if (!--buffer->vmap_cnt) {
vunmap(buffer->vaddr);
buffer->vaddr = NULL;
}
mutex_unlock(&buffer->lock);
}
static void cma_heap_dma_buf_release(struct dma_buf *dmabuf)
{
struct cma_heap_buffer *buffer = dmabuf->priv;
struct cma_heap *cma_heap = buffer->heap;
if (buffer->vmap_cnt > 0) {
WARN(1, "%s: buffer still mapped in the kernel\n", __func__);
vunmap(buffer->vaddr);
}
/* free page list */
kfree(buffer->pages);
/* release memory */
cma_release(cma_heap->cma, buffer->cma_pages, buffer->pagecount);
kfree(buffer);
}
static const struct dma_buf_ops cma_heap_buf_ops = {
.attach = cma_heap_attach,
.detach = cma_heap_detach,
.map_dma_buf = cma_heap_map_dma_buf,
.unmap_dma_buf = cma_heap_unmap_dma_buf,
.begin_cpu_access = cma_heap_dma_buf_begin_cpu_access,
.end_cpu_access = cma_heap_dma_buf_end_cpu_access,
.begin_cpu_access_partial = cma_heap_dma_buf_begin_cpu_access_partial,
.end_cpu_access_partial = cma_heap_dma_buf_end_cpu_access_partial,
.mmap = cma_heap_mmap,
.vmap = cma_heap_vmap,
.vunmap = cma_heap_vunmap,
.release = cma_heap_dma_buf_release,
};
static struct dma_buf *cma_heap_do_allocate(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags, bool uncached)
{
struct cma_heap *cma_heap = dma_heap_get_drvdata(heap);
struct cma_heap_buffer *buffer;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
size_t size = PAGE_ALIGN(len);
pgoff_t pagecount = size >> PAGE_SHIFT;
unsigned long align = get_order(size);
struct page *cma_pages;
struct dma_buf *dmabuf;
int ret = -ENOMEM;
pgoff_t pg;
dma_addr_t dma;
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
buffer->uncached = uncached;
INIT_LIST_HEAD(&buffer->attachments);
mutex_init(&buffer->lock);
buffer->len = size;
if (align > CONFIG_CMA_ALIGNMENT)
align = CONFIG_CMA_ALIGNMENT;
cma_pages = cma_alloc(cma_heap->cma, pagecount, align, GFP_KERNEL);
if (!cma_pages)
goto free_buffer;
/* Clear the cma pages */
if (PageHighMem(cma_pages)) {
unsigned long nr_clear_pages = pagecount;
struct page *page = cma_pages;
while (nr_clear_pages > 0) {
void *vaddr = kmap_atomic(page);
memset(vaddr, 0, PAGE_SIZE);
kunmap_atomic(vaddr);
/*
* Avoid wasting time zeroing memory if the process
* has been killed by by SIGKILL
*/
if (fatal_signal_pending(current))
goto free_cma;
page++;
nr_clear_pages--;
}
} else {
memset(page_address(cma_pages), 0, size);
}
buffer->pages = kmalloc_array(pagecount, sizeof(*buffer->pages), GFP_KERNEL);
if (!buffer->pages) {
ret = -ENOMEM;
goto free_cma;
}
for (pg = 0; pg < pagecount; pg++)
buffer->pages[pg] = &cma_pages[pg];
buffer->cma_pages = cma_pages;
buffer->heap = cma_heap;
buffer->pagecount = pagecount;
/* create the dmabuf */
exp_info.exp_name = dma_heap_get_name(heap);
exp_info.ops = &cma_heap_buf_ops;
exp_info.size = buffer->len;
exp_info.flags = fd_flags;
exp_info.priv = buffer;
dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf)) {
ret = PTR_ERR(dmabuf);
goto free_pages;
}
if (buffer->uncached) {
dma = dma_map_page(dma_heap_get_dev(heap), buffer->cma_pages, 0,
buffer->pagecount * PAGE_SIZE, DMA_FROM_DEVICE);
dma_unmap_page(dma_heap_get_dev(heap), dma,
buffer->pagecount * PAGE_SIZE, DMA_FROM_DEVICE);
}
return dmabuf;
free_pages:
kfree(buffer->pages);
free_cma:
cma_release(cma_heap->cma, cma_pages, pagecount);
free_buffer:
kfree(buffer);
return ERR_PTR(ret);
}
static struct dma_buf *cma_heap_allocate(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags)
{
return cma_heap_do_allocate(heap, len, fd_flags, heap_flags, false);
}
#if IS_ENABLED(CONFIG_NO_GKI)
static int cma_heap_get_phys(struct dma_heap *heap,
struct dma_heap_phys_data *phys)
{
struct cma_heap *cma_heap = dma_heap_get_drvdata(heap);
struct cma_heap_buffer *buffer;
struct dma_buf *dmabuf;
phys->paddr = (__u64)-1;
if (IS_ERR_OR_NULL(phys))
return -EINVAL;
dmabuf = dma_buf_get(phys->fd);
if (IS_ERR_OR_NULL(dmabuf))
return -EBADFD;
buffer = dmabuf->priv;
if (IS_ERR_OR_NULL(buffer))
goto err;
if (buffer->heap != cma_heap)
goto err;
phys->paddr = page_to_phys(buffer->cma_pages);
err:
dma_buf_put(dmabuf);
return (phys->paddr == (__u64)-1) ? -EINVAL : 0;
}
#endif
static const struct dma_heap_ops cma_heap_ops = {
.allocate = cma_heap_allocate,
#if IS_ENABLED(CONFIG_NO_GKI)
.get_phys = cma_heap_get_phys,
#endif
};
static struct dma_buf *cma_uncached_heap_allocate(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags)
{
return cma_heap_do_allocate(heap, len, fd_flags, heap_flags, true);
}
static struct dma_buf *cma_uncached_heap_not_initialized(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags)
{
pr_info("heap %s not initialized\n", dma_heap_get_name(heap));
return ERR_PTR(-EBUSY);
}
static struct dma_heap_ops cma_uncached_heap_ops = {
.allocate = cma_uncached_heap_not_initialized,
};
static int set_heap_dev_dma(struct device *heap_dev)
{
int err = 0;
if (!heap_dev)
return -EINVAL;
dma_coerce_mask_and_coherent(heap_dev, DMA_BIT_MASK(64));
if (!heap_dev->dma_parms) {
heap_dev->dma_parms = devm_kzalloc(heap_dev,
sizeof(*heap_dev->dma_parms),
GFP_KERNEL);
if (!heap_dev->dma_parms)
return -ENOMEM;
err = dma_set_max_seg_size(heap_dev, (unsigned int)DMA_BIT_MASK(64));
if (err) {
devm_kfree(heap_dev, heap_dev->dma_parms);
dev_err(heap_dev, "Failed to set DMA segment size, err:%d\n", err);
return err;
}
}
return 0;
}
static int __add_cma_heap(struct cma *cma, void *data)
{
struct cma_heap *cma_heap, *cma_uncached_heap;
struct dma_heap_export_info exp_info;
int ret;
cma_heap = kzalloc(sizeof(*cma_heap), GFP_KERNEL);
if (!cma_heap)
return -ENOMEM;
cma_heap->cma = cma;
exp_info.name = cma_get_name(cma);
exp_info.ops = &cma_heap_ops;
exp_info.priv = cma_heap;
cma_heap->heap = dma_heap_add(&exp_info);
if (IS_ERR(cma_heap->heap)) {
ret = PTR_ERR(cma_heap->heap);
goto free_cma_heap;
}
cma_uncached_heap = kzalloc(sizeof(*cma_heap), GFP_KERNEL);
if (!cma_uncached_heap) {
ret = -ENOMEM;
goto put_cma_heap;
}
cma_uncached_heap->cma = cma;
exp_info.name = "cma-uncached";
exp_info.ops = &cma_uncached_heap_ops;
exp_info.priv = cma_uncached_heap;
cma_uncached_heap->heap = dma_heap_add(&exp_info);
if (IS_ERR(cma_uncached_heap->heap)) {
ret = PTR_ERR(cma_uncached_heap->heap);
goto free_uncached_cma_heap;
}
ret = set_heap_dev_dma(dma_heap_get_dev(cma_uncached_heap->heap));
if (ret)
goto put_uncached_cma_heap;
mb(); /* make sure we only set allocate after dma_mask is set */
cma_uncached_heap_ops.allocate = cma_uncached_heap_allocate;
return 0;
put_uncached_cma_heap:
dma_heap_put(cma_uncached_heap->heap);
free_uncached_cma_heap:
kfree(cma_uncached_heap);
put_cma_heap:
dma_heap_put(cma_heap->heap);
free_cma_heap:
kfree(cma_heap);
return ret;
}
static int add_default_cma_heap(void)
{
struct cma *default_cma = dev_get_cma_area(NULL);
int ret = 0;
if (default_cma)
ret = __add_cma_heap(default_cma, NULL);
return ret;
}
module_init(add_default_cma_heap);
MODULE_DESCRIPTION("DMA-BUF CMA Heap");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Deferred dmabuf freeing helper
*
* Copyright (C) 2020 Linaro, Ltd.
*
* Based on the ION page pool code
* Copyright (C) 2011 Google, Inc.
*/
#include <linux/module.h>
#include <linux/freezer.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/swap.h>
#include <uapi/linux/sched/types.h>
#include "deferred-free-helper.h"
static LIST_HEAD(free_list);
static size_t list_nr_pages;
wait_queue_head_t freelist_waitqueue;
struct task_struct *freelist_task;
static DEFINE_SPINLOCK(free_list_lock);
void deferred_free(struct deferred_freelist_item *item,
void (*free)(struct deferred_freelist_item*,
enum df_reason),
size_t nr_pages)
{
unsigned long flags;
INIT_LIST_HEAD(&item->list);
item->nr_pages = nr_pages;
item->free = free;
spin_lock_irqsave(&free_list_lock, flags);
list_add(&item->list, &free_list);
list_nr_pages += nr_pages;
spin_unlock_irqrestore(&free_list_lock, flags);
wake_up(&freelist_waitqueue);
}
EXPORT_SYMBOL_GPL(deferred_free);
static size_t free_one_item(enum df_reason reason)
{
unsigned long flags;
size_t nr_pages;
struct deferred_freelist_item *item;
spin_lock_irqsave(&free_list_lock, flags);
if (list_empty(&free_list)) {
spin_unlock_irqrestore(&free_list_lock, flags);
return 0;
}
item = list_first_entry(&free_list, struct deferred_freelist_item, list);
list_del(&item->list);
nr_pages = item->nr_pages;
list_nr_pages -= nr_pages;
spin_unlock_irqrestore(&free_list_lock, flags);
item->free(item, reason);
return nr_pages;
}
unsigned long get_freelist_nr_pages(void)
{
unsigned long nr_pages;
unsigned long flags;
spin_lock_irqsave(&free_list_lock, flags);
nr_pages = list_nr_pages;
spin_unlock_irqrestore(&free_list_lock, flags);
return nr_pages;
}
EXPORT_SYMBOL_GPL(get_freelist_nr_pages);
static unsigned long freelist_shrink_count(struct shrinker *shrinker,
struct shrink_control *sc)
{
return get_freelist_nr_pages();
}
static unsigned long freelist_shrink_scan(struct shrinker *shrinker,
struct shrink_control *sc)
{
unsigned long total_freed = 0;
if (sc->nr_to_scan == 0)
return 0;
while (total_freed < sc->nr_to_scan) {
size_t pages_freed = free_one_item(DF_UNDER_PRESSURE);
if (!pages_freed)
break;
total_freed += pages_freed;
}
return total_freed;
}
static struct shrinker freelist_shrinker = {
.count_objects = freelist_shrink_count,
.scan_objects = freelist_shrink_scan,
.seeks = DEFAULT_SEEKS,
.batch = 0,
};
static int deferred_free_thread(void *data)
{
while (true) {
wait_event_freezable(freelist_waitqueue,
get_freelist_nr_pages() > 0);
free_one_item(DF_NORMAL);
}
return 0;
}
static int deferred_freelist_init(void)
{
struct sched_param param = { .sched_priority = 0 };
list_nr_pages = 0;
init_waitqueue_head(&freelist_waitqueue);
freelist_task = kthread_run(deferred_free_thread, NULL,
"%s", "dmabuf-deferred-free-worker");
if (IS_ERR(freelist_task)) {
pr_err("Creating thread for deferred free failed\n");
return -1;
}
sched_setscheduler(freelist_task, SCHED_IDLE, &param);
return register_shrinker(&freelist_shrinker);
}
module_init(deferred_freelist_init);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef DEFERRED_FREE_HELPER_H
#define DEFERRED_FREE_HELPER_H
/**
* df_reason - enum for reason why item was freed
*
* This provides a reason for why the free function was called
* on the item. This is useful when deferred_free is used in
* combination with a pagepool, so under pressure the page can
* be immediately freed.
*
* DF_NORMAL: Normal deferred free
*
* DF_UNDER_PRESSURE: Free was called because the system
* is under memory pressure. Usually
* from a shrinker. Avoid allocating
* memory in the free call, as it may
* fail.
*/
enum df_reason {
DF_NORMAL,
DF_UNDER_PRESSURE,
};
/**
* deferred_freelist_item - item structure for deferred freelist
*
* This is to be added to the structure for whatever you want to
* defer freeing on.
*
* @nr_pages: number of pages used by item to be freed
* @free: function pointer to be called when freeing the item
* @list: list entry for the deferred list
*/
struct deferred_freelist_item {
size_t nr_pages;
void (*free)(struct deferred_freelist_item *i,
enum df_reason reason);
struct list_head list;
};
/**
* deferred_free - call to add item to the deferred free list
*
* @item: Pointer to deferred_freelist_item field of a structure
* @free: Function pointer to the free call
* @nr_pages: number of pages to be freed
*/
void deferred_free(struct deferred_freelist_item *item,
void (*free)(struct deferred_freelist_item *i,
enum df_reason reason),
size_t nr_pages);
unsigned long get_freelist_nr_pages(void);
#endif

View File

@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DMA BUF page pool system
*
* Copyright (C) 2020 Linaro Ltd.
*
* Based on the ION page pool code
* Copyright (C) 2011 Google, Inc.
*/
#include <linux/module.h>
#include <linux/freezer.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/swap.h>
#include <linux/sched/signal.h>
#include "page_pool.h"
static LIST_HEAD(pool_list);
static DEFINE_MUTEX(pool_list_lock);
static inline
struct page *dmabuf_page_pool_alloc_pages(struct dmabuf_page_pool *pool)
{
if (fatal_signal_pending(current))
return NULL;
return alloc_pages(pool->gfp_mask, pool->order);
}
static inline void dmabuf_page_pool_free_pages(struct dmabuf_page_pool *pool,
struct page *page)
{
__free_pages(page, pool->order);
}
static void dmabuf_page_pool_add(struct dmabuf_page_pool *pool, struct page *page)
{
int index;
if (PageHighMem(page))
index = POOL_HIGHPAGE;
else
index = POOL_LOWPAGE;
mutex_lock(&pool->mutex);
list_add_tail(&page->lru, &pool->items[index]);
pool->count[index]++;
mod_node_page_state(page_pgdat(page), NR_KERNEL_MISC_RECLAIMABLE,
1 << pool->order);
mutex_unlock(&pool->mutex);
}
static struct page *dmabuf_page_pool_remove(struct dmabuf_page_pool *pool, int index)
{
struct page *page;
mutex_lock(&pool->mutex);
page = list_first_entry_or_null(&pool->items[index], struct page, lru);
if (page) {
pool->count[index]--;
list_del(&page->lru);
mod_node_page_state(page_pgdat(page), NR_KERNEL_MISC_RECLAIMABLE,
-(1 << pool->order));
}
mutex_unlock(&pool->mutex);
return page;
}
static struct page *dmabuf_page_pool_fetch(struct dmabuf_page_pool *pool)
{
struct page *page = NULL;
page = dmabuf_page_pool_remove(pool, POOL_HIGHPAGE);
if (!page)
page = dmabuf_page_pool_remove(pool, POOL_LOWPAGE);
return page;
}
struct page *dmabuf_page_pool_alloc(struct dmabuf_page_pool *pool)
{
struct page *page = NULL;
if (WARN_ON(!pool))
return NULL;
page = dmabuf_page_pool_fetch(pool);
if (!page)
page = dmabuf_page_pool_alloc_pages(pool);
return page;
}
EXPORT_SYMBOL_GPL(dmabuf_page_pool_alloc);
void dmabuf_page_pool_free(struct dmabuf_page_pool *pool, struct page *page)
{
if (WARN_ON(pool->order != compound_order(page)))
return;
dmabuf_page_pool_add(pool, page);
}
EXPORT_SYMBOL_GPL(dmabuf_page_pool_free);
static int dmabuf_page_pool_total(struct dmabuf_page_pool *pool, bool high)
{
int count = pool->count[POOL_LOWPAGE];
if (high)
count += pool->count[POOL_HIGHPAGE];
return count << pool->order;
}
struct dmabuf_page_pool *dmabuf_page_pool_create(gfp_t gfp_mask, unsigned int order)
{
struct dmabuf_page_pool *pool = kmalloc(sizeof(*pool), GFP_KERNEL);
int i;
if (!pool)
return NULL;
for (i = 0; i < POOL_TYPE_SIZE; i++) {
pool->count[i] = 0;
INIT_LIST_HEAD(&pool->items[i]);
}
pool->gfp_mask = gfp_mask | __GFP_COMP;
pool->order = order;
mutex_init(&pool->mutex);
mutex_lock(&pool_list_lock);
list_add(&pool->list, &pool_list);
mutex_unlock(&pool_list_lock);
return pool;
}
EXPORT_SYMBOL_GPL(dmabuf_page_pool_create);
void dmabuf_page_pool_destroy(struct dmabuf_page_pool *pool)
{
struct page *page;
int i;
/* Remove us from the pool list */
mutex_lock(&pool_list_lock);
list_del(&pool->list);
mutex_unlock(&pool_list_lock);
/* Free any remaining pages in the pool */
for (i = 0; i < POOL_TYPE_SIZE; i++) {
while ((page = dmabuf_page_pool_remove(pool, i)))
dmabuf_page_pool_free_pages(pool, page);
}
kfree(pool);
}
EXPORT_SYMBOL_GPL(dmabuf_page_pool_destroy);
static int dmabuf_page_pool_do_shrink(struct dmabuf_page_pool *pool, gfp_t gfp_mask,
int nr_to_scan)
{
int freed = 0;
bool high;
if (current_is_kswapd())
high = true;
else
high = !!(gfp_mask & __GFP_HIGHMEM);
if (nr_to_scan == 0)
return dmabuf_page_pool_total(pool, high);
while (freed < nr_to_scan) {
struct page *page;
/* Try to free low pages first */
page = dmabuf_page_pool_remove(pool, POOL_LOWPAGE);
if (!page)
page = dmabuf_page_pool_remove(pool, POOL_HIGHPAGE);
if (!page)
break;
dmabuf_page_pool_free_pages(pool, page);
freed += (1 << pool->order);
}
return freed;
}
static int dmabuf_page_pool_shrink(gfp_t gfp_mask, int nr_to_scan)
{
struct dmabuf_page_pool *pool;
int nr_total = 0;
int nr_freed;
int only_scan = 0;
if (!nr_to_scan)
only_scan = 1;
mutex_lock(&pool_list_lock);
list_for_each_entry(pool, &pool_list, list) {
if (only_scan) {
nr_total += dmabuf_page_pool_do_shrink(pool,
gfp_mask,
nr_to_scan);
} else {
nr_freed = dmabuf_page_pool_do_shrink(pool,
gfp_mask,
nr_to_scan);
nr_to_scan -= nr_freed;
nr_total += nr_freed;
if (nr_to_scan <= 0)
break;
}
}
mutex_unlock(&pool_list_lock);
return nr_total;
}
static unsigned long dmabuf_page_pool_shrink_count(struct shrinker *shrinker,
struct shrink_control *sc)
{
return dmabuf_page_pool_shrink(sc->gfp_mask, 0);
}
static unsigned long dmabuf_page_pool_shrink_scan(struct shrinker *shrinker,
struct shrink_control *sc)
{
if (sc->nr_to_scan == 0)
return 0;
return dmabuf_page_pool_shrink(sc->gfp_mask, sc->nr_to_scan);
}
struct shrinker pool_shrinker = {
.count_objects = dmabuf_page_pool_shrink_count,
.scan_objects = dmabuf_page_pool_shrink_scan,
.seeks = DEFAULT_SEEKS,
.batch = 0,
};
static int dmabuf_page_pool_init_shrinker(void)
{
return register_shrinker(&pool_shrinker);
}
module_init(dmabuf_page_pool_init_shrinker);
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* DMA BUF PagePool implementation
* Based on earlier ION code by Google
*
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2020 Linaro Ltd.
*/
#ifndef _DMABUF_PAGE_POOL_H
#define _DMABUF_PAGE_POOL_H
#include <linux/device.h>
#include <linux/kref.h>
#include <linux/mm_types.h>
#include <linux/mutex.h>
#include <linux/shrinker.h>
#include <linux/types.h>
/* page types we track in the pool */
enum {
POOL_LOWPAGE, /* Clean lowmem pages */
POOL_HIGHPAGE, /* Clean highmem pages */
POOL_TYPE_SIZE,
};
/**
* struct dmabuf_page_pool - pagepool struct
* @count[]: array of number of pages of that type in the pool
* @items[]: array of list of pages of the specific type
* @mutex: lock protecting this struct and especially the count
* item list
* @gfp_mask: gfp_mask to use from alloc
* @order: order of pages in the pool
* @list: list node for list of pools
*
* Allows you to keep a pool of pre allocated pages to use
*/
struct dmabuf_page_pool {
int count[POOL_TYPE_SIZE];
struct list_head items[POOL_TYPE_SIZE];
struct mutex mutex;
gfp_t gfp_mask;
unsigned int order;
struct list_head list;
};
struct dmabuf_page_pool *dmabuf_page_pool_create(gfp_t gfp_mask,
unsigned int order);
void dmabuf_page_pool_destroy(struct dmabuf_page_pool *pool);
struct page *dmabuf_page_pool_alloc(struct dmabuf_page_pool *pool);
void dmabuf_page_pool_free(struct dmabuf_page_pool *pool, struct page *page);
#endif /* _DMABUF_PAGE_POOL_H */

View File

@ -0,0 +1,840 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DMABUF System heap exporter for Rockchip
*
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2019, 2020 Linaro Ltd.
* Copyright (c) 2021, 2022 Rockchip Electronics Co. Ltd.
*
* Portions based off of Andrew Davis' SRAM heap:
* Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
* Andrew F. Davis <afd@ti.com>
*/
#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
#include <linux/dma-heap.h>
#include <linux/err.h>
#include <linux/highmem.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/swiotlb.h>
#include <linux/vmalloc.h>
#include <linux/rockchip/rockchip_sip.h>
#include "page_pool.h"
#include "deferred-free-helper.h"
static struct dma_heap *sys_heap;
static struct dma_heap *sys_dma32_heap;
static struct dma_heap *sys_uncached_heap;
static struct dma_heap *sys_uncached_dma32_heap;
/* Default setting */
static u32 bank_bit_first = 12;
static u32 bank_bit_mask = 0x7;
struct system_heap_buffer {
struct dma_heap *heap;
struct list_head attachments;
struct mutex lock;
unsigned long len;
struct sg_table sg_table;
int vmap_cnt;
void *vaddr;
struct deferred_freelist_item deferred_free;
struct dmabuf_page_pool **pools;
bool uncached;
};
struct dma_heap_attachment {
struct device *dev;
struct sg_table *table;
struct list_head list;
bool mapped;
bool uncached;
};
#define LOW_ORDER_GFP (GFP_HIGHUSER | __GFP_ZERO | __GFP_COMP)
#define MID_ORDER_GFP (LOW_ORDER_GFP | __GFP_NOWARN)
#define HIGH_ORDER_GFP (((GFP_HIGHUSER | __GFP_ZERO | __GFP_NOWARN \
| __GFP_NORETRY) & ~__GFP_RECLAIM) \
| __GFP_COMP)
static gfp_t order_flags[] = {HIGH_ORDER_GFP, MID_ORDER_GFP, LOW_ORDER_GFP};
/*
* The selection of the orders used for allocation (1MB, 64K, 4K) is designed
* to match with the sizes often found in IOMMUs. Using order 4 pages instead
* of order 0 pages can significantly improve the performance of many IOMMUs
* by reducing TLB pressure and time spent updating page tables.
*/
static unsigned int orders[] = {8, 4, 0};
#define NUM_ORDERS ARRAY_SIZE(orders)
struct dmabuf_page_pool *pools[NUM_ORDERS];
struct dmabuf_page_pool *dma32_pools[NUM_ORDERS];
static struct sg_table *dup_sg_table(struct sg_table *table)
{
struct sg_table *new_table;
int ret, i;
struct scatterlist *sg, *new_sg;
new_table = kzalloc(sizeof(*new_table), GFP_KERNEL);
if (!new_table)
return ERR_PTR(-ENOMEM);
ret = sg_alloc_table(new_table, table->orig_nents, GFP_KERNEL);
if (ret) {
kfree(new_table);
return ERR_PTR(-ENOMEM);
}
new_sg = new_table->sgl;
for_each_sgtable_sg(table, sg, i) {
sg_set_page(new_sg, sg_page(sg), sg->length, sg->offset);
new_sg = sg_next(new_sg);
}
return new_table;
}
static int system_heap_attach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
struct sg_table *table;
a = kzalloc(sizeof(*a), GFP_KERNEL);
if (!a)
return -ENOMEM;
table = dup_sg_table(&buffer->sg_table);
if (IS_ERR(table)) {
kfree(a);
return -ENOMEM;
}
a->table = table;
a->dev = attachment->dev;
INIT_LIST_HEAD(&a->list);
a->mapped = false;
a->uncached = buffer->uncached;
attachment->priv = a;
mutex_lock(&buffer->lock);
list_add(&a->list, &buffer->attachments);
mutex_unlock(&buffer->lock);
return 0;
}
static void system_heap_detach(struct dma_buf *dmabuf,
struct dma_buf_attachment *attachment)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a = attachment->priv;
mutex_lock(&buffer->lock);
list_del(&a->list);
mutex_unlock(&buffer->lock);
sg_free_table(a->table);
kfree(a->table);
kfree(a);
}
static struct sg_table *system_heap_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
struct sg_table *table = a->table;
int attr = attachment->dma_map_attrs;
int ret;
if (a->uncached)
attr |= DMA_ATTR_SKIP_CPU_SYNC;
ret = dma_map_sgtable(attachment->dev, table, direction, attr);
if (ret)
return ERR_PTR(ret);
a->mapped = true;
return table;
}
static void system_heap_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *table,
enum dma_data_direction direction)
{
struct dma_heap_attachment *a = attachment->priv;
int attr = attachment->dma_map_attrs;
if (a->uncached)
attr |= DMA_ATTR_SKIP_CPU_SYNC;
a->mapped = false;
dma_unmap_sgtable(attachment->dev, table, direction, attr);
}
static int system_heap_dma_buf_begin_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
invalidate_kernel_vmap_range(buffer->vaddr, buffer->len);
if (!buffer->uncached) {
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_cpu(a->dev, a->table, direction);
}
}
mutex_unlock(&buffer->lock);
return 0;
}
static int system_heap_dma_buf_end_cpu_access(struct dma_buf *dmabuf,
enum dma_data_direction direction)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap_attachment *a;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
flush_kernel_vmap_range(buffer->vaddr, buffer->len);
if (!buffer->uncached) {
list_for_each_entry(a, &buffer->attachments, list) {
if (!a->mapped)
continue;
dma_sync_sgtable_for_device(a->dev, a->table, direction);
}
}
mutex_unlock(&buffer->lock);
return 0;
}
static int system_heap_sgl_sync_range(struct device *dev,
struct sg_table *sgt,
unsigned int offset,
unsigned int length,
enum dma_data_direction dir,
bool for_cpu)
{
struct scatterlist *sg;
unsigned int len = 0;
dma_addr_t sg_dma_addr;
int i;
for_each_sgtable_sg(sgt, sg, i) {
unsigned int sg_offset, sg_left, size = 0;
sg_dma_addr = sg_phys(sg);
len += sg->length;
if (len <= offset)
continue;
sg_left = len - offset;
sg_offset = sg->length - sg_left;
size = (length < sg_left) ? length : sg_left;
if (for_cpu)
dma_sync_single_range_for_cpu(dev, sg_dma_addr,
sg_offset, size, dir);
else
dma_sync_single_range_for_device(dev, sg_dma_addr,
sg_offset, size, dir);
offset += size;
length -= size;
if (length == 0)
break;
}
return 0;
}
static int
system_heap_dma_buf_begin_cpu_access_partial(struct dma_buf *dmabuf,
enum dma_data_direction direction,
unsigned int offset,
unsigned int len)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap *heap = buffer->heap;
struct sg_table *table = &buffer->sg_table;
int ret;
if (direction == DMA_TO_DEVICE)
return 0;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
invalidate_kernel_vmap_range(buffer->vaddr, buffer->len);
if (buffer->uncached) {
mutex_unlock(&buffer->lock);
return 0;
}
ret = system_heap_sgl_sync_range(dma_heap_get_dev(heap), table,
offset, len, direction, true);
mutex_unlock(&buffer->lock);
return ret;
}
static int
system_heap_dma_buf_end_cpu_access_partial(struct dma_buf *dmabuf,
enum dma_data_direction direction,
unsigned int offset,
unsigned int len)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct dma_heap *heap = buffer->heap;
struct sg_table *table = &buffer->sg_table;
int ret;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt)
flush_kernel_vmap_range(buffer->vaddr, buffer->len);
if (buffer->uncached) {
mutex_unlock(&buffer->lock);
return 0;
}
ret = system_heap_sgl_sync_range(dma_heap_get_dev(heap), table,
offset, len, direction, false);
mutex_unlock(&buffer->lock);
return ret;
}
static int system_heap_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
{
struct system_heap_buffer *buffer = dmabuf->priv;
struct sg_table *table = &buffer->sg_table;
unsigned long addr = vma->vm_start;
struct sg_page_iter piter;
int ret;
if (buffer->uncached)
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
for_each_sgtable_page(table, &piter, vma->vm_pgoff) {
struct page *page = sg_page_iter_page(&piter);
ret = remap_pfn_range(vma, addr, page_to_pfn(page), PAGE_SIZE,
vma->vm_page_prot);
if (ret)
return ret;
addr += PAGE_SIZE;
if (addr >= vma->vm_end)
return 0;
}
return 0;
}
static void *system_heap_do_vmap(struct system_heap_buffer *buffer)
{
struct sg_table *table = &buffer->sg_table;
int npages = PAGE_ALIGN(buffer->len) / PAGE_SIZE;
struct page **pages = vmalloc(sizeof(struct page *) * npages);
struct page **tmp = pages;
struct sg_page_iter piter;
pgprot_t pgprot = PAGE_KERNEL;
void *vaddr;
if (!pages)
return ERR_PTR(-ENOMEM);
if (buffer->uncached)
pgprot = pgprot_writecombine(PAGE_KERNEL);
for_each_sgtable_page(table, &piter, 0) {
WARN_ON(tmp - pages >= npages);
*tmp++ = sg_page_iter_page(&piter);
}
vaddr = vmap(pages, npages, VM_MAP, pgprot);
vfree(pages);
if (!vaddr)
return ERR_PTR(-ENOMEM);
return vaddr;
}
static void *system_heap_vmap(struct dma_buf *dmabuf)
{
struct system_heap_buffer *buffer = dmabuf->priv;
void *vaddr;
mutex_lock(&buffer->lock);
if (buffer->vmap_cnt) {
buffer->vmap_cnt++;
vaddr = buffer->vaddr;
goto out;
}
vaddr = system_heap_do_vmap(buffer);
if (IS_ERR(vaddr))
goto out;
buffer->vaddr = vaddr;
buffer->vmap_cnt++;
out:
mutex_unlock(&buffer->lock);
return vaddr;
}
static void system_heap_vunmap(struct dma_buf *dmabuf, void *vaddr)
{
struct system_heap_buffer *buffer = dmabuf->priv;
mutex_lock(&buffer->lock);
if (!--buffer->vmap_cnt) {
vunmap(buffer->vaddr);
buffer->vaddr = NULL;
}
mutex_unlock(&buffer->lock);
}
static int system_heap_zero_buffer(struct system_heap_buffer *buffer)
{
struct sg_table *sgt = &buffer->sg_table;
struct sg_page_iter piter;
struct page *p;
void *vaddr;
int ret = 0;
for_each_sgtable_page(sgt, &piter, 0) {
p = sg_page_iter_page(&piter);
vaddr = kmap_atomic(p);
memset(vaddr, 0, PAGE_SIZE);
kunmap_atomic(vaddr);
}
return ret;
}
static void system_heap_buf_free(struct deferred_freelist_item *item,
enum df_reason reason)
{
struct system_heap_buffer *buffer;
struct sg_table *table;
struct scatterlist *sg;
int i, j;
buffer = container_of(item, struct system_heap_buffer, deferred_free);
/* Zero the buffer pages before adding back to the pool */
if (reason == DF_NORMAL)
if (system_heap_zero_buffer(buffer))
reason = DF_UNDER_PRESSURE; // On failure, just free
table = &buffer->sg_table;
for_each_sgtable_sg(table, sg, i) {
struct page *page = sg_page(sg);
if (reason == DF_UNDER_PRESSURE) {
__free_pages(page, compound_order(page));
} else {
for (j = 0; j < NUM_ORDERS; j++) {
if (compound_order(page) == orders[j])
break;
}
dmabuf_page_pool_free(buffer->pools[j], page);
}
}
sg_free_table(table);
kfree(buffer);
}
static void system_heap_dma_buf_release(struct dma_buf *dmabuf)
{
struct system_heap_buffer *buffer = dmabuf->priv;
int npages = PAGE_ALIGN(buffer->len) / PAGE_SIZE;
deferred_free(&buffer->deferred_free, system_heap_buf_free, npages);
}
static const struct dma_buf_ops system_heap_buf_ops = {
.attach = system_heap_attach,
.detach = system_heap_detach,
.map_dma_buf = system_heap_map_dma_buf,
.unmap_dma_buf = system_heap_unmap_dma_buf,
.begin_cpu_access = system_heap_dma_buf_begin_cpu_access,
.end_cpu_access = system_heap_dma_buf_end_cpu_access,
.begin_cpu_access_partial = system_heap_dma_buf_begin_cpu_access_partial,
.end_cpu_access_partial = system_heap_dma_buf_end_cpu_access_partial,
.mmap = system_heap_mmap,
.vmap = system_heap_vmap,
.vunmap = system_heap_vunmap,
.release = system_heap_dma_buf_release,
};
static struct page *system_heap_alloc_largest_available(struct dma_heap *heap,
struct dmabuf_page_pool **pool,
unsigned long size,
unsigned int max_order)
{
struct page *page;
int i;
for (i = 0; i < NUM_ORDERS; i++) {
if (size < (PAGE_SIZE << orders[i]))
continue;
if (max_order < orders[i])
continue;
page = dmabuf_page_pool_alloc(pool[i]);
if (!page)
continue;
return page;
}
return NULL;
}
static struct dma_buf *system_heap_do_allocate(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags,
bool uncached)
{
struct system_heap_buffer *buffer;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
unsigned long size_remaining = len;
unsigned int max_order = orders[0];
struct dma_buf *dmabuf;
struct sg_table *table;
struct scatterlist *sg;
struct list_head pages;
struct page *page, *tmp_page;
int i, ret = -ENOMEM;
struct list_head lists[8];
unsigned int block_index[8] = {0};
unsigned int block_1M = 0;
unsigned int block_64K = 0;
unsigned int maximum;
int j;
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&buffer->attachments);
mutex_init(&buffer->lock);
buffer->heap = heap;
buffer->len = len;
buffer->uncached = uncached;
buffer->pools = strstr(dma_heap_get_name(heap), "dma32") ? dma32_pools : pools;
INIT_LIST_HEAD(&pages);
for (i = 0; i < 8; i++)
INIT_LIST_HEAD(&lists[i]);
i = 0;
while (size_remaining > 0) {
/*
* Avoid trying to allocate memory if the process
* has been killed by SIGKILL
*/
if (fatal_signal_pending(current))
goto free_buffer;
page = system_heap_alloc_largest_available(heap, buffer->pools,
size_remaining,
max_order);
if (!page)
goto free_buffer;
size_remaining -= page_size(page);
max_order = compound_order(page);
if (max_order) {
if (max_order == 8)
block_1M++;
if (max_order == 4)
block_64K++;
list_add_tail(&page->lru, &pages);
} else {
dma_addr_t phys = page_to_phys(page);
unsigned int bit_index = ((phys >> bank_bit_first) & bank_bit_mask) & 0x7;
list_add_tail(&page->lru, &lists[bit_index]);
block_index[bit_index]++;
}
i++;
}
table = &buffer->sg_table;
if (sg_alloc_table(table, i, GFP_KERNEL))
goto free_buffer;
maximum = block_index[0];
for (i = 1; i < 8; i++)
maximum = max(maximum, block_index[i]);
sg = table->sgl;
list_for_each_entry_safe(page, tmp_page, &pages, lru) {
sg_set_page(sg, page, page_size(page), 0);
sg = sg_next(sg);
list_del(&page->lru);
}
for (i = 0; i < maximum; i++) {
for (j = 0; j < 8; j++) {
if (!list_empty(&lists[j])) {
page = list_first_entry(&lists[j], struct page, lru);
sg_set_page(sg, page, PAGE_SIZE, 0);
sg = sg_next(sg);
list_del(&page->lru);
}
}
}
/* create the dmabuf */
exp_info.exp_name = dma_heap_get_name(heap);
exp_info.ops = &system_heap_buf_ops;
exp_info.size = buffer->len;
exp_info.flags = fd_flags;
exp_info.priv = buffer;
dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf)) {
ret = PTR_ERR(dmabuf);
goto free_pages;
}
/*
* For uncached buffers, we need to initially flush cpu cache, since
* the __GFP_ZERO on the allocation means the zeroing was done by the
* cpu and thus it is likely cached. Map (and implicitly flush) and
* unmap it now so we don't get corruption later on.
*/
if (buffer->uncached) {
dma_map_sgtable(dma_heap_get_dev(heap), table, DMA_BIDIRECTIONAL, 0);
dma_unmap_sgtable(dma_heap_get_dev(heap), table, DMA_BIDIRECTIONAL, 0);
}
return dmabuf;
free_pages:
for_each_sgtable_sg(table, sg, i) {
struct page *p = sg_page(sg);
__free_pages(p, compound_order(p));
}
sg_free_table(table);
free_buffer:
list_for_each_entry_safe(page, tmp_page, &pages, lru)
__free_pages(page, compound_order(page));
for (i = 0; i < 8; i++) {
list_for_each_entry_safe(page, tmp_page, &lists[i], lru)
__free_pages(page, compound_order(page));
}
kfree(buffer);
return ERR_PTR(ret);
}
static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags)
{
return system_heap_do_allocate(heap, len, fd_flags, heap_flags, false);
}
static long system_get_pool_size(struct dma_heap *heap)
{
int i;
long num_pages = 0;
struct dmabuf_page_pool **pool;
pool = strstr(dma_heap_get_name(heap), "dma32") ? dma32_pools : pools;
for (i = 0; i < NUM_ORDERS; i++, pool++) {
num_pages += ((*pool)->count[POOL_LOWPAGE] +
(*pool)->count[POOL_HIGHPAGE]) << (*pool)->order;
}
return num_pages << PAGE_SHIFT;
}
static const struct dma_heap_ops system_heap_ops = {
.allocate = system_heap_allocate,
.get_pool_size = system_get_pool_size,
};
static struct dma_buf *system_uncached_heap_allocate(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags)
{
return system_heap_do_allocate(heap, len, fd_flags, heap_flags, true);
}
/* Dummy function to be used until we can call coerce_mask_and_coherent */
static struct dma_buf *system_uncached_heap_not_initialized(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags)
{
return ERR_PTR(-EBUSY);
}
static struct dma_heap_ops system_uncached_heap_ops = {
/* After system_heap_create is complete, we will swap this */
.allocate = system_uncached_heap_not_initialized,
};
static int set_heap_dev_dma(struct device *heap_dev)
{
int err = 0;
if (!heap_dev)
return -EINVAL;
dma_coerce_mask_and_coherent(heap_dev, DMA_BIT_MASK(64));
if (!heap_dev->dma_parms) {
heap_dev->dma_parms = devm_kzalloc(heap_dev,
sizeof(*heap_dev->dma_parms),
GFP_KERNEL);
if (!heap_dev->dma_parms)
return -ENOMEM;
err = dma_set_max_seg_size(heap_dev, (unsigned int)DMA_BIT_MASK(64));
if (err) {
devm_kfree(heap_dev, heap_dev->dma_parms);
dev_err(heap_dev, "Failed to set DMA segment size, err:%d\n", err);
return err;
}
}
return 0;
}
static int system_heap_create(void)
{
struct dma_heap_export_info exp_info;
int i, err = 0;
struct dram_addrmap_info *ddr_map_info;
/*
* Since swiotlb has memory size limitation, this will calculate
* the maximum size locally.
*
* Once swiotlb_max_segment() return not '0', means that the totalram size
* is larger than 4GiB and swiotlb is not force mode, in this case, system
* heap should limit largest allocation.
*
* FIX: fix the orders[] as a workaround.
*/
if (swiotlb_max_segment()) {
unsigned int max_size = (1 << IO_TLB_SHIFT) * IO_TLB_SEGSIZE;
int max_order = MAX_ORDER;
int i;
max_size = max_t(unsigned int, max_size, PAGE_SIZE) >> PAGE_SHIFT;
max_order = min(max_order, ilog2(max_size));
for (i = 0; i < NUM_ORDERS; i++) {
if (max_order < orders[i])
orders[i] = max_order;
pr_info("system_heap: orders[%d] = %u\n", i, orders[i]);
}
}
for (i = 0; i < NUM_ORDERS; i++) {
pools[i] = dmabuf_page_pool_create(order_flags[i], orders[i]);
if (!pools[i]) {
int j;
pr_err("%s: page pool creation failed!\n", __func__);
for (j = 0; j < i; j++)
dmabuf_page_pool_destroy(pools[j]);
return -ENOMEM;
}
}
for (i = 0; i < NUM_ORDERS; i++) {
dma32_pools[i] = dmabuf_page_pool_create(order_flags[i] | GFP_DMA32, orders[i]);
if (!dma32_pools[i]) {
int j;
pr_err("%s: page dma32 pool creation failed!\n", __func__);
for (j = 0; j < i; j++)
dmabuf_page_pool_destroy(dma32_pools[j]);
goto err_dma32_pool;
}
}
exp_info.name = "system";
exp_info.ops = &system_heap_ops;
exp_info.priv = NULL;
sys_heap = dma_heap_add(&exp_info);
if (IS_ERR(sys_heap))
return PTR_ERR(sys_heap);
exp_info.name = "system-dma32";
exp_info.ops = &system_heap_ops;
exp_info.priv = NULL;
sys_dma32_heap = dma_heap_add(&exp_info);
if (IS_ERR(sys_dma32_heap))
return PTR_ERR(sys_dma32_heap);
exp_info.name = "system-uncached";
exp_info.ops = &system_uncached_heap_ops;
exp_info.priv = NULL;
sys_uncached_heap = dma_heap_add(&exp_info);
if (IS_ERR(sys_uncached_heap))
return PTR_ERR(sys_uncached_heap);
err = set_heap_dev_dma(dma_heap_get_dev(sys_uncached_heap));
if (err)
return err;
exp_info.name = "system-uncached-dma32";
exp_info.ops = &system_uncached_heap_ops;
exp_info.priv = NULL;
sys_uncached_dma32_heap = dma_heap_add(&exp_info);
if (IS_ERR(sys_uncached_dma32_heap))
return PTR_ERR(sys_uncached_dma32_heap);
err = set_heap_dev_dma(dma_heap_get_dev(sys_uncached_dma32_heap));
if (err)
return err;
dma_coerce_mask_and_coherent(dma_heap_get_dev(sys_uncached_dma32_heap), DMA_BIT_MASK(32));
mb(); /* make sure we only set allocate after dma_mask is set */
system_uncached_heap_ops.allocate = system_uncached_heap_allocate;
ddr_map_info = sip_smc_get_dram_map();
if (ddr_map_info) {
bank_bit_first = ddr_map_info->bank_bit_first;
bank_bit_mask = ddr_map_info->bank_bit_mask;
}
return 0;
err_dma32_pool:
for (i = 0; i < NUM_ORDERS; i++)
dmabuf_page_pool_destroy(pools[i]);
return -ENOMEM;
}
module_init(system_heap_create);
MODULE_LICENSE("GPL v2");

128
include/linux/dma-heap.h Normal file
View File

@ -0,0 +1,128 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* DMABUF Heaps Allocation Infrastructure
*
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2019 Linaro Ltd.
*/
#ifndef _DMA_HEAPS_H
#define _DMA_HEAPS_H
#include <linux/cdev.h>
#include <linux/types.h>
#include <uapi/linux/dma-heap.h>
struct dma_heap;
/**
* struct dma_heap_ops - ops to operate on a given heap
* @allocate: allocate dmabuf and return struct dma_buf ptr
* @get_pool_size: if heap maintains memory pools, get pool size in bytes
*
* allocate returns dmabuf on success, ERR_PTR(-errno) on error.
*/
struct dma_heap_ops {
struct dma_buf *(*allocate)(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags);
long (*get_pool_size)(struct dma_heap *heap);
#if IS_ENABLED(CONFIG_NO_GKI)
int (*get_phys)(struct dma_heap *heap, struct dma_heap_phys_data *phys);
#endif
};
/**
* struct dma_heap_export_info - information needed to export a new dmabuf heap
* @name: used for debugging/device-node name
* @ops: ops struct for this heap
* @priv: heap exporter private data
*
* Information needed to export a new dmabuf heap.
*/
struct dma_heap_export_info {
const char *name;
const struct dma_heap_ops *ops;
void *priv;
};
/**
* dma_heap_get_drvdata() - get per-heap driver data
* @heap: DMA-Heap to retrieve private data for
*
* Returns:
* The per-heap data for the heap.
*/
void *dma_heap_get_drvdata(struct dma_heap *heap);
/**
* dma_heap_get_dev() - get device struct for the heap
* @heap: DMA-Heap to retrieve device struct from
*
* Returns:
* The device struct for the heap.
*/
struct device *dma_heap_get_dev(struct dma_heap *heap);
/**
* dma_heap_get_name() - get heap name
* @heap: DMA-Heap to retrieve private data for
*
* Returns:
* The char* for the heap name.
*/
const char *dma_heap_get_name(struct dma_heap *heap);
/**
* dma_heap_add - adds a heap to dmabuf heaps
* @exp_info: information needed to register this heap
*/
struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info);
/**
* dma_heap_put - drops a reference to a dmabuf heaps, potentially freeing it
* @heap: heap pointer
*/
void dma_heap_put(struct dma_heap *heap);
/**
* dma_heap_find - Returns the registered dma_heap with the specified name
* @name: Name of the heap to find
*
* NOTE: dma_heaps returned from this function MUST be released
* using dma_heap_put() when the user is done.
*/
struct dma_heap *dma_heap_find(const char *name);
/**
* dma_heap_buffer_alloc - Allocate dma-buf from a dma_heap
* @heap: dma_heap to allocate from
* @len: size to allocate
* @fd_flags: flags to set on returned dma-buf fd
* @heap_flags: flags to pass to the dma heap
*
* This is for internal dma-buf allocations only.
*/
struct dma_buf *dma_heap_buffer_alloc(struct dma_heap *heap, size_t len,
unsigned int fd_flags,
unsigned int heap_flags);
/** dma_heap_buffer_free - Free dma_buf allocated by dma_heap_buffer_alloc
* @dma_buf: dma_buf to free
*
* This is really only a simple wrapper to dma_buf_put()
*/
void dma_heap_buffer_free(struct dma_buf *);
/**
* dma_heap_bufferfd_alloc - Allocate dma-buf fd from a dma_heap
* @heap: dma_heap to allocate from
* @len: size to allocate
* @fd_flags: flags to set on returned dma-buf fd
* @heap_flags: flags to pass to the dma heap
*/
int dma_heap_bufferfd_alloc(struct dma_heap *heap, size_t len,
unsigned int fd_flags,
unsigned int heap_flags);
#endif /* _DMA_HEAPS_H */

View File

@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* DMABUF Heaps Userspace API
*
* Copyright (C) 2011 Google, Inc.
* Copyright (C) 2019 Linaro Ltd.
*/
#ifndef _UAPI_LINUX_DMABUF_POOL_H
#define _UAPI_LINUX_DMABUF_POOL_H
#include <linux/ioctl.h>
#include <linux/types.h>
/**
* DOC: DMABUF Heaps Userspace API
*/
/* Valid FD_FLAGS are O_CLOEXEC, O_RDONLY, O_WRONLY, O_RDWR */
#define DMA_HEAP_VALID_FD_FLAGS (O_CLOEXEC | O_ACCMODE)
/* Currently no heap flags */
#define DMA_HEAP_VALID_HEAP_FLAGS (0)
/**
* struct dma_heap_allocation_data - metadata passed from userspace for
* allocations
* @len: size of the allocation
* @fd: will be populated with a fd which provides the
* handle to the allocated dma-buf
* @fd_flags: file descriptor flags used when allocating
* @heap_flags: flags passed to heap
*
* Provided by userspace as an argument to the ioctl
*/
struct dma_heap_allocation_data {
__u64 len;
__u32 fd;
__u32 fd_flags;
__u64 heap_flags;
};
struct dma_heap_phys_data {
__u64 paddr;
__u32 fd;
};
#define DMA_HEAP_IOC_MAGIC 'H'
/**
* DOC: DMA_HEAP_IOCTL_ALLOC - allocate memory from pool
*
* Takes a dma_heap_allocation_data struct and returns it with the fd field
* populated with the dmabuf handle of the allocation.
*/
#define DMA_HEAP_IOCTL_ALLOC _IOWR(DMA_HEAP_IOC_MAGIC, 0x0,\
struct dma_heap_allocation_data)
#define DMA_HEAP_IOCTL_GET_PHYS _IOWR(DMA_HEAP_IOC_MAGIC, 0x1, \
struct dma_heap_phys_data)
#endif /* _UAPI_LINUX_DMABUF_POOL_H */