/**
* Copyright (c) 2011 Trusted Logic S.A.
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <linux/atomic.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/page-flags.h>
#include <linux/pm.h>
#include <linux/syscore_ops.h>
#include <linux/vmalloc.h>
#include <linux/signal.h>
#ifdef CONFIG_ANDROID
#include <linux/device.h>
#endif
#include "tf_protocol.h"
#include "tf_defs.h"
#include "tf_util.h"
#include "tf_conn.h"
#include "tf_comm.h"
#ifdef CONFIG_TF_ZEBRA
#include <plat/cpu.h>
#include "tf_zebra.h"
#endif
#ifdef CONFIG_TF_DRIVER_CRYPTO_FIPS
#include "tf_crypto.h"
#endif
#include "s_version.h"
/*----------------------------------------------------------------------------
* Forward Declarations
*----------------------------------------------------------------------------*/
/*
* Creates and registers the device to be managed by the specified driver.
*
* Returns zero upon successful completion, or an appropriate error code upon
* failure.
*/
static int tf_device_register(void);
/*
* Implements the device Open callback.
*/
static int tf_device_open(
struct inode *inode,
struct file *file);
/*
* Implements the device Release callback.
*/
static int tf_device_release(
struct inode *inode,
struct file *file);
/*
* Implements the device ioctl callback.
*/
static long tf_device_ioctl(
struct file *file,
unsigned int ioctl_num,
unsigned long ioctl_param);
/*
* Implements the device shutdown callback.
*/
static int tf_device_shutdown(void);
/*
* Implements the device suspend callback.
*/
static int tf_device_suspend(void);
/*
* Implements the device resume callback.
*/
static int tf_device_resume(void);
/*---------------------------------------------------------------------------
* Module Parameters
*---------------------------------------------------------------------------*/
/*
* The device major number used to register a unique character device driver.
* Let the default value be 122
*/
static int device_major_number = 122;
module_param(device_major_number, int, 0000);
MODULE_PARM_DESC(device_major_number,
"The device major number used to register a unique character "
"device driver");
#ifdef CONFIG_TF_TRUSTZONE
/**
* The softint interrupt line used by the Secure World.
*/
static int soft_interrupt = -1;
module_param(soft_interrupt, int, 0000);
MODULE_PARM_DESC(soft_interrupt,
"The softint interrupt line used by the Secure world");
#endif
#ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT
unsigned tf_debug_level = UINT_MAX;
module_param_named(debug, tf_debug_level, uint, 0644);
#endif
#ifdef CONFIG_TF_DRIVER_CRYPTO_FIPS
char *tf_integrity_hmac_sha256_expected_value;
module_param_named(hmac_sha256, tf_integrity_hmac_sha256_expected_value,
charp, 0444);
#ifdef CONFIG_TF_DRIVER_FAULT_INJECTION
unsigned tf_fault_injection_mask;
module_param_named(fault, tf_fault_injection_mask, uint, 0644);
#endif
int tf_self_test_blkcipher_align;
module_param_named(post_align, tf_self_test_blkcipher_align, int, 0644);
int tf_self_test_blkcipher_use_vmalloc;
module_param_named(post_vmalloc, tf_self_test_blkcipher_use_vmalloc, int, 0644);
#endif
#ifdef CONFIG_ANDROID
static struct class *tf_class;
#endif
/*----------------------------------------------------------------------------
* Global Variables
*----------------------------------------------------------------------------*/
/*
* tf_driver character device definitions.
* read and write methods are not defined
* and will return an error if used by user space
*/
static const struct file_operations g_tf_device_file_ops = {
.owner = THIS_MODULE,
.open = tf_device_open,
.release = tf_device_release,
.unlocked_ioctl = tf_device_ioctl,
.llseek = no_llseek,
};
static struct syscore_ops g_tf_device_syscore_ops = {
.shutdown = tf_device_shutdown,
.suspend = tf_device_suspend,
.resume = tf_device_resume,
};
/* The single device supported by this driver */
static struct tf_device g_tf_dev;
/*----------------------------------------------------------------------------
* Implementations
*----------------------------------------------------------------------------*/
struct tf_device *tf_get_device(void)
{
return &g_tf_dev;
}
/*
* sysfs entries
*/
struct tf_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct tf_device *, char *);
ssize_t (*store)(struct tf_device *, const char *, size_t);
};
/*
* sysfs entry showing allocation stats
*/
static ssize_t info_show(struct tf_device *dev, char *buf)
{
struct tf_device_stats *dev_stats = &dev->stats;
return snprintf(buf, PAGE_SIZE,
"stat.memories.allocated: %d\n"
"stat.pages.allocated: %d\n"
"stat.pages.locked: %d\n",
atomic_read(&dev_stats->stat_memories_allocated),
atomic_read(&dev_stats->stat_pages_allocated),
atomic_read(&dev_stats->stat_pages_locked));
}
static struct tf_sysfs_entry tf_info_entry = __ATTR_RO(info);
#ifdef CONFIG_TF_ZEBRA
/*
* sysfs entry showing whether secure world is up and running
*/
static ssize_t tf_started_show(struct tf_device *dev, char *buf)
{
int tf_started = test_bit(TF_COMM_FLAG_PA_AVAILABLE,
&dev->sm.flags);
return snprintf(buf, PAGE_SIZE, "%s\n", tf_started ? "yes" : "no");
}
static struct tf_sysfs_entry tf_started_entry =
__ATTR_RO(tf_started);
static ssize_t workspace_addr_show(struct tf_device *dev, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%08x\n", dev->workspace_addr);
}
static struct tf_sysfs_entry tf_workspace_addr_entry =
__ATTR_RO(workspace_addr);
static ssize_t workspace_size_show(struct tf_device *dev, char *buf)
{
return snprintf(buf, PAGE_SIZE, "0x%08x\n", dev->workspace_size);
}
static struct tf_sysfs_entry tf_workspace_size_entry =
__ATTR_RO(workspace_size);
#endif
static ssize_t tf_attr_show(struct kobject *kobj, struct attribute *attr,
char *page)
{
struct tf_sysfs_entry *entry = container_of(attr, struct tf_sysfs_entry,
attr);
struct tf_device *dev = container_of(kobj, struct tf_device, kobj);
if (!entry->show)
return -EIO;
return entry->show(dev, page);
}
static ssize_t tf_attr_store(struct kobject *kobj, struct attribute *attr,
const char *page, size_t length)
{
struct tf_sysfs_entry *entry = container_of(attr, struct tf_sysfs_entry,
attr);
struct tf_device *dev = container_of(kobj, struct tf_device, kobj);
if (!entry->store)
return -EIO;
return entry->store(dev, page, length);
}
static void tf_kobj_release(struct kobject *kobj) {}
static struct attribute *tf_default_attrs[] = {
&tf_info_entry.attr,
#ifdef CONFIG_TF_ZEBRA
&tf_started_entry.attr,
&tf_workspace_addr_entry.attr,
&tf_workspace_size_entry.attr,
#endif
NULL,
};
static const struct sysfs_ops tf_sysfs_ops = {
.show = tf_attr_show,
.store = tf_attr_store,
};
static struct kobj_type tf_ktype = {
.release = tf_kobj_release,
.sysfs_ops = &tf_sysfs_ops,
.default_attrs = tf_default_attrs
};
/*----------------------------------------------------------------------------*/
#if defined(MODULE) && defined(CONFIG_TF_ZEBRA)
static char *smc_mem;
module_param(smc_mem, charp, S_IRUGO);
#endif
/*
* First routine called when the kernel module is loaded
*/
static int __init tf_device_register(void)
{
int error;
struct tf_device *dev = &g_tf_dev;
dprintk(KERN_INFO "tf_device_register()\n");
/*
* Initialize the device
*/
dev->dev_number = MKDEV(device_major_number,
TF_DEVICE_MINOR_NUMBER);
cdev_init(&dev->cdev, &g_tf_device_file_ops);
dev->cdev.owner = THIS_MODULE;
INIT_LIST_HEAD(&dev->connection_list);
spin_lock_init(&dev->connection_list_lock);
#if defined(MODULE) && defined(CONFIG_TF_ZEBRA)
error = (*tf_comm_early_init)();
if (error)
goto module_early_init_failed;
error = tf_device_mshield_init(smc_mem);
if (error)
goto mshield_init_failed;
#ifdef CONFIG_TF_DRIVER_CRYPTO_FIPS
error = tf_crypto_hmac_module_init();
if (error)
goto hmac_init_failed;
error = tf_self_test_register_device();
if (error)
goto self_test_register_device_failed;
#endif
#endif
/* register the sysfs object driver stats */
error = kobject_init_and_add(&dev->kobj, &tf_ktype, NULL, "%s",
TF_DEVICE_BASE_NAME);
if (error) {
printk(KERN_ERR "tf_device_register(): "
"kobject_init_and_add failed (error %d)!\n", error);
kobject_put(&dev->kobj);
goto kobject_init_and_add_failed;
}
/*
* Register the system device.
*/
register_syscore_ops(&g_tf_device_syscore_ops);
/*
* Register the char device.
*/
printk(KERN_INFO "Registering char device %s (%u:%u)\n",
TF_DEVICE_BASE_NAME,
MAJOR(dev->dev_number),
MINOR(dev->dev_number));
error = register_chrdev_region(dev->dev_number, 1,
TF_DEVICE_BASE_NAME);
if (error != 0) {
printk(KERN_ERR "tf_device_register():"
" register_chrdev_region failed (error %d)!\n",
error);
goto register_chrdev_region_failed;
}
error = cdev_add(&dev->cdev, dev->dev_number, 1);
if (error != 0) {
printk(KERN_ERR "tf_device_register(): "
"cdev_add failed (error %d)!\n",
error);
goto cdev_add_failed;
}
/*
* Initialize the communication with the Secure World.
*/
#ifdef CONFIG_TF_TRUSTZONE
dev->sm.soft_int_irq = soft_interrupt;
#endif
error = tf_init(&g_tf_dev.sm);
if (error != S_SUCCESS) {
dprintk(KERN_ERR "tf_device_register(): "
"tf_init failed (error %d)!\n",
error);
goto init_failed;
}
#ifdef CONFIG_TF_DRIVER_CRYPTO_FIPS
error = tf_self_test_post_init(&(dev_stats->kobj));
/* N.B. error > 0 indicates a POST failure, which will not
prevent the module from loading. */
if (error < 0) {
dprintk(KERN_ERR "tf_device_register(): "
"tf_self_test_post_vectors failed (error %d)!\n",
error);
goto post_failed;
}
#endif
#ifdef CONFIG_ANDROID
tf_class = class_create(THIS_MODULE, TF_DEVICE_BASE_NAME);
device_create(tf_class, NULL,
dev->dev_number,
NULL, TF_DEVICE_BASE_NAME);
#endif
#ifdef CONFIG_TF_ZEBRA
/*
* Initializes the /dev/tf_ctrl device node.
*/
error = tf_ctrl_device_register();
if (error)
goto ctrl_failed;
#endif
#ifdef CONFIG_TF_DRIVER_DEBUG_SUPPORT
address_cache_property((unsigned long) &tf_device_register);
#endif
/*
* Successful completion.
*/
dprintk(KERN_INFO "tf_device_register(): Success\n");
return 0;
/*
* Error: undo all operations in the reverse order
*/
#ifdef CONFIG_TF_ZEBRA
ctrl_failed:
#endif
#ifdef CONFIG_TF_DRIVER_CRYPTO_FIPS
tf_self_test_post_exit();
post_failed:
#endif
init_failed:
cdev_del(&dev->cdev);
cdev_add_failed:
unregister_chrdev_region(dev->dev_number, 1);
register_chrdev_region_failed:
unregister_syscore_ops(&g_tf_device_syscore_ops);
kobject_init_and_add_failed:
kobject_del(&g_tf_dev.kobj);
#if defined(MODULE) && defined(CONFIG_TF_ZEBRA)
#ifdef CONFIG_TF_DRIVER_CRYPTO_FIPS
tf_self_test_unregister_device();
self_test_register_device_failed:
tf_crypto_hmac_module_exit();
hmac_init_failed:
#endif
tf_device_mshield_exit();
mshield_init_failed:
module_early_init_failed:
#endif
dprintk(KERN_INFO "tf_device_register(): Failure (error %d)\n",
error);
return error;
}
/*----------------------------------------------------------------------------*/
static int tf_device_open(struct inode *inode, struct file *file)
{
int error;
struct tf_device *dev = &g_tf_dev;
struct tf_connection *connection = NULL;
dprintk(KERN_INFO "tf_device_open(%u:%u, %p)\n",
imajor(inode), iminor(inode), file);
/* Dummy lseek for non-seekable driver */
error = nonseekable_open(inode, file);
if (error != 0) {
dprintk(KERN_ERR "tf_device_open(%p): "
"nonseekable_open failed (error %d)!\n",
file, error);
goto error;
}
#ifndef CONFIG_ANDROID
/*
* Check file flags. We only autthorize the O_RDWR access
*/
if (file->f_flags != O_RDWR) {
dprintk(KERN_ERR "tf_device_open(%p): "
"Invalid access mode %u\n",
file, file->f_flags);
error = -EACCES;
goto error;
}
#endif
/*
* Open a new connection.
*/
error = tf_open(dev, file, &connection);
if (error != 0) {
dprintk(KERN_ERR "tf_device_open(%p): "
"tf_open failed (error %d)!\n",
file, error);
goto error;
}
file->private_data = connection;
/*
* Send the CreateDeviceContext command to the secure
*/
error = tf_create_device_context(connection);
if (error != 0) {
dprintk(KERN_ERR "tf_device_open(%p): "
"tf_create_device_context failed (error %d)!\n",
file, error);
goto error1;
}
/*
* Successful completion.
*/
dprintk(KERN_INFO "tf_device_open(%p): Success (connection=%p)\n",
file, connection);
return 0;
/*
* Error handling.
*/
error1:
tf_close(connection);
error:
dprintk(KERN_INFO "tf_device_open(%p): Failure (error %d)\n",
file, error);
return error;
}
/*----------------------------------------------------------------------------*/
static int tf_device_release(struct inode *inode, struct file *file)
{
struct tf_connection *connection;
dprintk(KERN_INFO "tf_device_release(%u:%u, %p)\n",
imajor(inode), iminor(inode), file);
connection = tf_conn_from_file(file);
tf_close(connection);
dprintk(KERN_INFO "tf_device_release(%p): Success\n", file);
return 0;
}
/*----------------------------------------------------------------------------*/
static long tf_device_ioctl(struct file *file, unsigned int ioctl_num,
unsigned long ioctl_param)
{
int result = S_SUCCESS;
struct tf_connection *connection;
union tf_command command;
struct tf_command_header header;
union tf_answer answer;
u32 command_size;
u32 answer_size;
void *user_answer;
dprintk(KERN_INFO "tf_device_ioctl(%p, %u, %p)\n",
file, ioctl_num, (void *) ioctl_param);
switch (ioctl_num) {
case IOCTL_TF_GET_VERSION:
/* ioctl is asking for the driver interface version */
result = TF_DRIVER_INTERFACE_VERSION;
goto exit;
case IOCTL_TF_EXCHANGE:
/*
* ioctl is asking to perform a message exchange with the Secure
* Module
*/
/*
* Make a local copy of the data from the user application
* This routine checks the data is readable
*
* Get the header first.
*/
if (copy_from_user(&header,
(struct tf_command_header *)ioctl_param,
sizeof(struct tf_command_header))) {
dprintk(KERN_ERR "tf_device_ioctl(%p): "
"Cannot access ioctl parameter %p\n",
file, (void *) ioctl_param);
result = -EFAULT;
goto exit;
}
/* size in words of u32 */
command_size = header.message_size +
sizeof(struct tf_command_header)/sizeof(u32);
if (command_size > sizeof(command)/sizeof(u32)) {
dprintk(KERN_ERR "tf_device_ioctl(%p): "
"Buffer overflow: too many bytes to copy %d\n",
file, command_size);
result = -EFAULT;
goto exit;
}
if (copy_from_user(&command,
(union tf_command *)ioctl_param,
command_size * sizeof(u32))) {
dprintk(KERN_ERR "tf_device_ioctl(%p): "
"Cannot access ioctl parameter %p\n",
file, (void *) ioctl_param);
result = -EFAULT;
goto exit;
}
connection = tf_conn_from_file(file);
BUG_ON(connection == NULL);
/*
* The answer memory space address is in the operation_id field
*/
user_answer = (void *) command.header.operation_id;
atomic_inc(&(connection->pending_op_count));
dprintk(KERN_WARNING "tf_device_ioctl(%p): "
"Sending message type 0x%08x\n",
file, command.header.message_type);
switch (command.header.message_type) {
case TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION:
result = tf_open_client_session(connection,
&command, &answer);
break;
case TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION:
result = tf_close_client_session(connection,
&command, &answer);
break;
case TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY:
result = tf_register_shared_memory(connection,
&command, &answer);
break;
case TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY:
result = tf_release_shared_memory(connection,
&command, &answer);
break;
case TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND:
result = tf_invoke_client_command(connection,
&command, &answer);
break;
case TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND:
result = tf_cancel_client_command(connection,
&command, &answer);
break;
default:
dprintk(KERN_ERR "tf_device_ioctl(%p): "
"Incorrect message type (0x%08x)!\n",
connection, command.header.message_type);
result = -EOPNOTSUPP;
break;
}
atomic_dec(&(connection->pending_op_count));
if (result != 0) {
dprintk(KERN_WARNING "tf_device_ioctl(%p): "
"Operation returning error code 0x%08x)!\n",
file, result);
goto exit;
}
/*
* Copy the answer back to the user space application.
* The driver does not check this field, only copy back to user
* space the data handed over by Secure World
*/
answer_size = answer.header.message_size +
sizeof(struct tf_answer_header)/sizeof(u32);
if (copy_to_user(user_answer,
&answer, answer_size * sizeof(u32))) {
dprintk(KERN_WARNING "tf_device_ioctl(%p): "
"Failed to copy back the full command "
"answer to %p\n", file, user_answer);
result = -EFAULT;
goto exit;
}
/* successful completion */
dprintk(KERN_INFO "tf_device_ioctl(%p): Success\n", file);
break;
case IOCTL_TF_GET_DESCRIPTION: {
/* ioctl asking for the version information buffer */
struct tf_version_information_buffer *pInfoBuffer;
dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION:(%p, %u, %p)\n",
file, ioctl_num, (void *) ioctl_param);
pInfoBuffer =
((struct tf_version_information_buffer *) ioctl_param);
dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION1: "
"driver_description=\"%64s\"\n", S_VERSION_STRING);
if (copy_to_user(pInfoBuffer->driver_description,
S_VERSION_STRING,
strlen(S_VERSION_STRING) + 1)) {
dprintk(KERN_ERR "tf_device_ioctl(%p): "
"Fail to copy back the driver description "
"to %p\n",
file, pInfoBuffer->driver_description);
result = -EFAULT;
goto exit;
}
dprintk(KERN_INFO "IOCTL_TF_GET_DESCRIPTION2: "
"secure_world_description=\"%64s\"\n",
tf_get_description(&g_tf_dev.sm));
if (copy_to_user(pInfoBuffer->secure_world_description,
tf_get_description(&g_tf_dev.sm),
TF_DESCRIPTION_BUFFER_LENGTH)) {
dprintk(KERN_WARNING "tf_device_ioctl(%p): "
"Failed to copy back the secure world "
"description to %p\n",
file, pInfoBuffer->secure_world_description);
result = -EFAULT;
goto exit;
}
break;
}
default:
dprintk(KERN_ERR "tf_device_ioctl(%p): "
"Unknown IOCTL code 0x%08x!\n",
file, ioctl_num);
result = -EOPNOTSUPP;
goto exit;
}
exit:
return result;
}
/*----------------------------------------------------------------------------*/
static int tf_device_shutdown(void)
{
return tf_power_management(&g_tf_dev.sm,
TF_POWER_OPERATION_SHUTDOWN);
}
/*----------------------------------------------------------------------------*/
static int tf_device_suspend(void)
{
dprintk(KERN_INFO "tf_device_suspend: Enter\n");
return tf_power_management(&g_tf_dev.sm,
TF_POWER_OPERATION_HIBERNATE);
}
/*----------------------------------------------------------------------------*/
static int tf_device_resume(void)
{
return tf_power_management(&g_tf_dev.sm,
TF_POWER_OPERATION_RESUME);
}
/*----------------------------------------------------------------------------*/
module_init(tf_device_register);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Trusted Logic S.A.");