blob: 3cda84c6dbb317d06f7ce30de97dd06e696b908a [file] [log] [blame]
/*
* drivers/amlogic/media/common/uvm/meson_uvm_allocator.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/rbtree.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/dma-buf.h>
#include <linux/pagemap.h>
#include <ion/ion.h>
#include <ion/ion_priv.h>
#include <linux/meson_ion.h>
#include "meson_uvm_allocator.h"
#include "meson_uvm_conf.h"
static struct mua_device *mdev;
static int enable_screencap;
module_param_named(enable_screencap, enable_screencap, int, 0664);
static int mua_debug_level;
module_param(mua_debug_level, int, 0644);
#define MUA_PRINTK(level, fmt, arg...) \
do { \
if (mua_debug_level >= (level)) \
pr_info("MUA: " fmt, ## arg); \
} while (0)
static void mua_handle_free(struct uvm_buf_obj *obj)
{
struct mua_buffer *buffer;
buffer = container_of(obj, struct mua_buffer, base);
MUA_PRINTK(1, "%s get ion_handle, %px\n", __func__, obj);
if (buffer->handle)
ion_free(mdev->client, buffer->handle);
kfree(buffer);
}
static int meson_uvm_fill_pattern(struct mua_buffer *buffer,
struct dma_buf *dmabuf, void *vaddr)
{
struct v4l_data_t val_data;
memset(&val_data, 0, sizeof(val_data));
val_data.dst_addr = vaddr;
val_data.byte_stride = buffer->byte_stride;
val_data.width = buffer->width;
val_data.height = buffer->height;
val_data.phy_addr[0] = buffer->paddr;
MUA_PRINTK(1, "%s. width=%d height=%d byte_stride=%d\n",
__func__, buffer->width,
buffer->height, buffer->byte_stride);
v4lvideo_data_copy(&val_data, dmabuf);
vunmap(vaddr);
return 0;
}
static int mua_process_gpu_realloc(struct dma_buf *dmabuf,
struct uvm_buf_obj *obj, int scalar)
{
int i, j, num_pages;
struct ion_handle *handle;
struct uvm_alloc_info info;
struct mua_buffer *buffer;
struct page **tmp;
struct page **page_array;
pgprot_t pgprot;
void *vaddr;
phys_addr_t pat;
size_t len;
struct sg_table *src_sgt = NULL;
struct scatterlist *sg = NULL;
buffer = container_of(obj, struct mua_buffer, base);
MUA_PRINTK(1, "%s. buf_scalar=%d WxH: %dx%d\n",
__func__, scalar, buffer->width, buffer->height);
memset(&info, 0, sizeof(info));
if (!enable_screencap && current->tgid == mdev->pid &&
buffer->commit_display) {
MUA_PRINTK(0, "screen cap should not access the uvm buffer.\n");
return -ENODEV;
}
dmabuf->size = buffer->size * scalar * scalar;
MUA_PRINTK(1, "buffer->size:%zu realloc dmabuf->size=%zu\n",
buffer->size, dmabuf->size);
if (1 /*!buffer->handle*/) {
handle = ion_alloc(mdev->client, dmabuf->size,
0, (1 << ION_HEAP_TYPE_CUSTOM), 0);
if (IS_ERR(handle)) {
MUA_PRINTK(0, "%s: ion_alloc fail.\n", __func__);
return -ENOMEM;
}
ion_phys(mdev->client, handle, (ion_phys_addr_t *)&pat, &len);
buffer->paddr = pat;
buffer->handle = handle;
buffer->sg_table = handle->buffer->sg_table;
info.sgt = handle->buffer->sg_table;
dmabuf_bind_uvm_delay_alloc(dmabuf, &info);
}
//start to do vmap
if (!buffer->sg_table) {
MUA_PRINTK(0, "none uvm buffer allocated.\n");
return -ENODEV;
}
src_sgt = buffer->sg_table;
num_pages = PAGE_ALIGN(dmabuf->size) / PAGE_SIZE;
tmp = vmalloc(sizeof(struct page *) * num_pages);
page_array = tmp;
pgprot = pgprot_writecombine(PAGE_KERNEL);
for_each_sg(src_sgt->sgl, sg, src_sgt->nents, i) {
int npages_this_entry =
PAGE_ALIGN(sg->length) / PAGE_SIZE;
struct page *page = sg_page(sg);
for (j = 0; j < npages_this_entry; j++)
*(tmp++) = page++;
}
vaddr = vmap(page_array, num_pages, VM_MAP, pgprot);
if (!vaddr) {
MUA_PRINTK(0, "vmap fail, size: %d\n",
num_pages << PAGE_SHIFT);
vfree(page_array);
return -ENOMEM;
}
vfree(page_array);
MUA_PRINTK(1, "buffer vaddr: %p.\n", vaddr);
//start to filldata
meson_uvm_fill_pattern(buffer, dmabuf, vaddr);
return 0;
}
static int mua_process_delay_alloc(struct dma_buf *dmabuf,
struct uvm_buf_obj *obj)
{
int i, j, num_pages;
struct ion_handle *handle;
struct uvm_alloc_info info;
struct mua_buffer *buffer;
struct page **tmp;
struct page **page_array;
pgprot_t pgprot;
void *vaddr;
phys_addr_t pat;
size_t len;
struct sg_table *src_sgt = NULL;
struct scatterlist *sg = NULL;
buffer = container_of(obj, struct mua_buffer, base);
memset(&info, 0, sizeof(info));
MUA_PRINTK(1, "%p, %d.\n", __func__, __LINE__);
if (!enable_screencap && current->tgid == mdev->pid &&
buffer->commit_display) {
MUA_PRINTK(0, "screen cap should not access the uvm buffer.\n");
return -ENODEV;
}
if (!buffer->handle) {
handle = ion_alloc(mdev->client, dmabuf->size, 0,
(1 << ION_HEAP_TYPE_CUSTOM), 0);
if (IS_ERR(handle)) {
MUA_PRINTK(0, "%s: ion_alloc fail.\n", __func__);
return -ENOMEM;
}
ion_phys(mdev->client, handle, (ion_phys_addr_t *)&pat, &len);
buffer->paddr = pat;
buffer->handle = handle;
buffer->sg_table = handle->buffer->sg_table;
info.sgt = handle->buffer->sg_table;
dmabuf_bind_uvm_delay_alloc(dmabuf, &info);
}
//start to do vmap
if (!buffer->sg_table) {
MUA_PRINTK(0, "none uvm buffer allocated.\n");
return -ENODEV;
}
src_sgt = buffer->sg_table;
num_pages = PAGE_ALIGN(buffer->size) / PAGE_SIZE;
tmp = vmalloc(sizeof(struct page *) * num_pages);
page_array = tmp;
pgprot = pgprot_writecombine(PAGE_KERNEL);
for_each_sg(src_sgt->sgl, sg, src_sgt->nents, i) {
int npages_this_entry =
PAGE_ALIGN(sg->length) / PAGE_SIZE;
struct page *page = sg_page(sg);
for (j = 0; j < npages_this_entry; j++)
*(tmp++) = page++;
}
vaddr = vmap(page_array, num_pages, VM_MAP, pgprot);
if (!vaddr) {
MUA_PRINTK(0, "vmap fail, size: %d\n",
num_pages << PAGE_SHIFT);
vfree(page_array);
return -ENOMEM;
}
vfree(page_array);
MUA_PRINTK(1, "buffer vaddr: %p.\n", vaddr);
//start to filldata
meson_uvm_fill_pattern(buffer, dmabuf, vaddr);
return 0;
}
static int mua_handle_alloc(struct dma_buf *dmabuf,
struct uvm_alloc_data *data, int alloc_buf_size)
{
struct mua_buffer *buffer;
struct uvm_alloc_info info;
struct ion_handle *handle;
memset(&info, 0, sizeof(info));
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
buffer->size = alloc_buf_size;
buffer->dev = mdev;
buffer->byte_stride = data->byte_stride;
buffer->width = data->width;
buffer->height = data->height;
if (data->flags & MUA_IMM_ALLOC) {
handle = ion_alloc(mdev->client, alloc_buf_size, 0,
(1 << ION_HEAP_TYPE_CUSTOM), 0);
if (IS_ERR(handle)) {
MUA_PRINTK(0, "%s: ion_alloc fail.\n", __func__);
kfree(buffer);
return -ENOMEM;
}
buffer->handle = handle;
info.sgt = handle->buffer->sg_table;
info.obj = &buffer->base;
info.flags = data->flags;
info.size = alloc_buf_size;
info.scalar = data->scalar;
info.gpu_realloc = mua_process_gpu_realloc;
info.free = mua_handle_free;
MUA_PRINTK(1, "UVM FLAGS is MUA_IMM_ALLOC, %px\n", info.obj);
} else if (data->flags & MUA_DELAY_ALLOC) {
info.size = data->size;
info.obj = &buffer->base;
info.flags = data->flags;
info.delay_alloc = mua_process_delay_alloc;
info.free = mua_handle_free;
MUA_PRINTK(1, "UVM FLAGS is MUA_DELAY_ALLOC, %px\n", info.obj);
} else {
MUA_PRINTK(0, "unsupported MUA FLAGS.\n");
kfree(buffer);
return -EINVAL;
}
dmabuf_bind_uvm_alloc(dmabuf, &info);
return 0;
}
static int mua_set_commit_display(int fd, int commit_display)
{
struct dma_buf *dmabuf;
struct uvm_buf_obj *obj;
struct mua_buffer *buffer;
dmabuf = dma_buf_get(fd);
if (IS_ERR_OR_NULL(dmabuf)) {
MUA_PRINTK(0, "invalid dmabuf fd\n");
return -EINVAL;
}
obj = dmabuf_get_uvm_buf_obj(dmabuf);
buffer = container_of(obj, struct mua_buffer, base);
buffer->commit_display = commit_display;
dma_buf_put(dmabuf);
return 0;
}
static long mua_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct mua_device *md;
union uvm_ioctl_arg data;
struct dma_buf *dmabuf;
int pid;
int ret = 0;
int fd = 0;
int alloc_buf_size = 0;
md = file->private_data;
if (_IOC_SIZE(cmd) > sizeof(data))
return -EINVAL;
if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd)))
return -EFAULT;
switch (cmd) {
case UVM_IOC_ALLOC:
MUA_PRINTK(1, "%s. original buf size:%d width:%d height:%d\n",
__func__, data.alloc_data.size,
data.alloc_data.width,
data.alloc_data.height);
data.alloc_data.size = PAGE_ALIGN(data.alloc_data.size);
data.alloc_data.scaled_buf_size =
PAGE_ALIGN(data.alloc_data.scaled_buf_size);
if (data.alloc_data.scalar > 1)
alloc_buf_size = data.alloc_data.scaled_buf_size;
else
alloc_buf_size = data.alloc_data.size;
MUA_PRINTK(1, "%s. buf_scalar=%d size=%d\n",
__func__, data.alloc_data.scalar,
data.alloc_data.scaled_buf_size);
dmabuf = uvm_alloc_dmabuf(alloc_buf_size,
data.alloc_data.align,
data.alloc_data.flags);
if (IS_ERR(dmabuf))
return -ENOMEM;
ret = mua_handle_alloc(dmabuf, &data.alloc_data,
alloc_buf_size);
if (ret < 0) {
dma_buf_put(dmabuf);
return -ENOMEM;
}
fd = dma_buf_fd(dmabuf, O_CLOEXEC);
if (fd < 0) {
dma_buf_put(dmabuf);
return -ENOMEM;
}
data.alloc_data.fd = fd;
if (copy_to_user((void __user *)arg, &data, _IOC_SIZE(cmd)))
return -EFAULT;
break;
case UVM_IOC_SET_PID:
pid = data.pid_data.pid;
if (pid < 0)
return -ENOMEM;
md->pid = pid;
break;
case UVM_IOC_SET_FD:
fd = data.fd_data.fd;
ret = mua_set_commit_display(fd, data.fd_data.commit_display);
if (ret < 0) {
MUA_PRINTK(0, "invalid dmabuf fd.\n");
return -EINVAL;
}
break;
default:
return -ENOTTY;
}
return ret;
}
static int mua_open(struct inode *inode, struct file *file)
{
struct miscdevice *miscdev = file->private_data;
struct mua_device *md = container_of(miscdev, struct mua_device, dev);
MUA_PRINTK(1, "%s: %d\n", __func__, __LINE__);
file->private_data = md;
return 0;
}
static int mua_release(struct inode *inode, struct file *file)
{
return 0;
}
static const struct file_operations mua_fops = {
.owner = THIS_MODULE,
.open = mua_open,
.release = mua_release,
.unlocked_ioctl = mua_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mua_ioctl,
#endif
};
static int mua_probe(struct platform_device *pdev)
{
mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
if (!mdev)
return -ENOMEM;
mdev->dev.minor = MISC_DYNAMIC_MINOR;
mdev->dev.name = "uvm";
mdev->dev.fops = &mua_fops;
mutex_init(&mdev->buffer_lock);
mdev->client = meson_ion_client_create(-1, "meson-uvm-alloactor");
if (!mdev->client) {
pr_err("ion client create error.\n");
goto err;
}
return misc_register(&mdev->dev);
err:
kfree(mdev);
return -ENOMEM;
}
static int mua_remove(struct platform_device *pdev)
{
misc_deregister(&mdev->dev);
return 0;
}
static const struct of_device_id mua_match[] = {
{.compatible = "amlogic, meson_uvm",},
{},
};
static struct platform_driver mua_driver = {
.driver = {
.name = "meson_uvm_allocator",
.owner = THIS_MODULE,
.of_match_table = mua_match,
},
.probe = mua_probe,
.remove = mua_remove,
};
static int __init mua_init(void)
{
if (use_uvm) {
pr_info("meson_uvm_allocator call init\n");
return platform_driver_register(&mua_driver);
}
return 0;
}
static void __exit mua_exit(void)
{
if (use_uvm)
platform_driver_unregister(&mua_driver);
}
module_init(mua_init);
module_exit(mua_exit);
MODULE_DESCRIPTION("AMLOGIC uvm dmabuf allocator device driver");
MODULE_LICENSE("GPL");