diff options
Diffstat (limited to 'security/tlk_driver/ote_device.c')
-rw-r--r-- | security/tlk_driver/ote_device.c | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/security/tlk_driver/ote_device.c b/security/tlk_driver/ote_device.c new file mode 100644 index 000000000000..44d8a09971d0 --- /dev/null +++ b/security/tlk_driver/ote_device.c @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2013 NVIDIA Corporation. 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/atomic.h> +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/printk.h> +#include <linux/ioctl.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <asm/cacheflush.h> +#include <asm/outercache.h> +#include <linux/list.h> + +#include "ote_protocol.h" + +#define SET_ANSWER(a, r, ro) { a.result = r; a.return_origin = ro; } + +#define CREATE_TRACE_POINTS +#include <trace/events/nvsecurity.h> + +struct tlk_device tlk_dev; + +u32 notrace tegra_read_cycle(void) +{ + u32 cycle_count; + + asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(cycle_count)); + + return cycle_count; +} + +/* + * The maximum number of outstanding command requests. + */ +#define TE_CMD_DESC_MAX (PAGE_SIZE / sizeof(struct te_request)) +#define TE_PARAM_MAX (PAGE_SIZE / sizeof(struct te_oper_param)) + +static int te_create_free_cmd_list(struct tlk_device *dev) +{ + struct page *te_cmd_pages; + int cmd_desc_count = 0, ret = 0; + struct te_cmd_req_desc *req_desc; + int bitmap_size; + + te_cmd_pages = alloc_pages(GFP_KERNEL, get_count_order(2)); + if (!te_cmd_pages) { + ret = -ENOMEM; + goto error; + } + + /* first 4KB page is request freelist, second is param freelist */ + dev->req_addr = (unsigned long) page_address(te_cmd_pages); + dev->param_addr = (struct te_oper_param *)(dev->req_addr + PAGE_SIZE); + set_memory_uc(dev->req_addr, 2); + + /* alloc param bitmap allocator */ + bitmap_size = BITS_TO_LONGS(TE_PARAM_MAX) * sizeof(long); + dev->param_bitmap = kzalloc(bitmap_size, GFP_KERNEL); + + for (cmd_desc_count = 0; + cmd_desc_count < TE_CMD_DESC_MAX; cmd_desc_count++) { + + req_desc = kzalloc(sizeof(struct te_cmd_req_desc), GFP_KERNEL); + if (req_desc == NULL) { + pr_err("Failed to allocate cmd req descriptor\n"); + ret = -ENOMEM; + goto error; + } + req_desc->req_addr = dev->req_addr + + sizeof(struct te_request) * cmd_desc_count; + INIT_LIST_HEAD(&(req_desc->list)); + + /* Add the cmd param descriptor to free list */ + list_add_tail(&req_desc->list, &(dev->free_cmd_list)); + } +error: + return ret; + +} + +static struct te_oper_param *te_get_free_params(struct tlk_device *dev, + unsigned int nparams) +{ + struct te_oper_param *params = NULL; + int idx, nbits; + + if (nparams) { + nbits = get_count_order(nparams); + idx = bitmap_find_free_region(dev->param_bitmap, + TE_PARAM_MAX, nbits); + if (idx >= 0) + params = dev->param_addr + idx; + } + return params; +} + +static void te_put_free_params(struct tlk_device *dev, + struct te_oper_param *params, uint32_t nparams) +{ + int idx, nbits; + + idx = (params - dev->param_addr); + nbits = get_count_order(nparams); + bitmap_release_region(dev->param_bitmap, idx, nbits); +} + +static struct te_cmd_req_desc *te_get_free_cmd_desc(struct tlk_device *dev) +{ + struct te_cmd_req_desc *cmd_desc = NULL; + + if (!(list_empty(&(dev->free_cmd_list)))) { + cmd_desc = list_first_entry(&(dev->free_cmd_list), + struct te_cmd_req_desc, list); + list_del(&(cmd_desc->list)); + list_add_tail(&cmd_desc->list, &(dev->used_cmd_list)); + } + return cmd_desc; +} + +static void te_put_used_cmd_desc(struct tlk_device *dev, + struct te_cmd_req_desc *cmd_desc) +{ + struct te_cmd_req_desc *param_desc, *tmp_param_desc; + + if (cmd_desc) { + list_for_each_entry_safe(param_desc, tmp_param_desc, + &(dev->used_cmd_list), list) { + if (cmd_desc->req_addr == param_desc->req_addr) { + list_del(¶m_desc->list); + list_add_tail(¶m_desc->list, + &(dev->free_cmd_list)); + } + } + } +} + +static void __attribute__((unused)) te_print_cmd_list( + struct tlk_device *dev, int used_list) +{ + struct te_cmd_req_desc *param_desc; + + if (!used_list) { + pr_info("Printing free cmd list\n"); + if (!(list_empty(&(dev->free_cmd_list)))) { + list_for_each_entry(param_desc, &(dev->free_cmd_list), + list) + pr_info("Phys addr for cmd req desc (%lx)\n", + param_desc->req_addr); + } + } else { + pr_info("Printing used cmd list\n"); + if (!(list_empty(&(dev->used_cmd_list)))) { + list_for_each_entry(param_desc, &(dev->used_cmd_list), + list) + pr_info("Phys addr for cmd req desc (%lx)\n", + param_desc->req_addr); + } + } +} + +static int tlk_device_open(struct inode *inode, struct file *file) +{ + struct tlk_context *context; + int ret = 0; + + context = kzalloc(sizeof(struct tlk_context), GFP_KERNEL); + if (!context) { + ret = -ENOMEM; + goto error; + } + context->dev = &tlk_dev; + INIT_LIST_HEAD(&(context->shmem_alloc_list)); + + file->private_data = context; + return 0; +error: + return ret; +} + +static int tlk_device_release(struct inode *inode, struct file *file) +{ + kfree(file->private_data); + file->private_data = NULL; + return 0; +} + +static int copy_params_from_user(struct te_request *req, + struct te_operation *operation) +{ + struct te_oper_param *param_array; + struct te_oper_param *user_param; + uint32_t i; + + if (operation->list_count == 0) + return 0; + + param_array = req->params; + if (param_array == NULL) { + pr_err("param_array empty\n"); + return 1; + } + + user_param = operation->list_head; + for (i = 0; i < operation->list_count && user_param != NULL; i++) { + if (copy_from_user(param_array + i, user_param, + sizeof(struct te_oper_param))) { + pr_err("Failed to copy operation parameter:%d, %p, " \ + "list_count: %d\n", + i, user_param, operation->list_count); + return 1; + } + user_param = param_array[i].next_ptr_user; + } + return 0; +} + +static int copy_params_to_user(struct te_request *req, + struct te_operation *operation) +{ + struct te_oper_param *param_array; + struct te_oper_param *user_param; + uint32_t i; + + if (operation->list_count == 0) + return 0; + + param_array = req->params; + if (param_array == NULL) { + pr_err("param_array empty\n"); + return 1; + } + + user_param = operation->list_head; + for (i = 0; i < req->params_size; i++) { + if (copy_to_user(user_param, param_array + i, + sizeof(struct te_oper_param))) { + pr_err("Failed to copy back parameter:%d %p\n", i, + user_param); + return 1; + } + user_param = param_array[i].next_ptr_user; + } + return 0; +} + +static long te_handle_trustedapp_ioctl(struct file *file, + unsigned int ioctl_num, unsigned long ioctl_param) +{ + long err = 0; + union te_cmd cmd; + void *ptr_user_answer = NULL; + struct te_operation *operation = NULL; + struct te_oper_param *params = NULL; + struct te_answer answer; + struct te_request *request; + + struct te_cmd_req_desc *cmd_desc = NULL; + struct tlk_context *context = file->private_data; + struct tlk_device *dev = context->dev; + + if (copy_from_user(&cmd, (void __user *)ioctl_param, + sizeof(union te_cmd))) { + pr_err("Failed to copy command request\n"); + err = -EFAULT; + goto error; + } + + memset(&answer, 0, sizeof(struct te_answer)); + + switch (ioctl_num) { + case TE_IOCTL_OPEN_CLIENT_SESSION: + operation = &cmd.opensession.operation; + ptr_user_answer = (void *)cmd.opensession.answer; + + cmd_desc = te_get_free_cmd_desc(dev); + params = te_get_free_params(dev, operation->list_count); + + if (!cmd_desc || (operation->list_count && !params)) { + SET_ANSWER(answer, + OTE_ERROR_OUT_OF_MEMORY, + OTE_ERROR_ORIGIN_COMMS); + pr_err("failed to get cmd_desc/params\n"); + goto error; + } + + request = (struct te_request *)cmd_desc->req_addr; + memset(request, 0, sizeof(struct te_request)); + + request->params = params; + request->params_size = operation->list_count; + + if (copy_params_from_user(request, operation)) { + err = -EFAULT; + pr_info("failed to copy params from user\n"); + goto error; + } + + te_open_session(&cmd.opensession, request, context); + + SET_ANSWER(answer, request->result, request->result_origin); + answer.session_id = request->session_id; + break; + + case TE_IOCTL_CLOSE_CLIENT_SESSION: + ptr_user_answer = (void *)cmd.closesession.answer; + cmd_desc = te_get_free_cmd_desc(dev); + if (!cmd_desc) { + SET_ANSWER(answer, + OTE_ERROR_OUT_OF_MEMORY, + OTE_ERROR_ORIGIN_COMMS); + pr_err("failed to get cmd_desc\n"); + goto error; + } + + request = (struct te_request *)cmd_desc->req_addr; + memset(request, 0, sizeof(struct te_request)); + + /* close session cannot fail */ + te_close_session(&cmd.closesession, request); + break; + + case TE_IOCTL_LAUNCH_OPERATION: + operation = &cmd.launchop.operation; + ptr_user_answer = (void *)cmd.launchop.answer; + + cmd_desc = te_get_free_cmd_desc(dev); + params = te_get_free_params(dev, operation->list_count); + + if (!cmd_desc || (operation->list_count && !params)) { + SET_ANSWER(answer, + OTE_ERROR_OUT_OF_MEMORY, + OTE_ERROR_ORIGIN_COMMS); + pr_err("failed to get cmd_desc/params\n"); + goto error; + } + + request = (struct te_request *)cmd_desc->req_addr; + memset(request, 0, sizeof(struct te_request)); + + request->params = params; + request->params_size = operation->list_count; + + if (copy_params_from_user(request, operation)) { + err = -EFAULT; + pr_info("failed to copy params from user\n"); + goto error; + } + + te_launch_operation(&cmd.launchop, request, context); + + SET_ANSWER(answer, request->result, request->result_origin); + break; + + default: + pr_err("Invalid IOCTL Cmd\n"); + err = -EINVAL; + goto error; + } + if (ptr_user_answer && !err) { + if (copy_to_user(ptr_user_answer, &answer, + sizeof(struct te_answer))) { + pr_err("Failed to copy answer\n"); + err = -EFAULT; + } + } + if (request->params && !err) { + if (copy_params_to_user(request, operation)) { + pr_err("Failed to copy return params\n"); + err = -EFAULT; + } + } + +error: + if (cmd_desc) + te_put_used_cmd_desc(dev, cmd_desc); + if (params) + te_put_free_params(dev, params, operation->list_count); + return err; +} + +static long tlk_device_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + int err; + + switch (ioctl_num) { + case TE_IOCTL_OPEN_CLIENT_SESSION: + case TE_IOCTL_CLOSE_CLIENT_SESSION: + case TE_IOCTL_LAUNCH_OPERATION: + err = te_handle_trustedapp_ioctl(file, ioctl_num, ioctl_param); + break; + + case TE_IOCTL_FILE_NEW_REQ: + case TE_IOCTL_FILE_FILL_BUF: + case TE_IOCTL_FILE_REQ_COMPLETE: + err = te_handle_fs_ioctl(file, ioctl_num, ioctl_param); + break; + + default: + pr_err("%s: Invalid IOCTL (0x%x) id 0x%x max 0x%x\n", __func__, + ioctl_num, _IOC_NR(ioctl_num), TE_IOCTL_MAX_NR); + err = -EINVAL; + } + + return err; +} + +/* + * tlk_driver function definitions. + */ +static const struct file_operations tlk_device_fops = { + .owner = THIS_MODULE, + .open = tlk_device_open, + .release = tlk_device_release, + .unlocked_ioctl = tlk_device_ioctl, +}; + +struct miscdevice tlk_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "tlk_device", + .fops = &tlk_device_fops, +}; + +static int __init tlk_init(void) +{ + int ret; + + INIT_LIST_HEAD(&(tlk_dev.used_cmd_list)); + INIT_LIST_HEAD(&(tlk_dev.free_cmd_list)); + + ret = te_create_free_cmd_list(&tlk_dev); + if (ret != 0) + return ret; + + return misc_register(&tlk_misc_device); +} + +module_init(tlk_init); |