blob: f8902b570986dd2055c792ea9a6b895880c2e392 [file] [log] [blame]
/*
* Copyright (C) 2012-2020, Samsung Electronics Co., Ltd.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/atomic.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/mmzone.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include "tz_cdev.h"
#include "tz_common.h"
#include "tz_mem.h"
#include "tzdev.h"
#include "tzlog.h"
struct tzdev_mem_priv {
struct mutex mutex;
unsigned int id;
};
static atomic_t tzdev_mem_device_ready = ATOMIC_INIT(0);
static int tzdev_mem_op_open(struct inode *inode, struct file *filp)
{
struct tzdev_mem_priv *priv;
(void)inode;
priv = kmalloc(sizeof(struct tzdev_mem_priv), GFP_KERNEL);
if (!priv) {
tzdev_teec_error("Failed to allocate iwshmem private data.\n");
return -ENOMEM;
}
mutex_init(&priv->mutex);
priv->id = 0;
filp->private_data = priv;
return 0;
}
static int tzdev_mem_op_release(struct inode *inode, struct file *filp)
{
struct tzdev_mem_priv *priv;
(void)inode;
priv = filp->private_data;
if (priv->id)
tzdev_mem_release_user(priv->id);
mutex_destroy(&priv->mutex);
kfree(priv);
return 0;
}
static long tzdev_mem_op_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct tzdev_mem_priv *priv = filp->private_data;
struct tzio_mem_register __user *argp = (struct tzio_mem_register __user *)arg;
struct tzio_mem_register memreg;
int ret;
if (cmd != TZIO_MEM_REGISTER)
return -ENOTTY;
if (copy_from_user(&memreg, argp, sizeof(struct tzio_mem_register)))
return -EFAULT;
mutex_lock(&priv->mutex);
if (priv->id) {
ret = -EEXIST;
goto out;
}
ret = tzdev_mem_register_user(memreg.size, memreg.write);
if (ret < 0)
goto out;
priv->id = ret;
out:
mutex_unlock(&priv->mutex);
return ret;
}
static int tzdev_mem_op_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct tzdev_mem_priv *priv = filp->private_data;
struct tzdev_mem_reg *mem;
unsigned long i;
int ret;
if (vma->vm_pgoff)
return -EINVAL;
mutex_lock(&priv->mutex);
if (!priv->id) {
ret = -ENXIO;
goto out;
}
if (!(vma->vm_flags & VM_WRITE)) {
ret = -EPERM;
goto out;
}
if (vma->vm_flags & VM_EXEC) {
ret = -EPERM;
goto out;
}
vma->vm_flags |= VM_DONTCOPY | VM_DONTEXPAND;
vma->vm_flags &= ~VM_MAYEXEC;
BUG_ON(tzdev_mem_find(priv->id, &mem));
if (vma_pages(vma) != mem->nr_pages) {
ret = -EIO;
goto out;
}
for (i = 0; i < mem->nr_pages; i++) {
ret = vm_insert_page(vma, vma->vm_start + i * PAGE_SIZE, mem->pages[i]);
if (ret)
goto out;
}
out:
mutex_unlock(&priv->mutex);
return ret;
}
static const struct file_operations tzdev_mem_fops = {
.owner = THIS_MODULE,
.open = tzdev_mem_op_open,
.release = tzdev_mem_op_release,
.unlocked_ioctl = tzdev_mem_op_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = tzdev_mem_op_ioctl,
#endif /* CONFIG_COMPAT */
.mmap = tzdev_mem_op_mmap,
};
static struct tz_cdev tzdev_mem_cdev = {
.name = "tziwshmem",
.fops = &tzdev_mem_fops,
.owner = THIS_MODULE,
};
int tzdev_umem_register(void)
{
int ret;
ret = tz_cdev_register(&tzdev_mem_cdev);
if (ret) {
tzdev_teec_error("Failed to create iwshmem device, error=%d\n", ret);
return ret;
}
atomic_set(&tzdev_mem_device_ready, 1);
tzdev_teec_info("Iwshmem user interface initialization done.\n");
return 0;
}
int tzdev_umem_unregister(void)
{
if (!atomic_cmpxchg(&tzdev_mem_device_ready, 1, 0)) {
tzdev_teec_info("Iwshmem user interface was not initialized.\n");
return -EPERM;
}
tz_cdev_unregister(&tzdev_mem_cdev);
tzdev_teec_info("Iwshmem user interface finalization done.\n");
return 0;
}