Add OP-TEE mediator, so guests can access OP-TEE services.
OP-TEE mediator support address translation for DomUs. It tracks execution of STD calls, correctly handles memory-related RPC requests, tracks buffer allocated for RPCs.
With this patch OP-TEE sucessfully passes own tests, while client is running in DomU.
Signed-off-by: Volodymyr Babchuk volodymyr_babchuk@epam.com ---
Changes from "RFC": - Removed special case for Dom0/HwDOM - No more support for plain OP-TEE (only OP-TEE with virtualization config enabled is supported) - Multiple domains is now supported - Pages that are shared between OP-TEE and domain are now pinned - Renamed CONFIG_ARM_OPTEE to CONFIG_OPTEE - Command buffers from domain are now shadowed by XEN - Mediator now filters out unknown capabilities and requests - call contexts, shared memory object now stored per-domain
xen/arch/arm/tee/Kconfig | 4 + xen/arch/arm/tee/Makefile | 1 + xen/arch/arm/tee/optee.c | 972 ++++++++++++++++++++++++++++++++++++ xen/include/asm-arm/tee/optee_smc.h | 50 ++ 4 files changed, 1027 insertions(+) create mode 100644 xen/arch/arm/tee/optee.c
diff --git a/xen/arch/arm/tee/Kconfig b/xen/arch/arm/tee/Kconfig index e69de29..5b829db 100644 --- a/xen/arch/arm/tee/Kconfig +++ b/xen/arch/arm/tee/Kconfig @@ -0,0 +1,4 @@ +config OPTEE + bool "Enable OP-TEE mediator" + default n + depends on TEE diff --git a/xen/arch/arm/tee/Makefile b/xen/arch/arm/tee/Makefile index c54d479..982c879 100644 --- a/xen/arch/arm/tee/Makefile +++ b/xen/arch/arm/tee/Makefile @@ -1 +1,2 @@ obj-y += tee.o +obj-$(CONFIG_OPTEE) += optee.o diff --git a/xen/arch/arm/tee/optee.c b/xen/arch/arm/tee/optee.c new file mode 100644 index 0000000..7809406 --- /dev/null +++ b/xen/arch/arm/tee/optee.c @@ -0,0 +1,972 @@ +/* + * xen/arch/arm/tee/optee.c + * + * OP-TEE mediator + * + * Volodymyr Babchuk volodymyr_babchuk@epam.com + * Copyright (c) 2018 EPAM Systems. + * + * 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 <xen/domain_page.h> +#include <xen/types.h> +#include <xen/sched.h> + +#include <asm/p2m.h> +#include <asm/tee/tee.h> + +#include <asm/tee/optee_msg.h> +#include <asm/tee/optee_smc.h> + +#define MAX_NONCONTIG_ENTRIES 5 + +/* * Call context. OP-TEE can issue multiple RPC returns during one call. + * We need to preserve context during them. + */ +struct std_call_ctx { + struct list_head list; + struct optee_msg_arg *guest_arg; + struct optee_msg_arg *xen_arg; + void *non_contig[MAX_NONCONTIG_ENTRIES]; + int non_contig_order[MAX_NONCONTIG_ENTRIES]; + mfn_t guest_arg_mfn; + int optee_thread_id; + int rpc_op; +}; + +/* Pre-allocated SHM buffer for RPC commands */ +struct shm_rpc { + struct list_head list; + struct optee_msg_arg *guest_arg; + struct page *guest_page; + mfn_t guest_mfn; + uint64_t cookie; +}; + +/* Shared memory buffer for arbitrary data */ +struct shm_buf { + struct list_head list; + uint64_t cookie; + int page_cnt; + struct page_info *pages[]; +}; + +struct domain_ctx { + struct list_head list; + struct list_head call_ctx_list; + struct list_head shm_rpc_list; + struct list_head shm_buf_list; + struct domain *domain; + spinlock_t lock; +}; + +static LIST_HEAD(domain_ctx_list); +static DEFINE_SPINLOCK(domain_ctx_list_lock); + +static int optee_probe(void) +{ + struct dt_device_node *node; + struct smccc_res resp; + + /* Check for entry in dtb */ + node = dt_find_compatible_node(NULL, NULL, "linaro,optee-tz"); + if ( !node ) + return -ENODEV; + + /* Check UID */ + call_smccc_smc(ARM_SMCCC_CALL_UID_FID(TRUSTED_OS_END), 0, 0, 0, 0, 0, 0, 0, + &resp); + + if ( resp.a0 != OPTEE_MSG_UID_0 || + resp.a1 != OPTEE_MSG_UID_1 || + resp.a2 != OPTEE_MSG_UID_2 || + resp.a3 != OPTEE_MSG_UID_3 ) + return -ENODEV; + + printk("OP-TEE mediator initialized\n"); + return 0; +} + +static mfn_t lookup_and_pin_guest_ram_addr(paddr_t gaddr, + struct page_info **pg) +{ + mfn_t mfn; + gfn_t gfn; + p2m_type_t t; + struct page_info *page; + struct domain *d = current->domain; + + gfn = gaddr_to_gfn(gaddr); + mfn = p2m_lookup(d, gfn, &t); + + if ( t != p2m_ram_rw || mfn_eq(mfn, INVALID_MFN) ) + return INVALID_MFN; + + page = mfn_to_page(mfn); + if ( !page ) + return INVALID_MFN; + + if ( !get_page(page, d) ) + return INVALID_MFN; + + if ( pg ) + *pg = page; + + return mfn; +} + +static void unpin_guest_ram_addr(mfn_t mfn) +{ + struct page_info *page; + page = mfn_to_page(mfn); + if ( !page ) + return; + + put_page(page); +} + +static struct domain_ctx *find_domain_ctx(struct domain* d) +{ + struct domain_ctx *ctx; + + spin_lock(&domain_ctx_list_lock); + + list_for_each_entry( ctx, &domain_ctx_list, list ) + { + if ( ctx->domain == d ) + { + spin_unlock(&domain_ctx_list_lock); + return ctx; + } + } + + spin_unlock(&domain_ctx_list_lock); + return NULL; +} + +static int optee_domain_create(struct domain *d) +{ + struct smccc_res resp; + struct domain_ctx *ctx; + + ctx = xzalloc(struct domain_ctx); + if ( !ctx ) + return -ENOMEM; + + call_smccc_smc(OPTEE_SMC_VM_CREATED, + d->domain_id + 1, 0, 0, 0, 0, 0, 0, &resp); + if ( resp.a0 != OPTEE_SMC_RETURN_OK ) { + gprintk(XENLOG_WARNING, "OP-TEE don't want to support domain: %d\n", + (uint32_t)resp.a0); + xfree(ctx); + return -ENODEV; + } + + ctx->domain = d; + INIT_LIST_HEAD(&ctx->call_ctx_list); + INIT_LIST_HEAD(&ctx->shm_rpc_list); + INIT_LIST_HEAD(&ctx->shm_buf_list); + spin_lock_init(&ctx->lock); + + spin_lock(&domain_ctx_list_lock); + list_add_tail(&ctx->list, &domain_ctx_list); + spin_unlock(&domain_ctx_list_lock); + + return 0; +} + +static bool forward_call(struct cpu_user_regs *regs) +{ + struct smccc_res resp; + + call_smccc_smc(get_user_reg(regs, 0), + get_user_reg(regs, 1), + get_user_reg(regs, 2), + get_user_reg(regs, 3), + get_user_reg(regs, 4), + get_user_reg(regs, 5), + get_user_reg(regs, 6), + /* client id 0 is reserved for hypervisor itself */ + current->domain->domain_id + 1, + &resp); + + set_user_reg(regs, 0, resp.a0); + set_user_reg(regs, 1, resp.a1); + set_user_reg(regs, 2, resp.a2); + set_user_reg(regs, 3, resp.a3); + set_user_reg(regs, 4, 0); + set_user_reg(regs, 5, 0); + set_user_reg(regs, 6, 0); + set_user_reg(regs, 7, 0); + + return resp.a0 == OPTEE_SMC_RETURN_OK; +} + +static void set_return(struct cpu_user_regs *regs, uint32_t ret) +{ + set_user_reg(regs, 0, ret); + set_user_reg(regs, 1, 0); + set_user_reg(regs, 2, 0); + set_user_reg(regs, 3, 0); + set_user_reg(regs, 4, 0); + set_user_reg(regs, 5, 0); + set_user_reg(regs, 6, 0); + set_user_reg(regs, 7, 0); +} + +static struct shm_buf *allocate_shm_buf(struct domain_ctx *ctx, + uint64_t cookie, + int page_cnt) +{ + struct shm_buf *shm_buf; + + shm_buf = xzalloc_bytes(sizeof(struct shm_buf) + + page_cnt * sizeof(struct page *)); + + if ( !shm_buf ) + return NULL; + + shm_buf->cookie = cookie; + + spin_lock(&ctx->lock); + list_add_tail(&shm_buf->list, &ctx->shm_buf_list); + spin_unlock(&ctx->lock); + + return shm_buf; +} + +static void free_shm_buf(struct domain_ctx *ctx, uint64_t cookie) +{ + struct shm_buf *shm_buf; + bool found = false; + spin_lock(&ctx->lock); + + list_for_each_entry( shm_buf, &ctx->shm_buf_list, list ) + { + if ( shm_buf->cookie == cookie ) + { + found = true; + list_del(&shm_buf->list); + break; + } + } + spin_unlock(&ctx->lock); + + if ( !found ) { + return; + } + + for ( int i = 0; i < shm_buf->page_cnt; i++ ) + if ( shm_buf->pages[i] ) + put_page(shm_buf->pages[i]); + + xfree(shm_buf); +} + +static struct std_call_ctx *allocate_std_call_ctx(struct domain_ctx *ctx) +{ + struct std_call_ctx *call; + + call = xzalloc(struct std_call_ctx); + if ( !call ) + return NULL; + + call->optee_thread_id = -1; + + spin_lock(&ctx->lock); + list_add_tail(&call->list, &ctx->call_ctx_list); + spin_unlock(&ctx->lock); + + return call; +} + +static void free_std_call_ctx(struct domain_ctx *ctx, struct std_call_ctx *call) +{ + spin_lock(&ctx->lock); + list_del(&call->list); + spin_unlock(&ctx->lock); + + if ( call->xen_arg ) + free_xenheap_page(call->xen_arg); + + if ( call->guest_arg ) { + unmap_domain_page_global(call->guest_arg); + unpin_guest_ram_addr(call->guest_arg_mfn); + } + + for ( int i = 0; i < MAX_NONCONTIG_ENTRIES; i++ ) { + if ( call->non_contig[i] ) + free_xenheap_pages(call->non_contig[i], call->non_contig_order[i]); + } + + xfree(call); +} + +static struct std_call_ctx *find_call_ctx(struct domain_ctx *ctx, int thread_id) +{ + struct std_call_ctx *call; + + spin_lock(&ctx->lock); + list_for_each_entry( call, &ctx->call_ctx_list, list ) + { + if ( call->optee_thread_id == thread_id ) + { + spin_unlock(&ctx->lock); + return call; + } + } + spin_unlock(&ctx->lock); + + return NULL; +} + +#define PAGELIST_ENTRIES_PER_PAGE \ + ((OPTEE_MSG_NONCONTIG_PAGE_SIZE / sizeof(u64)) - 1) + +static size_t get_pages_list_size(size_t num_entries) +{ + int pages = DIV_ROUND_UP(num_entries, PAGELIST_ENTRIES_PER_PAGE); + + return pages * OPTEE_MSG_NONCONTIG_PAGE_SIZE; +} + +static struct shm_rpc *allocate_and_map_shm_rpc(struct domain_ctx *ctx, paddr_t gaddr, + uint64_t cookie) +{ + struct shm_rpc *shm_rpc; + + shm_rpc = xzalloc(struct shm_rpc); + if ( !shm_rpc ) + return NULL; + + shm_rpc->guest_mfn = lookup_and_pin_guest_ram_addr(gaddr, NULL); + + if ( mfn_eq(shm_rpc->guest_mfn, INVALID_MFN) ) + { + xfree(shm_rpc); + return NULL; + } + + shm_rpc->guest_arg = map_domain_page_global(shm_rpc->guest_mfn); + if ( !shm_rpc->guest_arg ) + { + gprintk(XENLOG_INFO, "Could not map domain page\n"); + xfree(shm_rpc); + return NULL; + } + shm_rpc->cookie = cookie; + + spin_lock(&ctx->lock); + list_add_tail(&shm_rpc->list, &ctx->shm_rpc_list); + spin_unlock(&ctx->lock); + + return shm_rpc; +} + +static void free_shm_rpc(struct domain_ctx *ctx, uint64_t cookie) +{ + struct shm_rpc *shm_rpc; + bool found = false; + + spin_lock(&ctx->lock); + + list_for_each_entry( shm_rpc, &ctx->shm_rpc_list, list ) + { + if ( shm_rpc->cookie == cookie ) + { + found = true; + list_del(&shm_rpc->list); + break; + } + } + spin_unlock(&ctx->lock); + + if ( !found ) { + return; + } + + if ( shm_rpc->guest_arg ) { + unpin_guest_ram_addr(shm_rpc->guest_mfn); + unmap_domain_page_global(shm_rpc->guest_arg); + } + + xfree(shm_rpc); +} + +static void optee_domain_destroy(struct domain *d) +{ + struct smccc_res resp; + struct domain_ctx *ctx; + struct std_call_ctx *call, *call_tmp; + struct shm_rpc *shm_rpc, *shm_rpc_tmp; + struct shm_buf *shm_buf, *shm_buf_tmp; + bool found = false; + + /* At this time all domain VCPUs should be stopped */ + + /* Inform OP-TEE that domain is shutting down */ + call_smccc_smc(OPTEE_SMC_VM_DESTROYED, + d->domain_id + 1, 0, 0, 0, 0, 0, 0, &resp); + + /* Remove context from the list */ + spin_lock(&domain_ctx_list_lock); + list_for_each_entry( ctx, &domain_ctx_list, list ) + { + if ( ctx->domain == d ) + { + found = true; + list_del(&ctx->list); + break; + } + } + spin_unlock(&domain_ctx_list_lock); + + if ( !found ) + return; + + ASSERT(!spin_is_locked(&ctx->lock)); + + list_for_each_entry_safe( call, call_tmp, &ctx->call_ctx_list, list ) + free_std_call_ctx(ctx, call); + + list_for_each_entry_safe( shm_rpc, shm_rpc_tmp, &ctx->shm_rpc_list, list ) + free_shm_rpc(ctx, shm_rpc->cookie); + + list_for_each_entry_safe( shm_buf, shm_buf_tmp, &ctx->shm_buf_list, list ) + free_shm_buf(ctx, shm_buf->cookie); + + xfree(ctx); +} + +static struct shm_rpc *find_shm_rpc(struct domain_ctx *ctx, uint64_t cookie) +{ + struct shm_rpc *shm_rpc; + + spin_lock(&ctx->lock); + list_for_each_entry( shm_rpc, &ctx->shm_rpc_list, list ) + { + if ( shm_rpc->cookie == cookie ) + { + spin_unlock(&ctx->lock); + return shm_rpc; + } + } + spin_unlock(&ctx->lock); + + return NULL; +} + +static bool translate_noncontig(struct domain_ctx *ctx, + struct std_call_ctx *call, + struct optee_msg_param *param, + int idx) +{ + /* + * Refer to OPTEE_MSG_ATTR_NONCONTIG description in optee_msg.h for details. + */ + uint64_t size; + int page_offset; + int num_pages; + int order; + int entries_on_page = 0; + paddr_t gaddr; + mfn_t guest_mfn; + struct { + uint64_t pages_list[PAGELIST_ENTRIES_PER_PAGE]; + uint64_t next_page_data; + } *pages_data_guest, *pages_data_xen, *pages_data_xen_start; + struct shm_buf *shm_buf; + + page_offset = param->u.tmem.buf_ptr & (OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1); + + size = ROUNDUP(param->u.tmem.size + page_offset, + OPTEE_MSG_NONCONTIG_PAGE_SIZE); + + num_pages = DIV_ROUND_UP(size, OPTEE_MSG_NONCONTIG_PAGE_SIZE); + + order = get_order_from_bytes(get_pages_list_size(num_pages)); + + pages_data_xen_start = alloc_xenheap_pages(order, 0); + if ( !pages_data_xen_start ) + return false; + + shm_buf = allocate_shm_buf(ctx, param->u.tmem.shm_ref, num_pages); + if ( !shm_buf ) + goto err_free; + + gaddr = param->u.tmem.buf_ptr & ~(OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1); + guest_mfn = lookup_and_pin_guest_ram_addr(gaddr, NULL); + if ( mfn_eq(guest_mfn, INVALID_MFN) ) + goto err_free; + + pages_data_guest = map_domain_page(guest_mfn); + if ( !pages_data_guest ) + goto err_free; + + pages_data_xen = pages_data_xen_start; + while ( num_pages ) { + struct page_info *page; + mfn_t entry_mfn = lookup_and_pin_guest_ram_addr( + pages_data_guest->pages_list[entries_on_page], &page); + + if ( mfn_eq(entry_mfn, INVALID_MFN) ) + goto err_unmap; + + shm_buf->pages[shm_buf->page_cnt++] = page; + pages_data_xen->pages_list[entries_on_page] = mfn_to_maddr(entry_mfn); + entries_on_page++; + + if ( entries_on_page == PAGELIST_ENTRIES_PER_PAGE ) { + pages_data_xen->next_page_data = virt_to_maddr(pages_data_xen + 1); + pages_data_xen++; + gaddr = pages_data_guest->next_page_data; + + unmap_domain_page(pages_data_guest); + unpin_guest_ram_addr(guest_mfn); + + guest_mfn = lookup_and_pin_guest_ram_addr(gaddr, NULL); + if ( mfn_eq(guest_mfn, INVALID_MFN) ) + goto err_free; + + pages_data_guest = map_domain_page(guest_mfn); + if ( !pages_data_guest ) + goto err_free; + /* Roll over to the next page */ + entries_on_page = 0; + } + num_pages--; + } + + param->u.tmem.buf_ptr = virt_to_maddr(pages_data_xen_start) | page_offset; + + call->non_contig[idx] = pages_data_xen_start; + call->non_contig_order[idx] = order; + + unmap_domain_page(pages_data_guest); + unpin_guest_ram_addr(guest_mfn); + return true; + +err_unmap: + unmap_domain_page(pages_data_guest); + unpin_guest_ram_addr(guest_mfn); + free_shm_buf(ctx, shm_buf->cookie); + +err_free: + free_xenheap_pages(pages_data_xen_start, order); + + return false; +} + +static bool translate_params(struct domain_ctx *ctx, + struct std_call_ctx *call) +{ + unsigned int i; + uint32_t attr; + + for ( i = 0; i < call->xen_arg->num_params; i++ ) { + attr = call->xen_arg->params[i].attr; + + switch ( attr & OPTEE_MSG_ATTR_TYPE_MASK ) { + case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: + if ( attr & OPTEE_MSG_ATTR_NONCONTIG ) { + if ( !translate_noncontig(ctx, call, + call->xen_arg->params + i, i) ) + return false; + } + else { + gprintk(XENLOG_WARNING, "Guest tries to use old tmem arg\n"); + return false; + } + break; + case OPTEE_MSG_ATTR_TYPE_NONE: + case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT: + continue; + } + } + return true; +} + +/* + * Copy command buffer into xen memory to: + * 1) Hide translated addresses from guest + * 2) Make sure that guest wouldn't change data in command buffer during call + */ +static bool copy_std_request(struct cpu_user_regs *regs, + struct std_call_ctx *call) +{ + paddr_t cmd_gaddr, xen_addr; + + cmd_gaddr = (paddr_t)get_user_reg(regs, 1) << 32 | + get_user_reg(regs, 2); + + /* + * Command buffer should start at page boundary. + * This is OP-TEE ABI requirement. + */ + if ( cmd_gaddr & (OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1) ) + return false; + + call->guest_arg_mfn = lookup_and_pin_guest_ram_addr(cmd_gaddr, NULL); + if ( mfn_eq(call->guest_arg_mfn, INVALID_MFN) ) + return false; + + call->guest_arg = map_domain_page_global(call->guest_arg_mfn); + if ( !call->guest_arg ) { + unpin_guest_ram_addr(call->guest_arg_mfn); + return false; + } + + call->xen_arg = alloc_xenheap_page(); + if ( !call->xen_arg ) { + unpin_guest_ram_addr(call->guest_arg_mfn); + return false; + } + + memcpy(call->xen_arg, call->guest_arg, OPTEE_MSG_NONCONTIG_PAGE_SIZE); + + xen_addr = virt_to_maddr(call->xen_arg); + + set_user_reg(regs, 1, xen_addr >> 32); + set_user_reg(regs, 2, xen_addr & 0xFFFFFFFF); + + return true; +} + +static bool copy_std_request_back(struct domain_ctx *ctx, + struct cpu_user_regs *regs, + struct std_call_ctx *call) +{ + unsigned int i; + uint32_t attr; + + call->guest_arg->ret = call->xen_arg->ret; + call->guest_arg->ret_origin = call->xen_arg->ret_origin; + call->guest_arg->session = call->xen_arg->session; + for ( i = 0; i < call->xen_arg->num_params; i++ ) { + attr = call->xen_arg->params[i].attr; + + switch ( attr & OPTEE_MSG_ATTR_TYPE_MASK ) { + case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: + call->guest_arg->params[i].u.tmem.size = + call->xen_arg->params[i].u.tmem.size; + /* fall through */ + case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: + free_shm_buf(ctx, call->xen_arg->params[i].u.tmem.shm_ref); + continue; + case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT: + call->guest_arg->params[i].u.value.a = + call->xen_arg->params[i].u.value.a; + call->guest_arg->params[i].u.value.b = + call->xen_arg->params[i].u.value.b; + continue; + case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT: + call->guest_arg->params[i].u.rmem.size = + call->xen_arg->params[i].u.rmem.size; + continue; + case OPTEE_MSG_ATTR_TYPE_NONE: + case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT: + continue; + } + } + + return true; +} + +static bool execute_std_call(struct domain_ctx *ctx, + struct cpu_user_regs *regs, + struct std_call_ctx *call) +{ + register_t optee_ret; + forward_call(regs); + optee_ret = get_user_reg(regs, 0); + + if ( OPTEE_SMC_RETURN_IS_RPC(optee_ret) ) + { + call->optee_thread_id = get_user_reg(regs, 3); + call->rpc_op = OPTEE_SMC_RETURN_GET_RPC_FUNC(optee_ret); + return true; + } + + copy_std_request_back(ctx, regs, call); + + if ( call->xen_arg->cmd == OPTEE_MSG_CMD_UNREGISTER_SHM && + call->xen_arg->ret == 0 ) { + free_shm_buf(ctx, call->xen_arg->params[0].u.rmem.shm_ref); + } + + free_std_call_ctx(ctx, call); + + return true; +} + +static bool handle_std_call(struct domain_ctx *ctx, struct cpu_user_regs *regs) +{ + struct std_call_ctx *call; + bool ret; + + call = allocate_std_call_ctx(ctx); + + if (!call) + return false; + + ret = copy_std_request(regs, call); + if ( !ret ) + goto out; + + /* Now we can safely examine contents of command buffer */ + if ( OPTEE_MSG_GET_ARG_SIZE(call->xen_arg->num_params) > + OPTEE_MSG_NONCONTIG_PAGE_SIZE ) { + ret = false; + goto out; + } + + switch ( call->xen_arg->cmd ) + { + case OPTEE_MSG_CMD_OPEN_SESSION: + case OPTEE_MSG_CMD_CLOSE_SESSION: + case OPTEE_MSG_CMD_INVOKE_COMMAND: + case OPTEE_MSG_CMD_CANCEL: + case OPTEE_MSG_CMD_REGISTER_SHM: + case OPTEE_MSG_CMD_UNREGISTER_SHM: + ret = translate_params(ctx, call); + break; + default: + ret = false; + } + + if (!ret) + goto out; + + ret = execute_std_call(ctx, regs, call); + +out: + if (!ret) + free_std_call_ctx(ctx, call); + + return ret; +} + +static void handle_rpc_cmd_alloc(struct domain_ctx *ctx, + struct cpu_user_regs *regs, + struct std_call_ctx *call, + struct shm_rpc *shm_rpc) +{ + if ( shm_rpc->guest_arg->params[0].attr != (OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | + OPTEE_MSG_ATTR_NONCONTIG) ) + { + gprintk(XENLOG_WARNING, "Invalid attrs for shared mem buffer\n"); + return; + } + + /* Last entry in non_contig array is used to hold RPC-allocated buffer */ + if ( call->non_contig[MAX_NONCONTIG_ENTRIES - 1] ) + { + free_xenheap_pages(call->non_contig[MAX_NONCONTIG_ENTRIES - 1], + call->non_contig_order[MAX_NONCONTIG_ENTRIES - 1]); + call->non_contig[MAX_NONCONTIG_ENTRIES - 1] = NULL; + } + translate_noncontig(ctx, call, shm_rpc->guest_arg->params + 0, + MAX_NONCONTIG_ENTRIES - 1); +} + +static void handle_rpc_cmd(struct domain_ctx *ctx, struct cpu_user_regs *regs, + struct std_call_ctx *call) +{ + struct shm_rpc *shm_rpc; + uint64_t cookie; + + cookie = get_user_reg(regs, 1) << 32 | get_user_reg(regs, 2); + + shm_rpc = find_shm_rpc(ctx, cookie); + + if ( !shm_rpc ) + { + gprintk(XENLOG_ERR, "Can't find SHM-RPC with cookie %lx\n", cookie); + return; + } + + switch (shm_rpc->guest_arg->cmd) { + case OPTEE_MSG_RPC_CMD_GET_TIME: + break; + case OPTEE_MSG_RPC_CMD_WAIT_QUEUE: + break; + case OPTEE_MSG_RPC_CMD_SUSPEND: + break; + case OPTEE_MSG_RPC_CMD_SHM_ALLOC: + handle_rpc_cmd_alloc(ctx, regs, call, shm_rpc); + break; + case OPTEE_MSG_RPC_CMD_SHM_FREE: + free_shm_buf(ctx, shm_rpc->guest_arg->params[0].u.value.b); + break; + default: + break; + } +} + +static void handle_rpc_func_alloc(struct domain_ctx *ctx, + struct cpu_user_regs *regs, + struct std_call_ctx *call) +{ + paddr_t ptr = get_user_reg(regs, 1) << 32 | get_user_reg(regs, 2); + + if ( ptr & (OPTEE_MSG_NONCONTIG_PAGE_SIZE - 1) ) + gprintk(XENLOG_WARNING, "Domain returned invalid RPC command buffer\n"); + + if ( ptr ) { + uint64_t cookie = get_user_reg(regs, 4) << 32 | get_user_reg(regs, 5); + struct shm_rpc *shm_rpc; + + shm_rpc = allocate_and_map_shm_rpc(ctx, ptr, cookie); + if ( !shm_rpc ) + { + gprintk(XENLOG_WARNING, "Failed to allocate shm_rpc object\n"); + ptr = 0; + } + else + ptr = mfn_to_maddr(shm_rpc->guest_mfn); + + set_user_reg(regs, 1, ptr >> 32); + set_user_reg(regs, 2, ptr & 0xFFFFFFFF); + } +} + +static bool handle_rpc(struct domain_ctx *ctx, struct cpu_user_regs *regs) +{ + struct std_call_ctx *call; + + int optee_thread_id = get_user_reg(regs, 3); + + call = find_call_ctx(ctx, optee_thread_id); + + if ( !call ) + return false; + + switch ( call->rpc_op ) { + case OPTEE_SMC_RPC_FUNC_ALLOC: + handle_rpc_func_alloc(ctx, regs, call); + break; + case OPTEE_SMC_RPC_FUNC_FREE: + { + uint64_t cookie = get_user_reg(regs, 1) << 32 | get_user_reg(regs, 2); + free_shm_rpc(ctx, cookie); + break; + } + case OPTEE_SMC_RPC_FUNC_FOREIGN_INTR: + break; + case OPTEE_SMC_RPC_FUNC_CMD: + handle_rpc_cmd(ctx, regs, call); + break; + } + + return execute_std_call(ctx, regs, call); +} + +static bool handle_exchange_capabilities(struct cpu_user_regs *regs) +{ + uint32_t caps; + + /* Filter out unknown guest caps */ + caps = get_user_reg(regs, 1); + caps &= OPTEE_SMC_NSEC_CAP_UNIPROCESSOR; + set_user_reg(regs, 1, caps); + + /* Forward call and return error (if any) back to the guest */ + if ( !forward_call(regs) ) + return true; + + caps = get_user_reg(regs, 1); + + /* Filter out unknown OP-TEE caps */ + caps &= OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM | + OPTEE_SMC_SEC_CAP_UNREGISTERED_SHM | + OPTEE_SMC_SEC_CAP_DYNAMIC_SHM; + + /* Drop static SHM_RPC cap */ + caps &= ~OPTEE_SMC_SEC_CAP_HAVE_RESERVED_SHM; + + /* Don't allow guests to work without dynamic SHM */ + if ( !(caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM) ) { + set_return(regs, OPTEE_SMC_RETURN_ENOTAVAIL); + return true; + } + + set_user_reg(regs, 1, caps); + return true; +} + +static bool optee_handle_smc(struct cpu_user_regs *regs) +{ + struct domain_ctx *ctx; + + ctx = find_domain_ctx(current->domain); + if ( !ctx ) + return false; + + switch ( get_user_reg(regs, 0) ) + { + case OPTEE_SMC_CALLS_COUNT: + case OPTEE_SMC_CALLS_UID: + case OPTEE_SMC_CALLS_REVISION: + case OPTEE_SMC_CALL_GET_OS_UUID: + case OPTEE_SMC_FUNCID_GET_OS_REVISION: + case OPTEE_SMC_ENABLE_SHM_CACHE: + case OPTEE_SMC_DISABLE_SHM_CACHE: + forward_call(regs); + return true; + case OPTEE_SMC_GET_SHM_CONFIG: + /* No static SHM available for guests */ + set_return(regs, OPTEE_SMC_RETURN_ENOTAVAIL); + return true; + case OPTEE_SMC_EXCHANGE_CAPABILITIES: + return handle_exchange_capabilities(regs); + case OPTEE_SMC_CALL_WITH_ARG: + return handle_std_call(ctx, regs); + case OPTEE_SMC_CALL_RETURN_FROM_RPC: + return handle_rpc(ctx, regs); + default: + return false; + } +} + +static void optee_remove(void) +{ +} + +static const struct tee_mediator_ops optee_ops = +{ + .probe = optee_probe, + .domain_create = optee_domain_create, + .domain_destroy = optee_domain_destroy, + .handle_smc = optee_handle_smc, + .remove = optee_remove, +}; + +REGISTER_TEE_MEDIATOR(optee, "OP-TEE", &optee_ops); + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/include/asm-arm/tee/optee_smc.h b/xen/include/asm-arm/tee/optee_smc.h index 26d100e..1c5a247 100644 --- a/xen/include/asm-arm/tee/optee_smc.h +++ b/xen/include/asm-arm/tee/optee_smc.h @@ -305,6 +305,56 @@ struct optee_smc_disable_shm_cache_result { OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_ENABLE_SHM_CACHE)
/* + * Inform OP-TEE about a new virtual machine + * + * Hypervisor issues this call during virtual machine (guest) creation. + * OP-TEE records VM_ID of new virtual machine and makes self ready + * to receive requests from it. + * + * Call requests usage: + * a0 SMC Function ID, OPTEE_SMC_VM_CREATED + * a1 VM_ID of newly created virtual machine + * a2-6 Not used + * a7 Hypervisor Client ID register. Must be 0, because only hypervisor + * can issue this call + * + * Normal return register usage: + * a0 OPTEE_SMC_RETURN_OK + * a1-7 Preserved + * + * Error return: + * a0 OPTEE_SMC_RETURN_ENOTAVAIL OP-TEE has no resources for + * another VM + * a1-7 Preserved + * + */ +#define OPTEE_SMC_FUNCID_VM_CREATED 13 +#define OPTEE_SMC_VM_CREATED \ + OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_VM_CREATED) + +/* + * Inform OP-TEE about shutdown of a virtual machine + * + * Hypervisor issues this call during virtual machine (guest) destruction. + * OP-TEE will clean up all resources associated with this VM. + * + * Call requests usage: + * a0 SMC Function ID, OPTEE_SMC_VM_DESTROYED + * a1 VM_ID of virtual machine being shutted down + * a2-6 Not used + * a7 Hypervisor Client ID register. Must be 0, because only hypervisor + * can issue this call + * + * Normal return register usage: + * a0 OPTEE_SMC_RETURN_OK + * a1-7 Preserved + * + */ +#define OPTEE_SMC_FUNCID_VM_DESTROYED 14 +#define OPTEE_SMC_VM_DESTROYED \ + OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_VM_DESTROYED) + +/* * Resume from RPC (for example after processing a foreign interrupt) * * Call register usage: