blob: 7a2a2a092714080e58c1aa1d9dc39920337fb024 [file] [log] [blame]
/*
* Copyright (C) 2013-2016 Samsung Electronics, Inc.
*
* 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/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <tzdev/tzdev.h>
#include "tzdev.h"
#include "tz_iwcbuf.h"
#include "tz_cdev.h"
#include "tz_iwcbuf.h"
#include "tz_iwio.h"
#include "tz_transport.h"
MODULE_AUTHOR("Pavel Bogachev <p.bogachev@partner.samsung.com>");
MODULE_DESCRIPTION("Trustzone transport driver");
MODULE_LICENSE("GPL");
#define TZ_TRANSPORT_DEVICE_NAME "tz_transport"
#define TZDEV_TRANSPORT_BUF_SIZE (CONFIG_TZ_TRANSPORT_PG_CNT * PAGE_SIZE - sizeof(struct tz_iwcbuf))
static DEFINE_MUTEX(tz_transport_init_mutex);
static DEFINE_SPINLOCK(tz_transport_slock);
static DEFINE_PER_CPU(struct tz_iwcbuf *, tz_transport_buffer);
enum tz_transport_state {
NOT_INITIALIZED,
INITIALIZED,
PANICKED,
};
static enum tz_transport_state tz_transport_state = NOT_INITIALIZED;
static long tz_transport_unlocked_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case TZ_TRANSPORT_GET_BUF_SIZE: {
__u32 __user *argp = (__u32 *) arg;
__u32 size;
size = TZDEV_TRANSPORT_BUF_SIZE;
return put_user(size, argp);
}
default:
return -ENOTTY;
}
}
static ssize_t tz_transport_read_buffer(struct tz_iwcbuf *buf,
char __user *buffer, size_t size)
{
unsigned int count, write_count, avail;
size_t total = 0;
write_count = buf->write_count;
if (write_count == buf->read_count)
return 0;
if (write_count < buf->read_count)
avail = TZDEV_TRANSPORT_BUF_SIZE - buf->read_count + write_count;
else
avail = write_count - buf->read_count;
if (size < avail)
return 0;
if (write_count < buf->read_count) {
count = TZDEV_TRANSPORT_BUF_SIZE - buf->read_count;
if (copy_to_user(buffer, buf->buffer + buf->read_count, count))
return -EFAULT;
buf->read_count = 0;
buffer += count;
total += count;
}
count = write_count - buf->read_count;
if (copy_to_user(buffer, buf->buffer + buf->read_count, count))
return -EFAULT;
buf->read_count += count;
total += count;
return total;
}
static ssize_t tz_transport_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct tz_iwcbuf *iwc_buf;
unsigned int i;
ssize_t position = 0;
ssize_t ret;
(void) file;
(void) ppos;
if (count == 0)
return 0;
spin_lock(&tz_transport_slock);
for (i = 0; i < NR_SW_CPU_IDS; ++i) {
iwc_buf = per_cpu(tz_transport_buffer, i);
if (!iwc_buf)
continue;
ret = tz_transport_read_buffer(iwc_buf, buf + position,
count - position);
if (ret < 0) {
spin_unlock(&tz_transport_slock);
return ret;
}
position += ret;
}
spin_unlock(&tz_transport_slock);
return position;
}
static int tz_transport_alloc_buffers(void)
{
unsigned int i;
for (i = 0; i < NR_SW_CPU_IDS; ++i) {
struct tz_iwcbuf *buffer = tz_iwio_alloc_iw_channel(
TZ_IWIO_CONNECT_TRANSPORT,
CONFIG_TZ_TRANSPORT_PG_CNT);
if (!buffer)
return -ENOMEM;
per_cpu(tz_transport_buffer, i) = buffer;
}
return 0;
}
static int tz_transport_open(struct inode *inode, struct file *filp)
{
int ret = 0;
if (!tzdev_is_up())
return -EPERM;
mutex_lock(&tz_transport_init_mutex);
if (tz_transport_state == PANICKED) {
ret = -ESHUTDOWN;
goto out;
}
if (tz_transport_state == INITIALIZED)
goto out;
ret = tz_transport_alloc_buffers();
if (ret)
tz_transport_state = PANICKED;
else
tz_transport_state = INITIALIZED;
out:
mutex_unlock(&tz_transport_init_mutex);
return ret;
}
static const struct file_operations tz_transport_fops = {
.owner = THIS_MODULE,
.open = tz_transport_open,
.read = tz_transport_read,
.unlocked_ioctl = tz_transport_unlocked_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = tz_transport_unlocked_ioctl,
#endif /* CONFIG_COMPAT */
};
static struct tz_cdev tz_transport_cdev = {
.name = TZ_TRANSPORT_DEVICE_NAME,
.fops = &tz_transport_fops,
.owner = THIS_MODULE,
};
static int __init tz_transport_init(void)
{
int rc;
rc = tz_cdev_register(&tz_transport_cdev);
return rc;
}
static void __exit tz_transport_exit(void)
{
tz_cdev_unregister(&tz_transport_cdev);
}
module_init(tz_transport_init);
module_exit(tz_transport_exit);