/*
* Copyright (c) 2015-2016, Linaro Limited
*
* 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.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/tee_drv.h>
#include <linux/uaccess.h>
#include "tee_private.h"
#define TEE_NUM_DEVICES 32
#define TEE_IOCTL_PARAM_SIZE(x) (sizeof(struct tee_param) * (x))
/*
* Unprivileged devices in the lower half range and privileged devices in
* the upper half range.
*/
static DECLARE_BITMAP(dev_mask, TEE_NUM_DEVICES);
static DEFINE_SPINLOCK(driver_lock);
static struct class *tee_class;
static dev_t tee_devt;
static int tee_open(struct inode *inode, struct file *filp)
{
int rc;
struct tee_device *teedev;
struct tee_context *ctx;
teedev = container_of(inode->i_cdev, struct tee_device, cdev);
if (!tee_device_get(teedev))
return -EINVAL;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
rc = -ENOMEM;
goto err;
}
kref_init(&ctx->refcount);
ctx->teedev = teedev;
INIT_LIST_HEAD(&ctx->list_shm);
filp->private_data = ctx;
rc = teedev->desc->ops->open(ctx);
if (rc)
goto err;
return 0;
err:
kfree(ctx);
tee_device_put(teedev);
return rc;
}
void teedev_ctx_get(struct tee_context *ctx)
{
if (ctx->releasing)
return;
kref_get(&ctx->refcount);
}
static void teedev_ctx_release(struct kref *ref)
{
struct tee_context *ctx = container_of(ref, struct tee_context,
refcount);
ctx->releasing = true;
ctx->teedev->desc->ops->release(ctx);
kfree(ctx);
}
void teedev_ctx_put(struct tee_context *ctx)
{
if (ctx->releasing)
return;
kref_put(&ctx->refcount, teedev_ctx_release);
}
static void teedev_close_context(struct tee_context *ctx)
{
tee_device_put(ctx->teedev);
teedev_ctx_put(ctx);
}
static int tee_release(struct inode *inode, struct file *filp)
{
teedev_close_context(filp->private_data);
return 0;
}
static int tee_ioctl_version(struct tee_context *ctx,
struct tee_ioctl_version_data __user *uvers)
{
struct tee_ioctl_version_data vers;
ctx->teedev->desc->ops->get_version(ctx->teedev, &vers);
if (ctx->teedev->desc->flags & TEE_DESC_PRIVILEGED)
vers.gen_caps |= TEE_GEN_CAP_PRIVILEGED;
if (copy_to_user(uvers, &vers, sizeof(vers)))
return -EFAULT;
return 0;
}
static int tee_ioctl_shm_alloc(struct tee_context *ctx,
struct tee_ioctl_shm_alloc_data __user *udata)
{
long ret;
struct tee_ioctl_shm_alloc_data data;
struct tee_shm *shm;
if (copy_from_user(&data, udata, sizeof(data)))
return -EFAULT;
/* Currently no input flags are supported */
if (data.flags)
return -EINVAL;
shm = tee_shm_alloc(ctx, data.size, TEE_SHM_MAPPED | TEE_SHM_DMA_BUF);
if (IS_ERR(shm))
return PTR_ERR(shm);
data.id = shm->id;
data.flags = shm->flags;
data.size = shm->size;
if (copy_to_user(udata, &data, sizeof(data)))
ret = -EFAULT;
else
ret = tee_shm_get_fd(shm);
/*
* When user space closes the file descriptor the shared memory
* should be freed or if tee_shm_get_fd() failed then it will
* be freed immediately.
*/
tee_shm_put(shm);
return ret;
}
static int
tee_ioctl_shm_register(struct tee_context *ctx,
struct tee_ioctl_shm_register_data __user *udata)
{
long ret;
struct tee_ioctl_shm_register_data data;
struct tee_shm *shm;
if (copy_from_user(&data, udata, sizeof(data)))
return -EFAULT;
/* Currently no input flags are supported */
if (data.flags)
return -EINVAL;
shm = tee_shm_register(ctx, data.addr, data.length,
TEE_SHM_DMA_BUF | TEE_SHM_USER_MAPPED