blob: 9e93008a181815ff163aa73c6b0d8b17f7d27f1f [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/dma-buf.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/genalloc.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/compat.h>
#include <linux/amlogic/tee.h>
#include <linux/amlogic/media/codec_mm/codec_mm.h>
#include <linux/amlogic/media/codec_mm/dmabuf_manage.h>
#include <linux/amlogic/media/codec_mm/configs.h>
#include <uapi/linux/dvb/aml_dmx_ext.h>
#ifdef CONFIG_AMLOGIC_DVB_COMPAT
#include <media/aml_demux_ext.h>
#endif
#include "dmxdev.h"
#include "demux.h"
#if IS_BUILTIN(CONFIG_AMLOGIC_MEDIA_MODULE)
#include <linux/amlogic/aml_cma.h>
#endif
static int dmabuf_manage_debug = 1;
module_param(dmabuf_manage_debug, int, 0644);
static u32 secure_heap_version = SECURE_HEAP_DYNAMIC_ALLOC_VERSION;
static u32 secure_pool_max_block_count = 8;
static u32 secure_vdec_def_size_bytes;
static u32 secure_hd_pool_size_mb = 4;
static u32 secure_uhd_pool_size_mb = 12;
static u32 secure_fuhd_pool_size_mb = 32;
#define DEVICE_NAME "secmem"
#define CLASS_NAME "dmabuf_manage"
#define CONFIG_PATH "media.dmabuf_manage"
#define CONFIG_PREFIX "media"
#define dprintk(level, fmt, arg...) \
do { \
if (dmabuf_manage_debug >= (level)) \
pr_info(CLASS_NAME ": %s: " fmt, __func__, ## arg);\
} while (0)
#define pr_dbg(fmt, args ...) dprintk(6, fmt, ## args)
#define pr_error(fmt, args ...) dprintk(1, fmt, ## args)
#define pr_inf(fmt, args ...) dprintk(8, fmt, ## args)
#define pr_enter() pr_inf("enter")
#define POOL_SIZE_MB (1024 * 1024)
#define DMABUF_MANAGE_POOL_ALIGN_2N 20
#define DMABUF_MANAGE_POOL_SIZE_MB 32
struct kdmabuf_attachment {
struct sg_table sgt;
enum dma_data_direction dma_dir;
};
struct block_node {
struct list_head node;
u32 addr;
u32 size;
};
struct secure_pool_info {
struct list_head node;
struct list_head block_node;
u32 version;
u32 id_high;
u32 id_low;
u32 protect_handle;
struct gen_pool *gen_pool;
struct codec_mm_s *mms;
phys_addr_t pool_addr;
u32 pool_size;
u32 block_size;
u32 max_frame_size;
u32 channel_register;
};
typedef int (*decode_info)(struct dmx_demux *demux, void *info);
static long dmabuf_manage_release_channel(u32 id_high, u32 id_low);
struct dmx_filter_info {
struct list_head list;
__u32 token;
__u32 filter_fd;
struct dmx_demux *demux;
decode_info decode_info_func;
};
static struct list_head pool_list;
static struct list_head dmx_filter_list;
static int dev_no;
static struct device *dmabuf_manage_dev;
static DEFINE_MUTEX(g_secure_pool_mutex);
static int dmabuf_manage_attach(struct dma_buf *dbuf, struct dma_buf_attachment *attachment)
{
struct kdmabuf_attachment *attach;
struct dmabuf_manage_block *block = dbuf->priv;
struct sg_table *sgt;
struct page *page;
phys_addr_t phys = block->paddr;
int ret;
int sgnum = 1;
struct dmabuf_dmx_sec_es_data *es = NULL;
int len = 0;
pr_enter();
attach = (struct kdmabuf_attachment *)
kzalloc(sizeof(*attach), GFP_KERNEL);
if (!attach) {
pr_error("kzalloc failed\n");
goto error;
}
if (block->type == DMA_BUF_TYPE_DMX_ES) {
es = (struct dmabuf_dmx_sec_es_data *)block->priv;
if (es->data_end < es->data_start)
sgnum = 2;
}
sgt = &attach->sgt;
ret = sg_alloc_table(sgt, sgnum, GFP_KERNEL);
if (ret) {
pr_error("No memory for sgtable");
goto error_alloc;
}
if (sgnum == 2) {
len = PAGE_ALIGN(es->buf_end - es->data_start);
if (block->paddr != es->data_start) {
pr_error("Invalid buffer info %x %x",
block->paddr, es->data_start);
goto error_attach;
}
page = phys_to_page(phys);
sg_set_page(sgt->sgl, page, len, 0);
len = PAGE_ALIGN(es->data_end - es->buf_start);
page = phys_to_page(es->buf_start);
sg_set_page(sg_next(sgt->sgl), page, len, 0);
} else {
page = phys_to_page(phys);
sg_set_page(sgt->sgl, page, PAGE_ALIGN(block->size), 0);
}
attach->dma_dir = DMA_NONE;
attachment->priv = attach;
return 0;
error_attach:
sg_free_table(sgt);
error_alloc:
kfree(attach);
error:
return 0;
}
static void dmabuf_manage_detach(struct dma_buf *dbuf,
struct dma_buf_attachment *attachment)
{
struct kdmabuf_attachment *attach = attachment->priv;
struct sg_table *sgt;
pr_enter();
if (!attach)
return;
sgt = &attach->sgt;
sg_free_table(sgt);
kfree(attach);
attachment->priv = NULL;
}
static struct sg_table *dmabuf_manage_map_dma_buf(struct dma_buf_attachment *attachment,
enum dma_data_direction dma_dir)
{
struct kdmabuf_attachment *attach = attachment->priv;
struct dmabuf_manage_block *block = attachment->dmabuf->priv;
struct mutex *lock = &attachment->dmabuf->lock;
struct sg_table *sgt;
pr_enter();
mutex_lock(lock);
sgt = &attach->sgt;
if (attach->dma_dir == dma_dir) {
mutex_unlock(lock);
return sgt;
}
sgt->sgl->dma_address = block->paddr;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
sgt->sgl->dma_length = PAGE_ALIGN(block->size);
#else
sgt->sgl->length = PAGE_ALIGN(block->size);
#endif
pr_dbg("nents %d, %x, %d, %d\n", sgt->nents, block->paddr,
sg_dma_len(sgt->sgl), block->size);
attach->dma_dir = dma_dir;
mutex_unlock(lock);
return sgt;
}
static void dmabuf_manage_unmap_dma_buf(struct dma_buf_attachment *attachment,
struct sg_table *sgt,
enum dma_data_direction dma_dir)
{
pr_enter();
}
static void dmabuf_manage_buf_release(struct dma_buf *dbuf)
{
struct dmabuf_manage_block *block = NULL;
struct dmabuf_dmx_sec_es_data *es = NULL;
struct dmx_filter_info *node = NULL;
struct secure_vdec_channel *channel = NULL;
struct list_head *pos = NULL, *tmp = NULL;
int found = 0;
struct decoder_mem_info rp_info;
pr_enter();
block = (struct dmabuf_manage_block *)dbuf->priv;
if (block && block->priv && block->type == DMA_BUF_TYPE_DMX_ES) {
es = (struct dmabuf_dmx_sec_es_data *)block->priv;
list_for_each_safe(pos, tmp, &dmx_filter_list) {
node = list_entry(pos, struct dmx_filter_info, list);
if (node && node->token == es->token) {
found = 1;
break;
}
}
if (found) {
if (es->buf_rp == 0)
es->buf_rp = es->data_end;
if (es->buf_rp >= es->buf_start && es->buf_rp <= es->buf_end &&
node->demux && node->decode_info_func) {
rp_info.rp_phy = es->buf_rp;
node->decode_info_func(node->demux, &rp_info);
}
}
} else if (block && block->type == DMA_BUF_TYPE_DMABUF) {
if (block->flags & DMABUF_ALLOC_FROM_CMA)
codec_mm_free_for_dma("dmabuf", block->paddr);
} else if (block && block->priv &&
block->type == DMA_BUF_TYPE_SECURE_VDEC) {
channel = (struct secure_vdec_channel *)block->priv;
dmabuf_manage_release_channel(channel->id_high, channel->id_low);
}
if (block) {
pr_dbg("dma release handle:%x\n", block->handle);
kfree(block->priv);
kfree(block);
}
}
static int dmabuf_manage_mmap(struct dma_buf *dbuf, struct vm_area_struct *vma)
{
struct dmabuf_manage_block *block;
struct dmabuf_dmx_sec_es_data *es;
unsigned long addr = vma->vm_start;
int len = 0;
int ret = -EFAULT;
pr_enter();
block = (struct dmabuf_manage_block *)dbuf->priv;
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
if (block->type == DMA_BUF_TYPE_DMX_ES) {
es = (struct dmabuf_dmx_sec_es_data *)block->priv;
if (es->data_end >= es->data_start) {
len = PAGE_ALIGN(es->data_end - es->data_start);
if (block->paddr != es->data_start) {
pr_error("Invalid buffer info %x %x",
block->paddr, es->data_start);
goto error;
}
ret = remap_pfn_range(vma, addr,
page_to_pfn(phys_to_page(block->paddr)),
len, vma->vm_page_prot);
} else {
len = PAGE_ALIGN(es->buf_end - es->data_start);
if (block->paddr != es->data_start) {
pr_error("Invalid buffer info %x %x",
block->paddr, es->data_start);
goto error;
}
ret = remap_pfn_range(vma, addr,
page_to_pfn(phys_to_page(block->paddr)),
len, vma->vm_page_prot);
if (ret) {
pr_error("remap failed %d", ret);
goto error;
}
addr += len;
if (addr >= vma->vm_end)
return ret;
len = PAGE_ALIGN(es->data_end - es->buf_start);
ret = remap_pfn_range(vma, addr,
page_to_pfn(phys_to_page(es->buf_start)),
len, vma->vm_page_prot);
}
} else {
ret = remap_pfn_range(vma, vma->vm_start, page_to_pfn(phys_to_page(block->paddr)),
block->size, vma->vm_page_prot);
}
error:
return ret;
}
static struct dma_buf_ops dmabuf_manage_ops = {
.attach = dmabuf_manage_attach,
.detach = dmabuf_manage_detach,
.map_dma_buf = dmabuf_manage_map_dma_buf,
.unmap_dma_buf = dmabuf_manage_unmap_dma_buf,
.release = dmabuf_manage_buf_release,
.mmap = dmabuf_manage_mmap
};
static struct dma_buf *get_dmabuf(struct dmabuf_manage_block *block,
unsigned long flags)
{
struct dma_buf *dbuf;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
pr_dbg("export handle:%x, paddr:%x, size:%x\n",
block->handle, block->paddr, block->size);
exp_info.ops = &dmabuf_manage_ops;
exp_info.size = block->size;
exp_info.flags = flags;
exp_info.priv = (void *)block;
exp_info.exp_name = "dmabuf_manage";
dbuf = dma_buf_export(&exp_info);
if (IS_ERR(dbuf))
return NULL;
return dbuf;
}
static long dmabuf_manage_export(unsigned long args)
{
int ret;
struct dmabuf_manage_block *block;
struct dma_buf *dbuf;
int fd;
int fd_flags = O_CLOEXEC;
pr_enter();
block = (struct dmabuf_manage_block *)
kzalloc(sizeof(*block), GFP_KERNEL);
if (!block) {
pr_error("kmalloc failed\n");
goto error_alloc_object;
}
ret = copy_from_user((void *)block, (void __user *)args,
sizeof(struct secmem_block));
if (ret) {
pr_error("copy_from_user failed\n");
goto error_copy;
}
block->type = DMA_BUF_TYPE_SECMEM;
dbuf = get_dmabuf(block, fd_flags);
if (!dbuf) {
pr_error("get_dmabuf failed\n");
goto error_export;
}
fd = dma_buf_fd(dbuf, fd_flags);
if (fd < 0) {
pr_error("dma_buf_fd failed\n");
goto error_fd;
}
return fd;
error_fd:
dma_buf_put(dbuf);
error_export:
error_copy:
kfree(block);
error_alloc_object:
return -EFAULT;
}
static long dmabuf_manage_get_handle(unsigned long args)
{
int ret;
long res = -EFAULT;
int fd;
struct dma_buf *dbuf;
struct dmabuf_manage_block *block;
pr_enter();
ret = copy_from_user((void *)&fd, (void __user *)args, sizeof(fd));
if (ret) {
pr_error("copy_from_user failed\n");
goto error_copy;
}
dbuf = dma_buf_get(fd);
if (IS_ERR(dbuf)) {
pr_error("dma_buf_get failed\n");
goto error_get;
}
block = dbuf->priv;
res = (long)(block->handle & (0xffffffff));
dma_buf_put(dbuf);
error_get:
error_copy:
return res;
}
static long dmabuf_manage_get_phyaddr(unsigned long args)
{
int ret;
long res = -EFAULT;
int fd;
struct dma_buf *dbuf;
struct dmabuf_manage_block *block;
pr_enter();
ret = copy_from_user((void *)&fd, (void __user *)args, sizeof(fd));
if (ret) {
pr_error("copy_from_user failed\n");
goto error_copy;
}
dbuf = dma_buf_get(fd);
if (IS_ERR(dbuf)) {
pr_error("dma_buf_get failed\n");
goto error_get;
}
block = dbuf->priv;
res = (long)(block->paddr & (0xffffffff));
dma_buf_put(dbuf);
error_get:
error_copy:
return res;
}
static long dmabuf_manage_import(unsigned long args)
{
int ret;
long res = -EFAULT;
int fd;
struct dma_buf *dbuf;
struct dma_buf_attachment *attach;
struct sg_table *sgt;
dma_addr_t paddr;
pr_enter();
ret = copy_from_user((void *)&fd, (void __user *)args, sizeof(fd));
if (ret) {
pr_error("copy_from_user failed\n");
goto error_copy;
}
dbuf = dma_buf_get(fd);
if (IS_ERR(dbuf)) {
pr_error("dma_buf_get failed\n");
goto error_get;
}
attach = dma_buf_attach(dbuf, dmabuf_manage_dev);
if (IS_ERR(attach)) {
pr_error("dma_buf_attach failed\n");
goto error_attach;
}
sgt = dma_buf_map_attachment(attach, DMA_FROM_DEVICE);
if (IS_ERR(sgt)) {
pr_error("dma_buf_map_attachment failed\n");
goto error_map;
}
paddr = sg_dma_address(sgt->sgl);
res = (long)(paddr & (0xffffffff));
dma_buf_unmap_attachment(attach, sgt, DMA_FROM_DEVICE);
error_map:
dma_buf_detach(dbuf, attach);
error_attach:
dma_buf_put(dbuf);
error_get:
error_copy:
return res;
}
static long dmabuf_manage_export_dmabuf(unsigned long args)
{
long res = -EFAULT;
struct dmabuf_manage_buffer info;
struct dmabuf_manage_block *block;
struct dmabuf_dmx_sec_es_data *dmxes;
struct dmabuf_videodec_es_data *vdecdata;
struct dma_buf *dbuf;
int fd = -1;
int fd_flags = O_CLOEXEC;
pr_enter();
res = copy_from_user((void *)&info, (void __user *)args, sizeof(info));
if (res) {
pr_error("copy_from_user failed\n");
goto error_copy;
}
if (info.type == DMA_BUF_TYPE_INVALID) {
pr_error("unknown dma buf type\n");
goto error_copy;
}
block = kzalloc(sizeof(*block), GFP_KERNEL);
if (!block) {
pr_error("kmalloc failed\n");
goto error_copy;
}
switch (info.type) {
case DMA_BUF_TYPE_SECMEM:
fd_flags = O_CLOEXEC;
break;
case DMA_BUF_TYPE_DMX_ES:
fd_flags = O_CLOEXEC;
dmxes = kzalloc(sizeof(*dmxes), GFP_KERNEL);
if (!dmxes) {
pr_error("kmalloc failed\n");
goto error_alloc_object;
}
memcpy(dmxes, &info.buffer.dmxes, sizeof(*dmxes));
block->priv = dmxes;
break;
case DMA_BUF_TYPE_DMABUF:
fd_flags = O_RDWR | O_CLOEXEC;
break;
case DMA_BUF_TYPE_VIDEODEC_ES:
fd_flags = O_RDWR | O_CLOEXEC;
vdecdata = kzalloc(sizeof(*vdecdata), GFP_KERNEL);
if (!vdecdata) {
pr_error("kmalloc failed\n");
goto error_alloc_object;
}
memcpy(vdecdata, &info.buffer.vdecdata, sizeof(*vdecdata));
block->priv = vdecdata;
break;
default:
block->priv = NULL;
break;
}
block->paddr = info.paddr;
block->size = PAGE_ALIGN(info.size);
block->handle = info.handle;
block->type = info.type;
dbuf = get_dmabuf(block, fd_flags);
if (!dbuf) {
pr_error("get_dmabuf failed\n");
goto error_alloc_object;
}
fd = dma_buf_fd(dbuf, fd_flags);
if (fd < 0) {
pr_error("dma_buf_fd failed\n");
goto error_fd;
}
return fd;
error_fd:
dma_buf_put(dbuf);
error_alloc_object:
kfree(block->priv);
kfree(block);
error_copy:
return res;
}
static long dmabuf_manage_get_dmabufinfo(unsigned long args)
{
struct dmabuf_manage_buffer info;
struct dmabuf_manage_block *block;
struct dma_buf *dbuf;
pr_enter();
if (copy_from_user((void *)&info, (void __user *)args, sizeof(info))) {
pr_error("copy_from_user failed\n");
goto error;
}
if (info.type == DMA_BUF_TYPE_INVALID)
goto error;
dbuf = dma_buf_get(info.fd);
if (IS_ERR(dbuf))
goto error;
if (dbuf->priv && dbuf->ops == &dmabuf_manage_ops) {
block = dbuf->priv;
if (block->type != info.type)
goto error;
info.paddr = block->paddr;
info.size = block->size;
info.handle = block->handle;
switch (info.type) {
case DMA_BUF_TYPE_DMX_ES:
memcpy(&info.buffer.dmxes, block->priv,
sizeof(struct dmabuf_dmx_sec_es_data));
break;
case DMA_BUF_TYPE_VIDEODEC_ES:
memcpy(&info.buffer.vdecdata, block->priv,
sizeof(struct dmabuf_videodec_es_data));
break;
default:
break;
}
if (copy_to_user((void __user *)args, &info, sizeof(info))) {
pr_error("error copy to use space");
goto error_fd;
}
}
dma_buf_put(dbuf);
return 0;
error_fd:
dma_buf_put(dbuf);
error:
return -EFAULT;
}
static long dmabuf_manage_set_filterfd(unsigned long args)
{
struct filter_info info;
struct dmx_filter_info *node = NULL;
struct list_head *pos, *tmp;
int found = 0;
struct dmx_demux *demux = NULL;
decode_info decode_info_func = NULL;
#ifdef CONFIG_AMLOGIC_DVB_COMPAT
struct fd f;
struct dmxdev_filter *dmxdevfilter = NULL;
struct dmxdev *dmxdev = NULL;
struct dmx_demux_ext *demux_ext = NULL;
#endif
pr_enter();
if (copy_from_user((void *)&info, (void __user *)args, sizeof(info))) {
pr_error("copy_from_user failed\n");
goto error;
}
if (list_empty(&dmx_filter_list)) {
if (info.release) {
pr_error("No filter info setting\n");
goto error;
} else {
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node)
goto error;
list_add_tail(&node->list, &dmx_filter_list);
}
} else {
list_for_each_safe(pos, tmp, &dmx_filter_list) {
node = list_entry(pos, struct dmx_filter_info, list);
if (node->token == info.token && node->filter_fd == info.filter_fd) {
found = 1;
break;
}
}
if (info.release) {
if (!found) {
pr_error("No filter info setting\n");
goto error;
} else {
list_del(&node->list);
kfree(node);
node = NULL;
}
} else {
if (!found) {
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (!node)
goto error;
list_add_tail(&node->list, &dmx_filter_list);
}
}
}
if (node) {
node->token = info.token;
node->filter_fd = info.filter_fd;
#ifdef CONFIG_AMLOGIC_DVB_COMPAT
f = fdget(node->filter_fd);
if (f.file && f.file->private_data) {
dmxdevfilter = f.file->private_data;
if (dmxdevfilter)
dmxdev = dmxdevfilter->dev;
}
if (dmxdev && dmxdev->demux) {
demux = dmxdev->demux;
demux_ext = container_of(demux, struct dmx_demux_ext, dmx);
decode_info_func = demux_ext->decode_info;
} else {
pr_error("Invalid filter fd\n");
}
fdput(f);
#endif
node->demux = demux;
node->decode_info_func = decode_info_func;
}
return 0;
error:
return -EFAULT;
}
static int dmabuf_manage_alloc_dmabuf(unsigned long args)
{
int res = -EFAULT;
struct dmabuf_manage_buffer info;
int fd_flags = O_RDWR | O_CLOEXEC;
struct dmabuf_manage_block *block;
struct dma_buf *dbuf;
pr_enter();
res = copy_from_user((void *)&info, (void __user *)args, sizeof(info));
if (res)
goto error_copy;
if (info.size <= 0 || info.size % 4096 != 0) {
pr_error("Invalid size isn't 4K align %d", info.size);
goto error_copy;
}
block = kzalloc(sizeof(*block), GFP_KERNEL);
if (!block) {
pr_error("kmalloc failed\n");
goto error_copy;
}
block->paddr = codec_mm_alloc_for_dma("dmabuf", info.size / PAGE_SIZE,
PAGE_SHIFT, CODEC_MM_FLAGS_DMA);
if (block->paddr <= 0)
goto error_alloc_object;
block->size = PAGE_ALIGN(info.size);
block->handle = info.handle;
block->type = info.type;
block->flags |= DMABUF_ALLOC_FROM_CMA;
dbuf = get_dmabuf(block, fd_flags);
if (!dbuf) {
pr_error("get_dmabuf failed\n");
goto error_alloc;
}
res = dma_buf_fd(dbuf, fd_flags);
if (res < 0) {
pr_error("dma_buf_fd failed\n");
goto error_fd;
}
return res;
error_fd:
dma_buf_put(dbuf);
error_alloc:
codec_mm_free_for_dma("dmabuf", block->paddr);
error_alloc_object:
kfree(block);
error_copy:
return -EFAULT;
}
unsigned int dmabuf_manage_get_type(struct dma_buf *dbuf)
{
int ret = DMA_BUF_TYPE_INVALID;
struct dmabuf_manage_block *block;
if (!dbuf) {
pr_dbg("acquire dma_buf failed");
goto error;
}
if (dbuf->priv && dbuf->ops == &dmabuf_manage_ops) {
block = (struct dmabuf_manage_block *)dbuf->priv;
if (block)
ret = block->type;
}
error:
return ret;
}
EXPORT_SYMBOL(dmabuf_manage_get_type);
void *dmabuf_manage_get_info(struct dma_buf *dbuf, unsigned int type)
{
void *buf = NULL;
struct dmabuf_manage_block *block;
if (type == DMA_BUF_TYPE_INVALID)
goto error;
if (!dbuf) {
pr_dbg("acquire dma_buf failed");
goto error;
}
if (dbuf->priv && dbuf->ops == &dmabuf_manage_ops) {
block = (struct dmabuf_manage_block *)dbuf->priv;
switch (block->type) {
case DMA_BUF_TYPE_SECMEM:
buf = block;
break;
case DMA_BUF_TYPE_DMX_ES:
buf = block->priv;
break;
case DMA_BUF_TYPE_DMABUF:
buf = block;
break;
case DMA_BUF_TYPE_VIDEODEC_ES:
buf = block->priv;
break;
default:
break;
}
}
error:
return buf;
}
EXPORT_SYMBOL(dmabuf_manage_get_info);
static struct secure_pool_info *dmabuf_manage_get_secure_vdec_pool(u32 id_high,
u32 id_low)
{
struct list_head *pos = NULL;
struct list_head *tmp = NULL;
struct secure_pool_info *pool = NULL;
if (!list_empty(&pool_list)) {
list_for_each_safe(pos, tmp, &pool_list) {
pool = list_entry(pos, struct secure_pool_info, node);
if (pool && pool->id_high == id_high && pool->id_low == id_low)
return pool;
}
}
return NULL;
}
static int dmabuf_manage_secure_pool_get_block_count(struct secure_pool_info *pool)
{
int count = 0;
struct list_head *pos = NULL;
struct list_head *tmp = NULL;
if (pool && !list_empty(&pool->block_node)) {
list_for_each_safe(pos, tmp, &pool->block_node)
count++;
}
return count;
}
static int dmabuf_manage_secure_pool_get_block_free_slot(struct secure_pool_info *pool,
u32 frame_size)
{
int slot = 0;
int index = 0;
phys_addr_t *paddr = NULL;
int alignsize = PAGE_ALIGN(frame_size);
if (pool && pool->gen_pool) {
if (pool->max_frame_size < alignsize)
pool->max_frame_size = alignsize;
paddr = kcalloc(secure_pool_max_block_count, sizeof(phys_addr_t), GFP_KERNEL);
if (!paddr)
return slot;
for (index = 0; index < secure_pool_max_block_count; index++, slot++) {
paddr[index] = gen_pool_alloc(pool->gen_pool, alignsize);
if (paddr[index] <= 0)
break;
}
for (index = 0; index < slot; index++)
gen_pool_free(pool->gen_pool, paddr[index], alignsize);
kfree(paddr);
}
return slot;
}
static inline u32 secure_pool_align_up2n(u32 size, u32 alg2n)
{
return ((size + (1 << alg2n) - 1) & (~((1 << alg2n) - 1)));
}
static int dmabuf_manage_secure_pool_init(u32 id_high, u32 id_low, u32 block_size,
u32 *pool_addr, u32 *pool_size, u32 version)
{
int res = 1;
struct secure_pool_info *pool = NULL;
if (!pool_addr || !pool_size)
goto error;
pool = kzalloc(sizeof(*pool), GFP_KERNEL);
if (!pool)
goto error;
pool->version = version;
pool->id_high = id_high;
pool->id_low = id_low;
pool->block_size = block_size;
if (version == SECURE_HEAP_DEFAULT_VERSION) {
pool->pool_size = PAGE_ALIGN(block_size * secure_pool_max_block_count);
} else {
if (block_size <= 2 * POOL_SIZE_MB)
pool->pool_size = secure_hd_pool_size_mb * POOL_SIZE_MB;
else if (block_size <= 6 * POOL_SIZE_MB)
pool->pool_size = secure_uhd_pool_size_mb * POOL_SIZE_MB;
else
pool->pool_size = secure_fuhd_pool_size_mb * POOL_SIZE_MB;
}
pool->pool_size = secure_pool_align_up2n(pool->pool_size,
DMABUF_MANAGE_POOL_ALIGN_2N);
if (pool->pool_size > DMABUF_MANAGE_POOL_SIZE_MB * POOL_SIZE_MB)
pool->pool_size = DMABUF_MANAGE_POOL_SIZE_MB * POOL_SIZE_MB;
pool->mms = codec_mm_alloc("dma-secure-buf", pool->pool_size,
DMABUF_MANAGE_POOL_ALIGN_2N, CODEC_MM_FLAGS_DMA);
if (!pool->mms || !pool->mms->phy_addr)
goto error_alloc;
pool->pool_addr = pool->mms->phy_addr;
#if IS_BUILTIN(CONFIG_AMLOGIC_MEDIA_MODULE)
cma_mmu_op(pool->mms->mem_handle, pool->mms->page_count, 0);
#endif
res = tee_protect_mem_by_type(TEE_MEM_TYPE_STREAM_INPUT,
(u32)pool->pool_addr, pool->pool_size, &pool->protect_handle);
if (res) {
pr_error("Protect vdec fail %x %d %d %x %x\n", res, id_high, id_low,
(u32)pool->pool_addr, pool->pool_size);
goto error_protect;
}
res = tee_check_in_mem((u32)pool->pool_addr, (u32)pool->pool_size);
if (res) {
pr_error("CheckIn vdec fail %x %d %d %x %x\n", res, id_high, id_low,
(u32)pool->pool_addr, pool->pool_size);
goto error_checkin;
}
pool->gen_pool = gen_pool_create(PAGE_SHIFT, -1);
if (!pool->gen_pool) {
pr_error("Create gen pool fail for %d %d %x %x\n", id_high, id_low,
(u32)pool->pool_addr, pool->pool_size);
goto error_create_pool;
}
res = gen_pool_add(pool->gen_pool, pool->pool_addr, pool->pool_size, -1);
if (res) {
pr_error("Add gen pool fail %d for %d %d %x %x\n", res, id_high, id_low,
(u32)pool->pool_addr, pool->pool_size);
goto error_add_pool;
}
*pool_addr = pool->pool_addr;
*pool_size = pool->pool_size;
INIT_LIST_HEAD(&pool->block_node);
list_add_tail(&pool->node, &pool_list);
return 0;
error_add_pool:
gen_pool_destroy(pool->gen_pool);
error_create_pool:
res = tee_check_out_mem(pool->pool_addr, pool->pool_size);
if (res)
pr_error("Check Out %x %x fail\n", (u32)pool->pool_addr, pool->pool_size);
error_checkin:
tee_unprotect_mem(pool->protect_handle);
error_protect:
#if IS_BUILTIN(CONFIG_AMLOGIC_MEDIA_MODULE)
cma_mmu_op(pool->mms->mem_handle, pool->mms->page_count, 1);
#endif
res = codec_mm_free_for_dma("dma-secure-buf", pool->pool_addr);
if (res)
pr_error("Free %x %x fail\n", (u32)pool->pool_addr, pool->pool_size);
error_alloc:
kfree(pool);
error:
return 1;
}
int dmabuf_manage_secure_pool_create(u32 id_high, u32 id_low, u32 block_size,
u32 *pool_addr, u32 *pool_size, u32 version)
{
int res = 0;
struct secure_pool_info *pool = NULL;
if (!pool_addr || !pool_size)
return 1;
mutex_lock(&g_secure_pool_mutex);
pool = dmabuf_manage_get_secure_vdec_pool(id_high, id_low);
if (pool) {
*pool_addr = pool->pool_addr;
*pool_size = pool->pool_size;
} else {
res = dmabuf_manage_secure_pool_init(id_high, id_low,
block_size, pool_addr, pool_size, version);
}
mutex_unlock(&g_secure_pool_mutex);
return res;
}
static void dmabuf_manage_destroy_secure_pool(struct secure_pool_info *pool)
{
int res = 0;
if (!pool)
return;
if (!pool->channel_register && list_empty(&pool->block_node)) {
gen_pool_destroy(pool->gen_pool);
res = tee_check_out_mem(pool->pool_addr, pool->pool_size);
if (res)
pr_error("Check Out %x %x fail\n", (u32)pool->pool_addr, pool->pool_size);
tee_unprotect_mem(pool->protect_handle);
#if IS_BUILTIN(CONFIG_AMLOGIC_MEDIA_MODULE)
cma_mmu_op(pool->mms->mem_handle, pool->mms->page_count, 1);
#endif
res = codec_mm_free_for_dma("dma-secure-buf", pool->pool_addr);
if (res)
pr_error("Free %x %x fail\n", (u32)pool->pool_addr, pool->pool_size);
list_del(&pool->node);
kfree(pool);
}
}
static long dmabuf_manage_register_channel(unsigned long args)
{
struct secure_vdec_channel *channel = NULL;
struct secure_pool_info *pool = NULL;
int ret = 0;
int fd = -1;
int fd_flags = O_CLOEXEC;
struct dmabuf_manage_block *block = NULL;
struct dma_buf *dbuf = NULL;
mutex_lock(&g_secure_pool_mutex);
pr_enter();
channel = (struct secure_vdec_channel *)
kzalloc(sizeof(*channel), GFP_KERNEL);
if (!channel) {
pr_error("kmalloc channel failed\n");
goto error_alloc_channel;
}
ret = copy_from_user((void *)channel, (void __user *)args, sizeof(*channel));
if (ret) {
pr_error("copy channel information failed\n");
goto error_copy;
}
pool = dmabuf_manage_get_secure_vdec_pool(channel->id_high, channel->id_low);
if (pool) {
pool->channel_register = 1;
block = (struct dmabuf_manage_block *)
kzalloc(sizeof(*block), GFP_KERNEL);
if (!block) {
pr_error("kmalloc failed\n");
goto error_alloc_object;
}
block->handle = pool->protect_handle;
block->paddr = pool->pool_addr;
block->size = pool->block_size;
block->type = DMA_BUF_TYPE_SECURE_VDEC;
block->priv = (void *)channel;
dbuf = get_dmabuf(block, fd_flags);
if (!dbuf) {
pr_error("get_dmabuf failed\n");
goto error_export;
}
fd = dma_buf_fd(dbuf, fd_flags);
if (fd < 0) {
pr_error("dma_buf_fd failed\n");
goto error_fd;
}
} else {
goto error_copy;
}
mutex_unlock(&g_secure_pool_mutex);
return fd;
error_fd:
dma_buf_put(dbuf);
error_export:
kfree(block);
error_alloc_object:
error_copy:
kfree(channel);
error_alloc_channel:
mutex_unlock(&g_secure_pool_mutex);
return -EFAULT;
}
static long dmabuf_manage_release_channel(u32 id_high, u32 id_low)
{
struct secure_pool_info *pool = NULL;
mutex_lock(&g_secure_pool_mutex);
pr_enter();
pool = dmabuf_manage_get_secure_vdec_pool(id_high, id_low);
if (pool) {
pool->channel_register = 0;
dmabuf_manage_destroy_secure_pool(pool);
}
mutex_unlock(&g_secure_pool_mutex);
return 0;
}
static void dmabuf_manage_dump_secure_pool(struct secure_pool_info *pool)
{
char buf[1024] = { 0 };
u32 size = 1024;
u32 s = 0;
u32 tsize = 0;
char *pbuf = buf;
u32 block_count = 0;
struct list_head *pos = NULL;
struct list_head *tmp = NULL;
struct block_node *block = NULL;
if (pool) {
s = snprintf(pbuf, size - tsize,
"Pool Ver %d %d Id %d %d Addr %x %x %x Block %x %x\n",
pool->version, pool->channel_register,
pool->id_high, pool->id_low, pool->protect_handle,
(u32)pool->pool_addr, pool->pool_size, pool->block_size,
pool->max_frame_size);
tsize += s;
pbuf += s;
if (!list_empty(&pool->block_node)) {
list_for_each_safe(pos, tmp, &pool->block_node) {
block = list_entry(pos, struct block_node, node);
if (block) {
s = snprintf(pbuf, size - tsize,
"Block %d addr %d size %d\n", block_count,
block->addr, block->size);
tsize += s;
pbuf += s;
block_count++;
}
}
}
pr_error("%s", buf);
}
}
int dmabuf_manage_secure_pool_status(u32 id_high, u32 id_low, u32 frame_size,
u32 *block_count, u32 *block_free_slot)
{
struct secure_pool_info *pool = NULL;
if (!block_count || !block_free_slot)
return 0;
mutex_lock(&g_secure_pool_mutex);
*block_count = 0;
*block_free_slot = 0;
pool = dmabuf_manage_get_secure_vdec_pool(id_high, id_low);
if (pool) {
*block_count = dmabuf_manage_secure_pool_get_block_count(pool);
*block_free_slot =
dmabuf_manage_secure_pool_get_block_free_slot(pool, frame_size);
}
mutex_unlock(&g_secure_pool_mutex);
return 0;
}
phys_addr_t dmabuf_manage_secure_block_alloc(u32 id_high, u32 id_low, u32 size)
{
struct secure_pool_info *pool = NULL;
phys_addr_t addr = 0;
struct block_node *block = NULL;
mutex_lock(&g_secure_pool_mutex);
pool = dmabuf_manage_get_secure_vdec_pool(id_high, id_low);
if (pool) {
addr = gen_pool_alloc(pool->gen_pool, size);
if (addr) {
block = kzalloc(sizeof(*block), GFP_KERNEL);
if (block) {
block->addr = addr;
block->size = size;
list_add_tail(&block->node, &pool->block_node);
} else {
pr_error("No Memory for secure block\n");
}
} else {
dmabuf_manage_dump_secure_pool(pool);
}
} else {
pr_error("Can't found pool for %d %d\n", id_high, id_low);
}
mutex_unlock(&g_secure_pool_mutex);
return addr;
}
int dmabuf_manage_secure_block_free(u32 id_high, u32 id_low, u32 release,
phys_addr_t addr, u32 size)
{
struct secure_pool_info *pool = NULL;
struct list_head *pos = NULL;
struct list_head *tmp = NULL;
struct block_node *block = NULL;
mutex_lock(&g_secure_pool_mutex);
pool = dmabuf_manage_get_secure_vdec_pool(id_high, id_low);
if (pool) {
if (addr && size > 0) {
gen_pool_free(pool->gen_pool, addr, size);
if (!list_empty(&pool->block_node)) {
list_for_each_safe(pos, tmp, &pool->block_node) {
block = list_entry(pos, struct block_node, node);
if (block && block->addr == addr && block->size == size) {
list_del(&block->node);
kfree(block);
}
}
}
}
if (release)
dmabuf_manage_destroy_secure_pool(pool);
}
mutex_unlock(&g_secure_pool_mutex);
return 0;
}
int dmabuf_manage_get_secure_heap_version(void)
{
return secure_heap_version;
}
static int dmabuf_manage_open(struct inode *inodep, struct file *filep)
{
pr_enter();
return 0;
}
static ssize_t dmabuf_manage_read(struct file *filep, char *buffer,
size_t len, loff_t *offset)
{
pr_enter();
return 0;
}
static ssize_t dmabuf_manage_write(struct file *filep, const char *buffer,
size_t len, loff_t *offset)
{
pr_enter();
return 0;
}
static int dmabuf_manage_release(struct inode *inodep, struct file *filep)
{
pr_enter();
return 0;
}
static long dmabuf_manage_ioctl(struct file *filep, unsigned int cmd,
unsigned long args)
{
long ret = -EINVAL;
pr_inf("cmd %x\n", cmd);
switch (cmd) {
case DMABUF_MANAGE_EXPORT_DMA:
ret = dmabuf_manage_export(args);
break;
case DMABUF_MANAGE_GET_HANDLE:
ret = dmabuf_manage_get_handle(args);
break;
case DMABUF_MANAGE_GET_PHYADDR:
ret = dmabuf_manage_get_phyaddr(args);
break;
case DMABUF_MANAGE_IMPORT_DMA:
ret = dmabuf_manage_import(args);
break;
case DMABUF_MANAGE_REGISTER_CHANNEL:
ret = dmabuf_manage_register_channel(args);
break;
case DMABUF_MANAGE_VERSION:
ret = AML_DMA_BUF_MANAGER_VERSION;
break;
case DMABUF_MANAGE_EXPORT_DMABUF:
ret = dmabuf_manage_export_dmabuf(args);
break;
case DMABUF_MANAGE_GET_DMABUFINFO:
ret = dmabuf_manage_get_dmabufinfo(args);
break;
case DMABUF_MANAGE_SET_FILTERFD:
ret = dmabuf_manage_set_filterfd(args);
break;
case DMABUF_MANAGE_ALLOCDMABUF:
ret = dmabuf_manage_alloc_dmabuf(args);
break;
default:
break;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long dmabuf_manage_compat_ioctl(struct file *filep, unsigned int cmd,
unsigned long args)
{
unsigned long ret;
args = (unsigned long)compat_ptr(args);
ret = dmabuf_manage_ioctl(filep, cmd, args);
return ret;
}
#endif
const struct file_operations fops = {
.owner = THIS_MODULE,
.open = dmabuf_manage_open,
.read = dmabuf_manage_read,
.write = dmabuf_manage_write,
.release = dmabuf_manage_release,
.unlocked_ioctl = dmabuf_manage_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = dmabuf_manage_compat_ioctl,
#endif
};
static ssize_t dmabuf_manage_dump_show(struct class *class,
struct class_attribute *attr, char *buf)
{
struct list_head *pos = NULL;
struct list_head *tmp = NULL;
struct secure_pool_info *pool = NULL;
mutex_lock(&g_secure_pool_mutex);
if (!list_empty(&pool_list) && dmabuf_manage_debug >= 6) {
list_for_each_safe(pos, tmp, &pool_list) {
pool = list_entry(pos, struct secure_pool_info, node);
dmabuf_manage_dump_secure_pool(pool);
}
}
mutex_unlock(&g_secure_pool_mutex);
return 0;
}
static ssize_t dmabuf_manage_config_show(struct class *class,
struct class_attribute *attr, char *buf)
{
ssize_t ret;
ret = configs_list_path_nodes(CONFIG_PATH, buf, PAGE_SIZE,
LIST_MODE_NODE_CMDVAL_ALL);
return ret;
}
static ssize_t dmabuf_manage_config_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t size)
{
int ret;
ret = configs_set_prefix_path_valonpath(CONFIG_PREFIX, buf);
if (ret < 0)
pr_err("set config failed %s\n", buf);
return size;
}
static struct mconfig dmabuf_manage_configs[] = {
MC_PI32("secure_vdec_def_size_bytes", &secure_vdec_def_size_bytes),
MC_PI32("secure_hd_pool_size_mb", &secure_hd_pool_size_mb),
MC_PI32("secure_uhd_pool_size_mb", &secure_uhd_pool_size_mb),
MC_PI32("secure_fuhd_pool_size_mb", &secure_fuhd_pool_size_mb),
MC_PI32("secure_pool_max_block_count", &secure_pool_max_block_count),
MC_PI32("secure_heap_version", &secure_heap_version)
};
static CLASS_ATTR_RO(dmabuf_manage_dump);
static CLASS_ATTR_RW(dmabuf_manage_config);
static struct attribute *dmabuf_manage_class_attrs[] = {
&class_attr_dmabuf_manage_dump.attr,
&class_attr_dmabuf_manage_config.attr,
NULL
};
ATTRIBUTE_GROUPS(dmabuf_manage_class);
static struct class dmabuf_manage_class = {
.name = CLASS_NAME,
.class_groups = dmabuf_manage_class_groups,
};
int __init dmabuf_manage_init(void)
{
int ret;
ret = register_chrdev(0, DEVICE_NAME, &fops);
if (ret < 0) {
pr_error("register_chrdev failed\n");
goto error_register;
}
dev_no = ret;
ret = class_register(&dmabuf_manage_class);
if (ret < 0) {
pr_error("class_register failed\n");
goto error_class;
}
dmabuf_manage_dev = device_create(&dmabuf_manage_class,
NULL, MKDEV(dev_no, 0),
NULL, DEVICE_NAME);
if (IS_ERR(dmabuf_manage_dev)) {
pr_error("device_create failed\n");
ret = PTR_ERR(dmabuf_manage_dev);
goto error_create;
}
REG_PATH_CONFIGS(CONFIG_PATH, dmabuf_manage_configs);
INIT_LIST_HEAD(&dmx_filter_list);
INIT_LIST_HEAD(&pool_list);
pr_dbg("init done\n");
return 0;
error_create:
class_unregister(&dmabuf_manage_class);
error_class:
unregister_chrdev(dev_no, DEVICE_NAME);
error_register:
return ret;
}
void __exit dmabuf_manage_exit(void)
{
device_destroy(&dmabuf_manage_class, MKDEV(dev_no, 0));
class_unregister(&dmabuf_manage_class);
class_destroy(&dmabuf_manage_class);
unregister_chrdev(dev_no, DEVICE_NAME);
pr_dbg("exit done\n");
}
MODULE_LICENSE("GPL");