288 lines
6.6 KiB
C
288 lines
6.6 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2022 Rockchip Electronics Co., Ltd.
|
|
*
|
|
* Due to hardware limitations, this module only supports
|
|
* up to 32bit continuous CMA memory.
|
|
*
|
|
* author:
|
|
* Xiao Yapeng, yp.xiao@rock-chips.com
|
|
* mender:
|
|
* Lin Jinhan, troy.lin@rock-chips.com
|
|
*/
|
|
|
|
#include <linux/dma-buf.h>
|
|
#include <linux/dma-direct.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/soc/rockchip/rockchip_decompress.h>
|
|
#include <uapi/linux/rk-decom.h>
|
|
|
|
#define RK_DECOME_TIMEOUT 3 /* 3 seconds */
|
|
|
|
struct rk_decom_dev {
|
|
struct miscdevice miscdev;
|
|
struct device *dev;
|
|
struct mutex mutex;
|
|
};
|
|
|
|
static long rk_decom_misc_ioctl(struct file *fptr, unsigned int cmd, unsigned long arg);
|
|
|
|
static const struct file_operations rk_decom_fops = {
|
|
.owner = THIS_MODULE,
|
|
.unlocked_ioctl = rk_decom_misc_ioctl,
|
|
};
|
|
|
|
static struct rk_decom_dev g_rk_decom = {
|
|
.miscdev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = RK_DECOM_NAME,
|
|
.fops = &rk_decom_fops,
|
|
},
|
|
};
|
|
|
|
static bool check_scatter_list(unsigned int max_size, struct sg_table *sg_tbl)
|
|
{
|
|
int i;
|
|
unsigned int total_len = 0;
|
|
dma_addr_t next_addr = 0;
|
|
struct scatterlist *sgl = NULL;
|
|
|
|
if (!sg_tbl || !(sg_tbl->sgl))
|
|
return false;
|
|
|
|
for_each_sgtable_sg(sg_tbl, sgl, i) {
|
|
if (sg_phys(sgl) > SZ_4G || sg_phys(sgl) + sg_dma_len(sgl) > SZ_4G)
|
|
return false;
|
|
|
|
if (i && next_addr != sg_dma_address(sgl))
|
|
return false;
|
|
|
|
total_len += sg_dma_len(sgl);
|
|
|
|
next_addr = sg_dma_address(sgl) + sg_dma_len(sgl);
|
|
}
|
|
|
|
return max_size <= total_len;
|
|
}
|
|
|
|
static int get_dmafd_sgtbl(struct device *dev, int dma_fd, enum dma_data_direction dir,
|
|
struct sg_table **sg_tbl, struct dma_buf_attachment **dma_attach,
|
|
struct dma_buf **dmabuf)
|
|
{
|
|
int ret = -EINVAL;
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
*sg_tbl = NULL;
|
|
*dmabuf = NULL;
|
|
*dma_attach = NULL;
|
|
|
|
*dmabuf = dma_buf_get(dma_fd);
|
|
if (IS_ERR(*dmabuf)) {
|
|
ret = PTR_ERR(*dmabuf);
|
|
goto error;
|
|
}
|
|
|
|
*dma_attach = dma_buf_attach(*dmabuf, dev);
|
|
if (IS_ERR(*dma_attach)) {
|
|
ret = PTR_ERR(*dma_attach);
|
|
goto error;
|
|
}
|
|
|
|
*sg_tbl = dma_buf_map_attachment(*dma_attach, dir);
|
|
if (IS_ERR(*sg_tbl)) {
|
|
ret = PTR_ERR(*sg_tbl);
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
error:
|
|
if (*sg_tbl)
|
|
dma_buf_unmap_attachment(*dma_attach, *sg_tbl, dir);
|
|
|
|
if (*dma_attach)
|
|
dma_buf_detach(*dmabuf, *dma_attach);
|
|
|
|
if (*dmabuf)
|
|
dma_buf_put(*dmabuf);
|
|
|
|
*sg_tbl = NULL;
|
|
*dmabuf = NULL;
|
|
*dma_attach = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int put_dmafd_sgtbl(struct device *dev, int dma_fd, enum dma_data_direction dir,
|
|
struct sg_table *sg_tbl, struct dma_buf_attachment *dma_attach,
|
|
struct dma_buf *dmabuf)
|
|
{
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (!sg_tbl || !dma_attach || !dmabuf)
|
|
return -EINVAL;
|
|
|
|
dma_buf_unmap_attachment(dma_attach, sg_tbl, dir);
|
|
dma_buf_detach(dmabuf, dma_attach);
|
|
dma_buf_put(dmabuf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rk_decom_for_user(struct device *dev, struct rk_decom_param *param)
|
|
{
|
|
int ret;
|
|
struct sg_table *sg_tbl_in = NULL, *sg_tbl_out = NULL;
|
|
struct dma_buf *dma_buf_in = NULL, *dma_buf_out = NULL;
|
|
struct dma_buf_attachment *dma_attach_in = NULL, *dma_attach_out = NULL;
|
|
|
|
if (param->mode != RK_GZIP_MOD && param->mode != RK_ZLIB_MOD) {
|
|
dev_err(dev, "unsupported mode %u for decompress.\n", param->mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = get_dmafd_sgtbl(dev, param->src_fd, DMA_TO_DEVICE,
|
|
&sg_tbl_in, &dma_attach_in, &dma_buf_in);
|
|
if (unlikely(ret)) {
|
|
dev_err(dev, "src_fd[%d] get_dmafd_sgtbl error.", (int)param->src_fd);
|
|
goto exit;
|
|
}
|
|
|
|
ret = get_dmafd_sgtbl(dev, param->dst_fd, DMA_FROM_DEVICE,
|
|
&sg_tbl_out, &dma_attach_out, &dma_buf_out);
|
|
if (unlikely(ret)) {
|
|
dev_err(dev, "dst_fd[%d] get_dmafd_sgtbl error.", (int)param->dst_fd);
|
|
goto exit;
|
|
}
|
|
|
|
if (!check_scatter_list(0, sg_tbl_in)) {
|
|
dev_err(dev, "Input dma_fd not a continuous buffer.\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (!check_scatter_list(param->dst_max_size, sg_tbl_out)) {
|
|
dev_err(dev, "Output dma_fd not a continuous buffer or dst_max_size too big.\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
ret = rk_decom_start(param->mode | DECOM_NOBLOCKING, sg_dma_address(sg_tbl_in->sgl),
|
|
sg_dma_address(sg_tbl_out->sgl), param->dst_max_size);
|
|
|
|
if (ret) {
|
|
dev_err(dev, "rk_decom_start failed[%d].", ret);
|
|
goto exit;
|
|
}
|
|
|
|
ret = rk_decom_wait_done(RK_DECOME_TIMEOUT, ¶m->decom_data_len);
|
|
|
|
exit:
|
|
if (sg_tbl_in && dma_buf_in && dma_attach_in)
|
|
put_dmafd_sgtbl(dev, param->src_fd, DMA_TO_DEVICE,
|
|
sg_tbl_in, dma_attach_in, dma_buf_in);
|
|
|
|
if (sg_tbl_out && dma_buf_out && dma_attach_out)
|
|
put_dmafd_sgtbl(dev, param->dst_fd, DMA_FROM_DEVICE,
|
|
sg_tbl_out, dma_attach_out, dma_buf_out);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long rk_decom_misc_ioctl(struct file *fptr, unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct rk_decom_param param;
|
|
struct rk_decom_dev *rk_decom = NULL;
|
|
int ret = -EINVAL;
|
|
|
|
rk_decom = container_of(fptr->private_data, struct rk_decom_dev, miscdev);
|
|
|
|
mutex_lock(&rk_decom->mutex);
|
|
|
|
switch (cmd) {
|
|
case RK_DECOM_USER: {
|
|
ret = copy_from_user((char *)¶m, (char *)arg, sizeof(param));
|
|
if (unlikely(ret)) {
|
|
ret = -EFAULT;
|
|
dev_err(rk_decom->dev, "copy from user fail.\n");
|
|
goto exit;
|
|
}
|
|
|
|
ret = rk_decom_for_user(rk_decom->dev, ¶m);
|
|
|
|
if (copy_to_user((char *)arg, ¶m, sizeof(param))) {
|
|
dev_err(rk_decom->dev, " copy to user fail.\n");
|
|
ret = -EFAULT;
|
|
goto exit;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
mutex_unlock(&rk_decom->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __init rk_decom_misc_init(void)
|
|
{
|
|
int ret;
|
|
struct rk_decom_dev *rk_decom = &g_rk_decom;
|
|
struct miscdevice *misc = &g_rk_decom.miscdev;
|
|
|
|
ret = misc_register(misc);
|
|
if (ret < 0) {
|
|
pr_err("rk_decom: misc device %s register failed[%d].\n", RK_DECOM_NAME, ret);
|
|
goto error;
|
|
}
|
|
|
|
rk_decom->dev = misc->this_device;
|
|
|
|
/* Save driver private data */
|
|
dev_set_drvdata(rk_decom->dev, rk_decom);
|
|
|
|
ret = dma_coerce_mask_and_coherent(misc->this_device, DMA_BIT_MASK(32));
|
|
if (ret) {
|
|
dev_err(rk_decom->dev, "No suitable DMA available.\n");
|
|
goto error;
|
|
}
|
|
|
|
mutex_init(&rk_decom->mutex);
|
|
|
|
dev_info(rk_decom->dev, "misc device %s register success.\n", RK_DECOM_NAME);
|
|
|
|
return 0;
|
|
error:
|
|
if (rk_decom->dev)
|
|
misc_deregister(&rk_decom->miscdev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit rk_decom_misc_exit(void)
|
|
{
|
|
misc_deregister(&g_rk_decom.miscdev);
|
|
}
|
|
|
|
module_init(rk_decom_misc_init)
|
|
module_exit(rk_decom_misc_exit)
|
|
|
|
MODULE_LICENSE("Dual MIT/GPL");
|
|
MODULE_VERSION("1.0.0");
|
|
MODULE_AUTHOR("Xiao Yapeng yp.xiao@rock-chips.com");
|
|
MODULE_DESCRIPTION("Rockchip decom driver");
|