M7350/kernel/drivers/gud/MobiCoreDriver/main.c
2024-09-09 08:57:42 +00:00

751 lines
16 KiB
C

/*
* Copyright (c) 2013-2015 TRUSTONIC LIMITED
* 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.
*/
#include <asm/pgtable.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/stat.h>
#include <linux/debugfs.h>
#include "public/mc_linux.h"
#include "main.h"
#include "fastcall.h"
#include "arm.h"
#include "mmu.h"
#include "scheduler.h"
#include "pm.h"
#include "debug.h"
#include "logging.h"
#include "admin.h"
#include "mcp.h"
#include "session.h"
#include "client.h"
#include "api.h"
#include "build_tag.h"
/* Define a MobiCore device structure for use with dev_debug() etc */
static struct device_driver driver = {
.name = "Trustonic"
};
static struct device device = {
.driver = &driver
};
struct mc_device_ctx g_ctx = {
.mcd = &device
};
/* device admin */
static dev_t mc_dev_admin;
/* device user */
static dev_t mc_dev_user;
/* Need to discover a chrdev region for the driver */
static struct cdev mc_user_cdev;
/* Device class for the driver assigned major */
static struct class *mc_device_class;
/*
* Get client object from file pointer
*/
static inline struct tbase_client *get_client(struct file *file)
{
return (struct tbase_client *)file->private_data;
}
/*
* Callback for system mmap()
*/
static int mc_fd_user_mmap(struct file *file, struct vm_area_struct *vmarea)
{
struct tbase_client *client = get_client(file);
uint32_t len = (uint32_t)(vmarea->vm_end - vmarea->vm_start);
/* Alloc contiguous buffer for this client */
return api_malloc_cbuf(client, len, NULL, vmarea);
}
/*
* Check r/w access to referenced memory
*/
static inline int ioctl_check_pointer(unsigned int cmd, int __user *uarg)
{
int err = 0;
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, uarg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, uarg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
return 0;
}
/*
* Callback for system ioctl()
* Implement most of ClientLib API functions
* @file pointer to file
* @cmd command
* @arg arguments
*
* Returns 0 for OK and an errno in case of error
*/
static long mc_fd_user_ioctl(struct file *file, unsigned int id,
unsigned long arg)
{
struct tbase_client *client = get_client(file);
int __user *uarg = (int __user *)arg;
int ret = -EINVAL;
MCDRV_DBG("%u from %s", _IOC_NR(id), current->comm);
if (WARN(!client, "No client data available"))
return -EPROTO;
if (ioctl_check_pointer(id, uarg))
return -EFAULT;
switch (id) {
case MC_IO_FREEZE:
/* Freeze the client */
ret = api_freeze_device(client);
break;
case MC_IO_OPEN_SESSION: {
struct mc_ioctl_open_sess sess;
if (copy_from_user(&sess, uarg, sizeof(sess))) {
ret = -EFAULT;
break;
}
ret = api_open_session(client, &sess.sid, &sess.uuid, sess.tci,
sess.tcilen, sess.is_gp_uuid,
&sess.identity);
if (ret)
break;
if (copy_to_user(uarg, &sess, sizeof(sess))) {
ret = -EFAULT;
api_close_session(client, sess.sid);
break;
}
break;
}
case MC_IO_OPEN_TRUSTLET: {
struct mc_ioctl_open_trustlet ta_desc;
if (copy_from_user(&ta_desc, uarg, sizeof(ta_desc))) {
ret = -EFAULT;
break;
}
/* Call internal api */
ret = api_open_trustlet(client, &ta_desc.sid, ta_desc.spid,
ta_desc.buffer, ta_desc.tlen,
ta_desc.tci, ta_desc.tcilen);
if (ret)
break;
if (copy_to_user(uarg, &ta_desc, sizeof(ta_desc))) {
ret = -EFAULT;
api_close_session(client, ta_desc.sid);
break;
}
break;
}
case MC_IO_CLOSE_SESSION: {
uint32_t sid = (uint32_t)arg;
ret = api_close_session(client, sid);
break;
}
case MC_IO_NOTIFY: {
uint32_t sid = (uint32_t)arg;
ret = api_notify(client, sid);
break;
}
case MC_IO_WAIT: {
struct mc_ioctl_wait wait;
if (copy_from_user(&wait, uarg, sizeof(wait))) {
ret = -EFAULT;
break;
}
ret = api_wait_notification(client, wait.sid, wait.timeout);
break;
}
case MC_IO_MAP: {
struct mc_ioctl_map map;
if (copy_from_user(&map, uarg, sizeof(map))) {
ret = -EFAULT;
break;
}
ret = api_map_wsms(client, map.sid, map.bufs);
if (ret)
break;
/* Fill in return struct */
if (copy_to_user(uarg, &map, sizeof(map))) {
ret = -EFAULT;
api_unmap_wsms(client, map.sid, map.bufs);
break;
}
break;
}
case MC_IO_UNMAP: {
struct mc_ioctl_map map;
if (copy_from_user(&map, uarg, sizeof(map))) {
ret = -EFAULT;
break;
}
ret = api_unmap_wsms(client, map.sid, map.bufs);
break;
}
case MC_IO_ERR: {
struct mc_ioctl_geterr *uerr = (struct mc_ioctl_geterr *)uarg;
uint32_t sid;
int32_t exit_code;
if (get_user(sid, &uerr->sid)) {
ret = -EFAULT;
break;
}
ret = api_get_session_exitcode(client, sid, &exit_code);
if (ret)
break;
/* Fill in return struct */
if (put_user(exit_code, &uerr->value)) {
ret = -EFAULT;
break;
}
break;
}
case MC_IO_VERSION: {
struct mc_version_info version_info;
ret = mcp_get_version(&version_info);
if (ret)
break;
if (copy_to_user(uarg, &version_info, sizeof(version_info)))
ret = -EFAULT;
break;
}
case MC_IO_DR_VERSION: {
uint32_t version = MC_VERSION(MCDRVMODULEAPI_VERSION_MAJOR,
MCDRVMODULEAPI_VERSION_MINOR);
ret = put_user(version, uarg);
break;
}
default:
MCDRV_ERROR("unsupported cmd=0x%x", id);
ret = -ENOIOCTLCMD;
}
return ret;
}
/*
* Callback for system open()
* A set of internal client data are created and initialized.
*
* @inode
* @file
* Returns 0 if OK or -ENOMEM if no allocation was possible.
*/
static int mc_fd_user_open(struct inode *inode, struct file *file)
{
struct tbase_client *client;
MCDRV_DBG("from %s", current->comm);
/* Create client */
client = api_open_device(false);
if (!client)
return -ENOMEM;
/* Store client in user file */
file->private_data = client;
return 0;
}
/*
* Callback for system close()
* The client object is freed.
* @inode
* @file
* Returns 0
*/
static int mc_fd_user_release(struct inode *inode, struct file *file)
{
struct tbase_client *client = get_client(file);
MCDRV_DBG("from %s", current->comm);
if (WARN(!client, "No client data available"))
return -EPROTO;
/* Detach client from user file */
file->private_data = NULL;
/* Destroy client, including remaining sessions */
api_close_device(client);
return 0;
}
static const struct file_operations mc_user_fops = {
.owner = THIS_MODULE,
.open = mc_fd_user_open,
.release = mc_fd_user_release,
.unlocked_ioctl = mc_fd_user_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = mc_fd_user_ioctl,
#endif
.mmap = mc_fd_user_mmap,
};
int kasnprintf(struct kasnprintf_buf *buf, const char *fmt, ...)
{
va_list args;
int max_size = buf->size - buf->off;
int i;
va_start(args, fmt);
i = vsnprintf(buf->buf + buf->off, max_size, fmt, args);
if (i >= max_size) {
int new_size = PAGE_ALIGN(buf->size + i + 1);
char *new_buf = krealloc(buf->buf, new_size, buf->gfp);
if (!new_buf) {
i = -ENOMEM;
} else {
buf->buf = new_buf;
buf->size = new_size;
max_size = buf->size - buf->off;
i = vsnprintf(buf->buf + buf->off, max_size, fmt, args);
}
}
if (i > 0)
buf->off += i;
va_end(args);
return i;
}
static ssize_t debug_info_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
/* Add/update buffer */
if (!file->private_data || !*ppos) {
struct kasnprintf_buf *buf, *old_buf;
int ret;
buf = kzalloc(GFP_KERNEL, sizeof(*buf));
if (!buf)
return -ENOMEM;
buf->gfp = GFP_KERNEL;
ret = api_info(buf);
if (ret < 0) {
kfree(buf);
return ret;
}
old_buf = file->private_data;
file->private_data = buf;
kfree(old_buf);
}
if (file->private_data) {
struct kasnprintf_buf *buf = file->private_data;
return simple_read_from_buffer(user_buf, count, ppos, buf->buf,
buf->off);
}
return 0;
}
static int debug_info_release(struct inode *inode, struct file *file)
{
kfree(file->private_data);
return 0;
}
static const struct file_operations mc_debug_info_ops = {
.read = debug_info_read,
.llseek = default_llseek,
.release = debug_info_release,
};
static inline int device_admin_init(int (*tee_start_cb)(void))
{
int ret = 0;
cdev_init(&mc_user_cdev, &mc_user_fops);
mc_device_class = class_create(THIS_MODULE, "trustonic_tee");
if (IS_ERR(mc_device_class)) {
MCDRV_ERROR("failed to create device class");
return PTR_ERR(mc_device_class);
}
/* Create the ADMIN node */
ret = mc_admin_init(mc_device_class, &mc_dev_admin, tee_start_cb);
if (ret < 0) {
MCDRV_ERROR("failed to init mobicore device");
class_destroy(mc_device_class);
return ret;
}
return 0;
}
static inline int device_user_init(void)
{
int ret = 0;
struct device *dev;
mc_dev_user = MKDEV(MAJOR(mc_dev_admin), 1);
/* Create the user node */
ret = cdev_add(&mc_user_cdev, mc_dev_user, 1);
if (ret) {
MCDRV_ERROR("user device register failed");
goto err_cdev_add;
}
mc_user_cdev.owner = THIS_MODULE;
dev = device_create(mc_device_class, NULL, mc_dev_user, NULL,
MC_USER_DEVNODE);
if (IS_ERR(dev)) {
ret = PTR_ERR(dev);
goto err_device_create;
}
/* Create debugfs info entry */
debugfs_create_file("info", 0400, g_ctx.debug_dir, NULL,
&mc_debug_info_ops);
return 0;
err_device_create:
cdev_del(&mc_user_cdev);
err_cdev_add:
mc_admin_exit(mc_device_class);
class_destroy(mc_device_class);
MCDRV_DBG("failed with %d", ret);
return ret;
}
static void devices_exit(void)
{
device_destroy(mc_device_class, mc_dev_user);
cdev_del(&mc_user_cdev);
mc_admin_exit(mc_device_class);
class_destroy(mc_device_class);
}
static inline int mobicore_start(void)
{
int ret;
struct mc_version_info version_info;
ret = mcp_start();
if (ret) {
MCDRV_ERROR("TEE start failed");
goto err_mcp;
}
ret = mc_logging_start();
if (ret) {
MCDRV_ERROR("Log start failed");
goto err_log;
}
ret = mc_scheduler_start();
if (ret) {
MCDRV_ERROR("Scheduler start failed");
goto err_sched;
}
ret = mc_pm_start();
if (ret) {
MCDRV_ERROR("Power Management start failed");
goto err_pm;
}
ret = mcp_get_version(&version_info);
if (ret)
goto err_mcp_cmd;
MCDRV_DBG("\n"
" product_id = %s\n"
" version_so = 0x%x\n"
" version_mci = 0x%x\n"
" version_mclf = 0x%x\n"
" version_container = 0x%x\n"
" version_mc_config = 0x%x\n"
" version_tl_api = 0x%x\n"
" version_dr_api = 0x%x\n"
" version_cmp = 0x%x\n",
version_info.product_id,
version_info.version_mci,
version_info.version_so,
version_info.version_mclf,
version_info.version_container,
version_info.version_mc_config,
version_info.version_tl_api,
version_info.version_dr_api,
version_info.version_cmp);
if (MC_VERSION_MAJOR(version_info.version_mci) > 1) {
pr_err("MCI version %d.%d is too recent for this driver",
MC_VERSION_MAJOR(version_info.version_mci),
MC_VERSION_MINOR(version_info.version_mci));
goto err_version;
}
if ((MC_VERSION_MAJOR(version_info.version_mci) == 0) &&
(MC_VERSION_MINOR(version_info.version_mci) < 6)) {
pr_err("MCI version %d.%d is too old for this driver",
MC_VERSION_MAJOR(version_info.version_mci),
MC_VERSION_MINOR(version_info.version_mci));
goto err_version;
}
dev_info(g_ctx.mcd, "MobiCore MCI version is %d.%d\n",
MC_VERSION_MAJOR(version_info.version_mci),
MC_VERSION_MINOR(version_info.version_mci));
/* Determine which features are supported */
switch (version_info.version_mci) {
case MC_VERSION(1, 2): /* 310 */
g_ctx.f_client_login = true;
/* Fall through */
case MC_VERSION(1, 1):
g_ctx.f_multimap = true;
/* Fall through */
case MC_VERSION(1, 0): /* 302 */
g_ctx.f_mem_ext = true;
g_ctx.f_ta_auth = true;
/* Fall through */
case MC_VERSION(0, 7):
g_ctx.f_timeout = true;
/* Fall through */
case MC_VERSION(0, 6): /* 301 */
break;
}
ret = device_user_init();
if (ret)
goto err_create_dev_user;
return 0;
err_create_dev_user:
err_version:
err_mcp_cmd:
mc_pm_stop();
err_pm:
mc_scheduler_stop();
err_sched:
mc_logging_stop();
err_log:
mcp_stop();
err_mcp:
return ret;
}
static inline void mobicore_stop(void)
{
mc_pm_stop();
mc_scheduler_stop();
mc_logging_stop();
mcp_stop();
}
/*
* This function is called by the kernel during startup or by a insmod command.
* This device is installed and registered as cdev, then interrupt and
* queue handling is set up
*/
static int mobicore_init(void)
{
int err = 0;
dev_set_name(g_ctx.mcd, "TEE");
/* Do not remove or change the following trace.
* The string "MobiCore" is used to detect if <t-base is in of the image
*/
dev_info(g_ctx.mcd, "MobiCore mcDrvModuleApi version is %d.%d\n",
MCDRVMODULEAPI_VERSION_MAJOR, MCDRVMODULEAPI_VERSION_MINOR);
#ifdef MOBICORE_COMPONENT_BUILD_TAG
dev_info(g_ctx.mcd, "MobiCore %s\n", MOBICORE_COMPONENT_BUILD_TAG);
#endif
/* Hardware does not support ARM TrustZone -> Cannot continue! */
if (!has_security_extensions()) {
MCDRV_ERROR("Hardware doesn't support ARM TrustZone!");
return -ENODEV;
}
/* Running in secure mode -> Cannot load the driver! */
if (is_secure_mode()) {
MCDRV_ERROR("Running in secure MODE!");
return -ENODEV;
}
/* Init common API layer */
api_init();
/* Init plenty of nice features */
err = mc_fastcall_init();
if (err) {
MCDRV_ERROR("Fastcall support init failed!");
goto fail_fastcall_init;
}
err = mcp_init();
if (err) {
MCDRV_ERROR("MCP init failed!");
goto fail_mcp_init;
}
err = mc_logging_init();
if (err) {
MCDRV_ERROR("Log init failed!");
goto fail_log_init;
}
/* The scheduler is the first to create a debugfs entry */
g_ctx.debug_dir = debugfs_create_dir("trustonic_tee", NULL);
err = mc_scheduler_init();
if (err) {
MCDRV_ERROR("Scheduler init failed!");
goto fail_mc_device_sched_init;
}
/*
* Create admin dev so that daemon can already communicate with
* the driver
*/
err = device_admin_init(mobicore_start);
if (err)
goto fail_creat_dev_admin;
return 0;
fail_creat_dev_admin:
mc_scheduler_exit();
fail_mc_device_sched_init:
debugfs_remove(g_ctx.debug_dir);
mc_logging_exit();
fail_log_init:
mcp_exit();
fail_mcp_init:
mc_fastcall_exit();
fail_fastcall_init:
return err;
}
/*
* This function removes this device driver from the Linux device manager .
*/
static void mobicore_exit(void)
{
MCDRV_DBG("enter");
devices_exit();
mobicore_stop();
mc_scheduler_exit();
mc_logging_exit();
mcp_exit();
mc_fastcall_exit();
debugfs_remove_recursive(g_ctx.debug_dir);
MCDRV_DBG("exit");
}
/* Linux Driver Module Macros */
#ifdef MC_DEVICE_PROPNAME
static int mobicore_probe(struct platform_device *pdev)
{
g_ctx.mcd->of_node = pdev->dev.of_node;
mobicore_init();
return 0;
}
static const struct of_device_id of_match_table[] = {
{ .compatible = MC_DEVICE_PROPNAME },
{ }
};
static struct platform_driver mc_plat_driver = {
.probe = mobicore_probe,
.driver = {
.name = "mcd",
.owner = THIS_MODULE,
.of_match_table = of_match_table,
}
};
static int mobicore_register(void)
{
return platform_driver_register(&mc_plat_driver);
}
static void mobicore_unregister(void)
{
platform_driver_unregister(&mc_plat_driver);
mobicore_exit();
}
module_init(mobicore_register);
module_exit(mobicore_unregister);
#else /* MC_DEVICE_PROPNAME */
module_init(mobicore_init);
module_exit(mobicore_exit);
#endif /* !MC_DEVICE_PROPNAME */
MODULE_AUTHOR("Trustonic Limited");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MobiCore driver");