Signed-off-by: Jens Wiklander jens.wiklander@linaro.org --- Changes since V3: * Moved stubbed OP-TEE driver to separate patch * Requires all flags to be zero for TEE_IOC_SHM_ALLOC * Frees all shared memory allocations done on the file descriptor to the specific driver when the file descriptor is closed. * Use phys_addr_t instead of dma_addr_t * Adds some more functions to tee_drv.h
V3: The generic TEE subsystem compiles and the devices are created when the kernel boots. Apart from that nothing is tested. I've added a stubbed OP-TEE driver which registers two devices where one is supposed to be used by tee-supplicant and the other by clients.
There's two kinds of shared memory, global dma-buf based, and driver private without dma-buf. The global dma-buf based shared memory is intended to be used when sharing memory from user space to Secure OS, typically what's passed in memrefs. The driver private shared memory is only shared between the driver and Secure OS, this is typically where pointers to the other arguments are stored to prevent user space from both changing pointers and also to not leak kernel pointers to user space. The driver private shared memory is expected to be only small buffers < 4KiB.
--- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/sec-hw/Kconfig | 31 ++++ drivers/sec-hw/Makefile | 2 + drivers/sec-hw/tee.c | 243 +++++++++++++++++++++++++ drivers/sec-hw/tee_private.h | 42 +++++ drivers/sec-hw/tee_shm.c | 403 +++++++++++++++++++++++++++++++++++++++++ include/linux/sec-hw/tee_drv.h | 214 ++++++++++++++++++++++ 8 files changed, 938 insertions(+) create mode 100644 drivers/sec-hw/Kconfig create mode 100644 drivers/sec-hw/Makefile create mode 100644 drivers/sec-hw/tee.c create mode 100644 drivers/sec-hw/tee_private.h create mode 100644 drivers/sec-hw/tee_shm.c create mode 100644 include/linux/sec-hw/tee_drv.h
diff --git a/drivers/Kconfig b/drivers/Kconfig index c0cc96b..4dd892b 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -182,4 +182,6 @@ source "drivers/thunderbolt/Kconfig"
source "drivers/android/Kconfig"
+source "drivers/sec-hw/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 527a6da..f4403d1 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -165,3 +165,4 @@ obj-$(CONFIG_RAS) += ras/ obj-$(CONFIG_THUNDERBOLT) += thunderbolt/ obj-$(CONFIG_CORESIGHT) += coresight/ obj-$(CONFIG_ANDROID) += android/ +obj-y += sec-hw/ diff --git a/drivers/sec-hw/Kconfig b/drivers/sec-hw/Kconfig new file mode 100644 index 0000000..a3292b0 --- /dev/null +++ b/drivers/sec-hw/Kconfig @@ -0,0 +1,31 @@ +# +# Copyright (c) 2015, Linaro Limited +# +# 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, see http://www.gnu.org/licenses/ + +# Generic Trusted Execution Environment Configuration +config TEE_GENERIC + bool "Trusted Execution Environment support" + default n + select DMA_SHARED_BUFFER + help + This implements a generic interface towards a Trusted Execution + Environment (TEE). + +if TEE_GENERIC + +menu "TEE drivers" + +endmenu + +endif diff --git a/drivers/sec-hw/Makefile b/drivers/sec-hw/Makefile new file mode 100644 index 0000000..d1b29cf --- /dev/null +++ b/drivers/sec-hw/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_TEE_GENERIC) += tee.o +obj-$(CONFIG_TEE_GENERIC) += tee_shm.o diff --git a/drivers/sec-hw/tee.c b/drivers/sec-hw/tee.c new file mode 100644 index 0000000..9cb91d9 --- /dev/null +++ b/drivers/sec-hw/tee.c @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2015, 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. + * + */ +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sec-hw/tee_drv.h> +#include <asm/uaccess.h> +#include "tee_private.h" + +static int tee_open(struct inode *inode, struct file *filp) +{ + int ret; + struct tee_device *teedev; + struct tee_filp *teefilp; + + teedev = container_of(filp->private_data, struct tee_device, miscdev); + teefilp = kzalloc(sizeof(*teefilp), GFP_KERNEL); + if (!teefilp) + return -ENOMEM; + + teefilp->teedev = teedev; + filp->private_data = teefilp; + ret = teedev->desc->ops->open(teefilp); + if (ret) + kfree(teefilp); + return ret; +} + +static int tee_release(struct inode *inode, struct file *filp) +{ + struct tee_filp *teefilp = filp->private_data; + + /* Free all shm:s related to this teefilp */ + tee_shm_free_by_teefilp(teefilp); + + teefilp->teedev->desc->ops->release(teefilp); + return 0; +} + +static long tee_ioctl_version(struct tee_filp *teefilp, + struct tee_ioctl_version __user *uvers) +{ + struct tee_ioctl_version vers; + + memset(&vers, 0, sizeof(vers)); + vers.gen_version = TEE_SUBSYS_VERSION; + teefilp->teedev->desc->ops->get_version(teefilp, &vers.spec_version, + vers.uuid); + + return copy_to_user(uvers, &vers, sizeof(vers)); +} + +static long tee_ioctl_cmd(struct tee_filp *teefilp, + struct tee_ioctl_cmd_data __user *ucmd) +{ + long ret; + struct tee_ioctl_cmd_data cmd; + void __user *buf_ptr; + + ret = copy_from_user(&cmd, ucmd, sizeof(cmd)); + if (ret) + return ret; + + buf_ptr = (void __user *)(uintptr_t)cmd.buf_ptr; + return teefilp->teedev->desc->ops->cmd(teefilp, buf_ptr, cmd.buf_len); +} + +static long tee_ioctl_shm_alloc(struct tee_filp *teefilp, + 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(teefilp->teedev, teefilp, data.size, + TEE_SHM_MAPPED | TEE_SHM_DMA_BUF); + if (IS_ERR(shm)) + return PTR_ERR(shm); + + ret = teefilp->teedev->desc->ops->shm_share(shm); + if (ret) + goto err; + + data.flags = shm->flags; + data.size = shm->size; + data.fd = tee_shm_fd(shm); + if (data.fd < 0) { + ret = data.fd; + goto err; + } + + if (copy_to_user(udata, &data, sizeof(data))) { + ret = -EFAULT; + goto err; + } + return 0; +err: + tee_shm_free(shm); + return ret; +} + +static long tee_ioctl_mem_share(struct tee_filp *teefilp, + struct tee_ioctl_mem_share_data __user *udata) +{ + /* Not supported yet */ + return -ENOENT; +} + +static long tee_ioctl_mem_unshare(struct tee_filp *teefilp, + struct tee_ioctl_mem_share_data __user *udata) +{ + /* Not supported yet */ + return -ENOENT; +} + +static long tee_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct tee_filp *teefilp = filp->private_data; + void __user *uarg = (void __user *)arg; + + switch (cmd) { + case TEE_IOC_VERSION: + return tee_ioctl_version(teefilp, uarg); + case TEE_IOC_CMD: + return tee_ioctl_cmd(teefilp, uarg); + case TEE_IOC_SHM_ALLOC: + return tee_ioctl_shm_alloc(teefilp, uarg); + case TEE_IOC_MEM_SHARE: + return tee_ioctl_mem_share(teefilp, uarg); + case TEE_IOC_MEM_UNSHARE: + return tee_ioctl_mem_unshare(teefilp, uarg); + default: + return -EINVAL; + } +} + + +static const struct file_operations tee_fops = { + .owner = THIS_MODULE, + .open = tee_open, + .release = tee_release, + .unlocked_ioctl = tee_ioctl +}; + +struct tee_device *tee_register(const struct tee_desc *teedesc, + struct device *dev, void *driver_data) +{ + static atomic_t device_no = ATOMIC_INIT(-1); + static atomic_t priv_device_no = ATOMIC_INIT(-1); + struct tee_device *teedev; + int ret; + + if (!teedesc || !teedesc->name || !dev) + return NULL; + + teedev = kzalloc(sizeof(*teedev), GFP_KERNEL); + if (!teedev) { + dev_err(dev, "failed to alloc struct tee_device\n"); + return NULL; + } + + teedev->dev = dev; + teedev->desc = teedesc; + teedev->driver_data = driver_data; + + if (teedesc->flags & TEE_DESC_PRIVILEGED) + snprintf(teedev->name, sizeof(teedev->name), + "teepriv%d", atomic_inc_return(&priv_device_no)); + else + snprintf(teedev->name, sizeof(teedev->name), + "tee%d", atomic_inc_return(&device_no)); + + teedev->miscdev.parent = dev; + teedev->miscdev.minor = MISC_DYNAMIC_MINOR; + teedev->miscdev.name = teedev->name; + teedev->miscdev.fops = &tee_fops; + + ret = misc_register(&teedev->miscdev); + if (ret) { + dev_err(dev, "misc_register() failed name="%s"\n", + teedev->name); + goto err; + } + + INIT_LIST_HEAD(&teedev->list_shm); + mutex_init(&teedev->mutex); + + dev_set_drvdata(teedev->miscdev.this_device, teedev); + + dev_info(dev, "register misc device "%s" (minor=%d)\n", + dev_name(teedev->miscdev.this_device), teedev->miscdev.minor); + + return teedev; +err: + kfree(teedev); + return NULL; +} +EXPORT_SYMBOL_GPL(tee_register); + +void tee_unregister(struct tee_device *teedev) +{ + if (!teedev) + return; + + dev_info(teedev->dev, "unregister misc device "%s" (minor=%d)\n", + dev_name(teedev->miscdev.this_device), teedev->miscdev.minor); + misc_deregister(&teedev->miscdev); + /* TODO finish this function */ +} +EXPORT_SYMBOL_GPL(tee_unregister); + +void *tee_get_drvdata(struct tee_device *teedev) +{ + return teedev->driver_data; +} +EXPORT_SYMBOL_GPL(tee_get_drvdata); + +static int __init tee_init(void) +{ + pr_info("initialized tee subsystem\n"); + return 0; +} + +core_initcall(tee_init); diff --git a/drivers/sec-hw/tee_private.h b/drivers/sec-hw/tee_private.h new file mode 100644 index 0000000..7b1668f --- /dev/null +++ b/drivers/sec-hw/tee_private.h @@ -0,0 +1,42 @@ +/* * Copyright (c) 2015, 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. + * + */ +#ifndef TEE_PRIVATE_H +#define TEE_PRIVATE_H + +#define TEE_MAX_DEV_NAME_LEN 32 +struct tee_device { + char name[TEE_MAX_DEV_NAME_LEN]; + const struct tee_desc *desc; + struct device *dev; + struct miscdevice miscdev; + struct list_head list_shm; + struct mutex mutex; + void *driver_data; +}; + +struct tee_shm { + struct list_head list_node; + struct tee_device *teedev; + struct tee_filp *teefilp; + phys_addr_t paddr; + void *kaddr; + size_t size; + struct dma_buf *dmabuf; + u32 flags; +}; + +int tee_shm_fd(struct tee_shm *shm); +void tee_shm_free_by_teefilp(struct tee_filp *teefilp); + + +#endif /*TEE_PRIVATE_H*/ diff --git a/drivers/sec-hw/tee_shm.c b/drivers/sec-hw/tee_shm.c new file mode 100644 index 0000000..885c753 --- /dev/null +++ b/drivers/sec-hw/tee_shm.c @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2015, 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. + * + */ +#include <linux/device.h> +#include <linux/dma-buf.h> +#include <linux/slab.h> +#include <linux/sec-hw/tee_drv.h> +#include "tee_private.h" + +static DEFINE_MUTEX(teeshm_list_mutex); +static LIST_HEAD(teeshm_list); +static LIST_HEAD(teeshm_pending_free_list); + +static void tee_shm_release(struct tee_shm *shm); + +static struct sg_table *tee_shm_op_map_dma_buf(struct dma_buf_attachment + *attach, enum dma_data_direction dir) +{ + return NULL; +} + +static void tee_shm_op_unmap_dma_buf(struct dma_buf_attachment *attach, + struct sg_table *table, enum dma_data_direction dir) +{ +} + +static void tee_shm_op_release(struct dma_buf *dmabuf) +{ + struct tee_shm *shm = dmabuf->priv; + + tee_shm_release(shm); +} + +static void *tee_shm_op_kmap_atomic(struct dma_buf *dmabuf, + unsigned long pgnum) +{ + return NULL; +} + +static void *tee_shm_op_kmap(struct dma_buf *dmabuf, unsigned long pgnum) +{ + return NULL; +} + +static int tee_shm_op_mmap(struct dma_buf *dmabuf, + struct vm_area_struct *vma) +{ + struct tee_shm *shm = dmabuf->priv; + size_t size = vma->vm_end - vma->vm_start; + + return remap_pfn_range(vma, vma->vm_start, shm->paddr >> PAGE_SHIFT, + size, vma->vm_page_prot); +} + +static struct dma_buf_ops tee_shm_dma_buf_ops = { + .map_dma_buf = tee_shm_op_map_dma_buf, + .unmap_dma_buf = tee_shm_op_unmap_dma_buf, + .release = tee_shm_op_release, + .kmap_atomic = tee_shm_op_kmap_atomic, + .kmap = tee_shm_op_kmap, + .mmap = tee_shm_op_mmap, +}; + +struct tee_shm *tee_shm_alloc(struct tee_device *teedev, + struct tee_filp *teefilp, size_t size, u32 flags) +{ + struct tee_shm *shm; + void *ret; + struct mutex *mutex; + struct list_head *list_shm; + + if (!(flags & TEE_SHM_MAPPED)) { + dev_err(teedev->dev, "only mapped allocations supported\n"); + return ERR_PTR(-EINVAL); + } + + if ((flags & ~(TEE_SHM_MAPPED|TEE_SHM_DMA_BUF))) { + dev_err(teedev->dev, "invalid shm flags 0x%x", flags); + return ERR_PTR(-EINVAL); + } + + shm = kzalloc(sizeof(struct tee_shm), GFP_KERNEL); + if (!shm) { + dev_err(teedev->dev, "failed to allocate struct tee_shm\n"); + return ERR_PTR(-ENOMEM); + } + + shm->flags = flags; + + if (flags & TEE_SHM_DMA_BUF) { + int order = get_order(size); + + /* Global shm's must have a teefilp attached */ + if (!teefilp) { + ret = ERR_PTR(-EINVAL); + goto err; + } + + shm->teefilp = teefilp; + shm->size = (1 << order) << PAGE_SHIFT; + /* Request zeroed pages to not leak information */ + shm->kaddr = (void *)__get_free_pages(GFP_KERNEL|__GFP_ZERO, + order); + if (!shm->kaddr) { + dev_err(teedev->dev, + "failed to get order %d pages for shared memory\n", + order); + ret = ERR_PTR(-ENOMEM); + goto err; + } + + shm->dmabuf = dma_buf_export(shm, &tee_shm_dma_buf_ops, + shm->size, O_RDWR, NULL); + if (IS_ERR(shm->dmabuf)) { + ret = ERR_CAST(shm->dmabuf); + goto err; + } + + mutex = &teeshm_list_mutex; + list_shm = &teeshm_list; + } else { + /* Driver private shm's must not have a teefilp attached */ + if (teefilp) { + ret = ERR_PTR(-EINVAL); + goto err; + } + shm->size = size; + shm->kaddr = kzalloc(size, GFP_KERNEL); + if (!shm->kaddr) { + dev_err(teedev->dev, + "failed to allocate %zu bytes of shared memory\n", + size); + ret = ERR_PTR(-ENOMEM); + goto err; + } + + mutex = &teedev->mutex; + list_shm = &teedev->list_shm; + } + + shm->teedev = teedev; + shm->paddr = virt_to_phys(shm->kaddr); + + if (flags & TEE_SHM_DMA_BUF) { + /* + * Only call share on global shm:s, as the driver private + * shm:s always originates from the driver itself. + */ + int rc = teedev->desc->ops->shm_share(shm); + if (rc) { + ret = ERR_PTR(rc); + goto err; + } + } + + mutex_lock(mutex); + list_add_tail(&shm->list_node, list_shm); + mutex_unlock(mutex); + + + + return shm; +err: + if (shm->kaddr) { + if (shm->flags & TEE_SHM_DMA_BUF) + free_pages((unsigned long)shm->kaddr, get_order(size)); + else + kfree(shm->kaddr); + } + kfree(shm); + return ret; +} +EXPORT_SYMBOL_GPL(tee_shm_alloc); + +int tee_shm_fd(struct tee_shm *shm) +{ + u32 req_flags = TEE_SHM_MAPPED | TEE_SHM_DMA_BUF; + + if ((shm->flags & req_flags) != req_flags) + return -EINVAL; + + return dma_buf_fd(shm->dmabuf, O_CLOEXEC); +} + +static void tee_shm_release(struct tee_shm *shm) +{ + struct tee_device *teedev = shm->teedev; + + if (shm->flags & TEE_SHM_DMA_BUF) { + /* Only unshare global shm:s */ + shm->teedev->desc->ops->shm_unshare(shm); + + free_pages((unsigned long)shm->kaddr, get_order(shm->size)); + mutex_lock(&teeshm_list_mutex); + list_del(&shm->list_node); + mutex_unlock(&teeshm_list_mutex); + } else { + kfree(shm->kaddr); + mutex_lock(&teedev->mutex); + list_del(&shm->list_node); + mutex_unlock(&teedev->mutex); + } + + kfree(shm); +} + +void tee_shm_free_by_teefilp(struct tee_filp *teefilp) +{ + struct tee_shm *shm; + struct tee_shm *tmp; + LIST_HEAD(tmp_list); + + /* + * Move all matching shm:s to a temporary list + */ + mutex_lock(&teeshm_list_mutex); + list_for_each_entry_safe(shm, tmp, &teeshm_list, list_node) { + if (shm->teefilp == teefilp) { + list_del(&shm->list_node); + list_add_tail(&shm->list_node, &tmp_list); + } + } + mutex_unlock(&teeshm_list_mutex); + + /* + * For each shm in the temporary list move it to the pending free + * list and call tee_shm_free(). Once the ref_count is 0 the shm + * will be removed from this list. + * + * Since the 'struct tee_filp' is about to be freed (the reason + * this function was called) set the teefilp to NULL. The only + * purpose of the teefilp in 'struct tee_shm' is to be able to find + * all shm:s related to a teefilp. + */ + while (true) { + mutex_lock(&teeshm_list_mutex); + shm = list_first_entry_or_null(&tmp_list, + struct tee_shm, list_node); + if (shm) { + list_del(&shm->list_node); + list_add_tail(&shm->list_node, + &teeshm_pending_free_list); + shm->teefilp = NULL; + } + mutex_unlock(&teeshm_list_mutex); + if (!shm) + break; + tee_shm_free(shm); + } + +} + +void tee_shm_free(struct tee_shm *shm) +{ + /* + * dma_buf_put() decreases the dmabuf reference counter and will + * call tee_shm_release() when the last reference is gone. + * + * In the case of anonymous memory we call tee_shm_release directly + * instead at it doesn't have a reference counter. + */ + if (shm->flags & TEE_SHM_DMA_BUF) + dma_buf_put(shm->dmabuf); + else + tee_shm_release(shm); +} +EXPORT_SYMBOL_GPL(tee_shm_free); + +static bool cmp_key_va(struct tee_shm *shm, uintptr_t va) +{ + uintptr_t shm_va = (uintptr_t)shm->kaddr; + + return (va >= shm_va) && (va < (shm_va + shm->size)); +} + +static bool cmp_key_pa(struct tee_shm *shm, uintptr_t pa) +{ + return (pa >= shm->paddr) && (pa < (shm->paddr + shm->size)); +} + +static struct tee_shm *tee_shm_find_by_key(struct tee_device *teedev, u32 flags, + bool (*cmp)(struct tee_shm *shm, uintptr_t key), + uintptr_t key) +{ + struct tee_shm *ret = NULL; + struct tee_shm *shm; + struct mutex *mutex; + struct list_head *list_shm; + + if (flags & TEE_SHM_DMA_BUF) { + mutex = &teeshm_list_mutex; + list_shm = &teeshm_list; + } else { + mutex = &teedev->mutex; + list_shm = &teedev->list_shm; + } + + mutex_lock(mutex); + list_for_each_entry(shm, list_shm, list_node) { + if (cmp(shm, key)) { + ret = shm; + break; + } + } + mutex_unlock(mutex); + + return ret; +} + +struct tee_shm *tee_shm_find_by_va(struct tee_device *teedev, u32 flags, + void *va) +{ + return tee_shm_find_by_key(teedev, flags, cmp_key_va, (uintptr_t)va); +} +EXPORT_SYMBOL_GPL(tee_shm_find_by_va); + +struct tee_shm *tee_shm_find_by_pa(struct tee_device *teedev, u32 flags, + phys_addr_t pa) +{ + return tee_shm_find_by_key(teedev, flags, cmp_key_pa, pa); +} +EXPORT_SYMBOL_GPL(tee_shm_find_by_pa); + +int tee_shm_va2pa(struct tee_shm *shm, void *va, phys_addr_t *pa) +{ + /* Check that we're in the range of the shm */ + if ((char *)va < (char *)shm->kaddr) + return -EINVAL; + if ((char *)va >= ((char *)shm->kaddr + shm->size)) + return -EINVAL; + + if (pa) + *pa = virt_to_phys(va); + return 0; +} +EXPORT_SYMBOL_GPL(tee_shm_va2pa); + +int tee_shm_pa2va(struct tee_shm *shm, phys_addr_t pa, void **va) +{ + /* Check that we're in the range of the shm */ + if (pa < shm->paddr) + return -EINVAL; + if (pa >= (shm->paddr + shm->size)) + return -EINVAL; + + if (va) + *va = phys_to_virt(pa); + return 0; +} +EXPORT_SYMBOL_GPL(tee_shm_pa2va); + +void *tee_shm_get_va(struct tee_shm *shm, size_t offs) +{ + if (offs >= shm->size) + return ERR_PTR(-EINVAL); + return shm->kaddr; +} +EXPORT_SYMBOL_GPL(tee_shm_get_va); + +int tee_shm_get_pa(struct tee_shm *shm, size_t offs, phys_addr_t *pa) +{ + if (offs >= shm->size) + return -EINVAL; + if (pa) + *pa = shm->paddr + offs; + return 0; +} +EXPORT_SYMBOL_GPL(tee_shm_get_pa); + +static bool is_shm_dma_buf(struct dma_buf *dmabuf) +{ + return dmabuf->ops == &tee_shm_dma_buf_ops; +} + +struct tee_shm *tee_shm_get_from_fd(int fd) +{ + struct dma_buf *dmabuf = dma_buf_get(fd); + + if (IS_ERR(dmabuf)) + return ERR_CAST(dmabuf); + + if (!is_shm_dma_buf(dmabuf)) + return ERR_PTR(-EINVAL); + return dmabuf->priv; +} +EXPORT_SYMBOL_GPL(tee_shm_get_from_fd); + +void tee_shm_put(struct tee_shm *shm) +{ + if (shm->flags & TEE_SHM_DMA_BUF) + dma_buf_put(shm->dmabuf); +} +EXPORT_SYMBOL_GPL(tee_shm_put); diff --git a/include/linux/sec-hw/tee_drv.h b/include/linux/sec-hw/tee_drv.h new file mode 100644 index 0000000..8693363 --- /dev/null +++ b/include/linux/sec-hw/tee_drv.h @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2015, 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. + * + */ + +#ifndef __TEE_DRV_H +#define __TEE_DRV_H + +#include <linux/types.h> +#include <linux/list.h> +#include <linux/miscdevice.h> +#include <linux/sec-hw/tee.h> + +/* + * The file describes the API provided by the generic TEE driver to the + * specific TEE driver. + */ + +#define TEE_SHM_MAPPED 0x1 /* Memory mapped by the kernel */ +#define TEE_SHM_DMA_BUF 0x2 /* Memory with dma-buf handle */ + +#define TEE_UUID_SIZE 16 + +struct tee_device; +struct tee_shm; + + +/** + * struct tee_filp - driver specific file pointer data + * @teedev: pointer to this drivers struct tee_device + * @filp_data: driver specific file pointer data, managed by the driver + */ +struct tee_filp { + struct tee_device *teedev; + void *filp_data; +}; + +/** + * struct tee_driver_ops - driver operations vtable + * @get_version: returns version of driver + * @open: called when the device file is opened + * @release: release this open file + * @cmd: process a command from user space + * @shm_share: share some memory with Secure OS + * @shm_unshare: unshare some memory with Secure OS + */ +struct tee_driver_ops { + void (*get_version)(struct tee_filp *teefilp, u32 *version, u8 *uuid); + int (*open)(struct tee_filp *teefilp); + void (*release)(struct tee_filp *teefilp); + int (*cmd)(struct tee_filp *teefilp, void __user *buf, size_t len); + int (*shm_share)(struct tee_shm *shm); + void (*shm_unshare)(struct tee_shm *shm); +}; + +/** + * struct tee_desc - Describes the TEE driver to the subsystem + * @name: name of driver + * @ops: driver operations vtable + * @owner: module providing the driver + * @flags: Extra properties of driver, defined by TEE_DESC_* below + */ +#define TEE_DESC_PRIVILEGED 0x1 +struct tee_desc { + const char *name; + const struct tee_driver_ops *ops; + struct module *owner; + u32 flags; +}; + + +/** + * tee_register() - Register a specific TEE driver + * @teedesc: Descriptor for this driver + * @dev: Parent device for this driver + * @driver_data: Private driver data for this driver + * + * Once the specific driver has been probed it registers in the generic + * driver with this function. + * + * @returns a pointer to struct tee_device + */ +struct tee_device *tee_register(const struct tee_desc *teedesc, + struct device *dev, void *driver_data); + +/** + * tee_unregister() - Unregister a specific TEE driver + * @teedev: Driver to unregister + */ +void tee_unregister(struct tee_device *teedev); + +/** + * tee_get_drvdata() - Return driver_data pointer + * @returns the driver_data pointer supplied to tee_register(). + */ +void *tee_get_drvdata(struct tee_device *teedev); + +/** + * tee_shm_alloc() - Allocate shared memory + * @teedev: Driver that allocates the shared memory + * @teefilp: TEE file pointer when allocating global shared memory, must be + * NULL for driver private shared memory. + * @size: Requested size of shared memory + * @flags: Flags setting properties for the requested shared memory. + * + * Memory allocated as global shared memory is automatically freed when the + * TEE file pointer is closed. The @flags field uses the bits defined by + * TEE_SHM_* above. TEE_SHM_MAPPED must currently always be set. If + * TEE_SHM_DMA_BUF global shared memory will be allocated and associated + * with a dma-buf handle, else driver private memory. + * + * @returns a pointer to 'struct tee_shm' + */ +struct tee_shm *tee_shm_alloc(struct tee_device *teedev, + struct tee_filp *teefilp, size_t size, u32 flags); + +/** + * tee_shm_free() - Free shared memory + * @shm: Handle to shared memory to free + */ +void tee_shm_free(struct tee_shm *shm); + +/** + * tee_shm_find_by_va() - Find a shared memory handle by a virtual address + * @teedev: The device that owns the shared memory + * @flags: Select which type of shared memory to locate, if + * TEE_SHM_DMA_BUF global shared memory else driver private + * shared memory. + * @va: Virtual address covered by the shared memory + * @returns a Handle to shared memory + */ +struct tee_shm *tee_shm_find_by_va(struct tee_device *teedev, u32 flags, + void *va); +/** + * tee_shm_find_by_pa() - Find a shared memory handle by a physical address + * @teedev: The device that owns the shared memory + * @flags: Select which type of shared memory to locate, if + * TEE_SHM_DMA_BUF global shared memory else driver private + * shared memory. + * @pa: Physical address covered by the shared memory + * @returns a Handle to shared memory + */ +struct tee_shm *tee_shm_find_by_pa(struct tee_device *teedev, u32 flags, + phys_addr_t pa); + +/** + * tee_shm_va2pa() - Get physical address of a virtual address + * @shm: Shared memory handle + * @va: Virtual address to tranlsate + * @pa: Returned physical address + * @returns 0 on success and < 0 on failure + */ +int tee_shm_va2pa(struct tee_shm *shm, void *va, phys_addr_t *pa); + +/** + * tee_shm_pa2va() - Get virtual address of a physical address + * @shm: Shared memory handle + * @pa: Physical address to tranlsate + * @va: Returned virtual address + * @returns 0 on success and < 0 on failure + */ +int tee_shm_pa2va(struct tee_shm *shm, phys_addr_t pa, void **va); + +/** + * tee_shm_get_size() - Get size of a shared memory + * @returns the size of the shared memory + */ +size_t tee_shm_get_size(struct tee_shm *shm); + +/** + * tee_shm_get_va() - Get virtual address of a shared memory plus an offset + * @shm: Shared memory handle + * @offs: Offset from start of this shared memory + * @returns virtual address of the shared memory + offs if offs is within + * the bounds of this shared memory, else an ERR_PTR + */ +void *tee_shm_get_va(struct tee_shm *shm, size_t offs); + +/** + * tee_shm_get_pa() - Get physical address of a shared memory plus an offset + * @shm: Shared memory handle + * @offs: Offset from start of this shared memory + * @pa: Physical address to return + * @returns 0 if offs is within the bounds of this shared memory, else an + * error code. + */ +int tee_shm_get_pa(struct tee_shm *shm, size_t offs, phys_addr_t *pa); + +/** + * tee_shm_get_from_fd() - Get a shared memory handle from a file descriptor + * @fd: A user space file descriptor + * + * This function increases the reference counter on the shared memory and + * returns a handle. + * @returns handle to shared memory + */ +struct tee_shm *tee_shm_get_from_fd(int fd); + +/** + * tee_shm_put() - Decrease reference count on a shared memory handle + * @shm: Shared memory handle + */ +void tee_shm_put(struct tee_shm *shm); + +#endif /*__TEE_DRV_H*/