diff --git a/drivers/gud/MobiCoreDriver/Android.mk b/drivers/gud/MobiCoreDriver/Android.mk new file mode 100644 index 000000000000..fbec34fac3a4 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/Android.mk @@ -0,0 +1,8 @@ +DLKM_DIR := motorola/kernel/modules +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := mcDrvModule.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk diff --git a/drivers/gud/MobiCoreDriver/Kbuild b/drivers/gud/MobiCoreDriver/Kbuild new file mode 100644 index 000000000000..041f808cfeed --- /dev/null +++ b/drivers/gud/MobiCoreDriver/Kbuild @@ -0,0 +1,44 @@ +#/* +# * Copyright (c) 2013-2018 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. +# */ +# +# Makefile for the Kinibi core driver +# + + +# add -Wall to try to catch everything we can. +EXTRA_CFLAGS += -Wall +EXTRA_CFLAGS += -DNDEBUG +EXTRA_CFLAGS += -Wno-declaration-after-statement +EXTRA_CFLAGS += -I$(TOP)/motorola/kernel/modules/drivers/gud/MobiCoreDriver + +obj-m += mcDrvModule.o + + +mcDrvModule-y := \ + admin.o \ + client.o \ + clientlib.o \ + clock.o \ + fastcall.o \ + iwp.o \ + logging.o \ + main.o \ + mcp.o \ + mmu.o \ + nq.o \ + pm.o \ + scheduler.o \ + session.o \ + teeclientapi.o \ + user.o diff --git a/drivers/gud/MobiCoreDriver/Makefile b/drivers/gud/MobiCoreDriver/Makefile new file mode 100644 index 000000000000..62a13fd28500 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/Makefile @@ -0,0 +1,11 @@ +KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KERNEL_SRC) M=$(shell pwd) modules $(KBUILD_OPTIONS) + +modules_install: + $(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(shell pwd) modules_install + +clean: + $(MAKE) -C $(KERNEL_SRC) M=$(PWD) clean + diff --git a/drivers/gud/MobiCoreDriver/admin.c b/drivers/gud/MobiCoreDriver/admin.c new file mode 100644 index 000000000000..e2b211035db9 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/admin.c @@ -0,0 +1,1108 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "public/mc_user.h" +#include "public/mc_admin.h" + +#include "mci/mcloadformat.h" + +#include "main.h" +#include "mmu.h" /* For load_check and load_token */ +#include "mcp.h" +#include "nq.h" +#include "client.h" +#include "admin.h" + +static struct { + struct mutex admin_tgid_mutex; /* Lock for admin_tgid below */ + pid_t admin_tgid; + int (*tee_start_cb)(void); + void (*tee_stop_cb)(void); + int last_start_ret; +} admin_ctx; + +static struct mc_admin_driver_request { + /* Global */ + struct mutex mutex; /* Protects access to this struct */ + struct mutex states_mutex; /* Protect access to the states */ + enum client_state { + IDLE, + REQUEST_SENT, + BUFFERS_READY, + } client_state; + enum server_state { + NOT_CONNECTED, /* Device not open */ + READY, /* Waiting for requests */ + REQUEST_RECEIVED, /* Got a request, is working */ + RESPONSE_SENT, /* Has sent a response header */ + DATA_SENT, /* Blocked until data is consumed */ + } server_state; + /* Request */ + u32 request_id; + struct mc_admin_request request; + struct completion client_complete; + /* Response */ + struct mc_admin_response response; + struct completion server_complete; + void *buffer; /* Reception buffer (pre-allocated) */ + size_t size; /* Size of the reception buffer */ + bool lock_channel_during_freeze;/* Is freezing ongoing ? */ +} g_request; + +#if KERNEL_VERSION(3, 13, 0) <= LINUX_VERSION_CODE +static inline void reinit_completion_local(struct completion *x) +{ + reinit_completion(x); +} +#else +static inline void reinit_completion_local(struct completion *x) +{ + INIT_COMPLETION(*x); +} +#endif + +/* The mutex around the channel communication has to be wrapped in order + * to handle this use case : + * client 1 calls request_send() + * wait on wait_for_completion_interruptible (with channel mutex taken) + * client 2 calls request_send() + * waits on mutex_lock(channel mutex) + * kernel starts freezing tasks (suspend or reboot ongoing) + * if we do nothing, then the freezing will be aborted because client 1 + * and 2 have to enter the refrigerator by themselves. + * Note : mutex cannot be held during freezing, so client 1 has release it + * => step 1 : client 1 sets a bool that says that the channel is still in use + * => step 2 : client 1 release the lock and enter the refrigerator + * => now any client trying to use the channel will face the bool preventing + * to use the channel. They also have to enter the refrigerator. + * + * These 3 functions handle this + */ +static void check_freezing_ongoing(void) +{ + /* We don't want to let the channel be used. Let everyone know + * that we're using it + */ + g_request.lock_channel_during_freeze = 1; + /* Now we can safely release the lock */ + mutex_unlock(&g_request.mutex); + /* Let's try to freeze */ + try_to_freeze(); + /* Either freezing happened or was canceled. + * In both cases, reclaim the lock + */ + mutex_lock(&g_request.mutex); + g_request.lock_channel_during_freeze = 0; +} + +static void channel_lock(void) +{ + while (1) { + mutex_lock(&g_request.mutex); + /* We took the lock, but is there any freezing ongoing? */ + if (g_request.lock_channel_during_freeze == 0) + break; + + /* yes, so let's freeze */ + mutex_unlock(&g_request.mutex); + try_to_freeze(); + /* Either freezing succeeded or was canceled. + * In both case, try again to get the lock. + * Give some CPU time to let the contender + * finish his channel operation + */ + msleep(500); + }; +} + +static void channel_unlock(void) +{ + mutex_unlock(&g_request.mutex); +} + +static struct tee_object *tee_object_alloc(bool is_sp_trustlet, size_t length) +{ + struct tee_object *obj; + size_t size = sizeof(*obj) + length; + size_t header_length = 0; + + /* Determine required size */ + if (is_sp_trustlet) { + /* Need space for lengths info and containers */ + header_length = sizeof(struct mc_blob_len_info); + size += header_length + 3 * MAX_SO_CONT_SIZE; + } + + /* Check size for overflow */ + if (size < length) { + mc_dev_err("cannot allocate object of size %zu", length); + return NULL; + } + + /* Allocate memory */ + obj = vzalloc(size); + if (!obj) + return NULL; + + /* A non-zero header_length indicates that we have a SP trustlet */ + obj->header_length = (u32)header_length; + obj->length = (u32)length; + return obj; +} + +void tee_object_free(struct tee_object *robj) +{ + vfree(robj); +} + +static inline void client_state_change(enum client_state state) +{ + mutex_lock(&g_request.states_mutex); + mc_dev_devel("client state changes from %d to %d", + g_request.client_state, state); + g_request.client_state = state; + mutex_unlock(&g_request.states_mutex); +} + +static inline bool client_state_is(enum client_state state) +{ + bool is; + + mutex_lock(&g_request.states_mutex); + is = g_request.client_state == state; + mutex_unlock(&g_request.states_mutex); + return is; +} + +static inline void server_state_change(enum server_state state) +{ + mutex_lock(&g_request.states_mutex); + mc_dev_devel("server state changes from %d to %d", + g_request.server_state, state); + g_request.server_state = state; + mutex_unlock(&g_request.states_mutex); +} + +static inline bool server_state_is(enum server_state state) +{ + bool is; + + mutex_lock(&g_request.states_mutex); + is = g_request.server_state == state; + mutex_unlock(&g_request.states_mutex); + return is; +} + +static void request_cancel(void); + +static int request_send(u32 command, const struct mc_uuid_t *uuid, bool is_gp, + u32 spid) +{ + int counter = 10; + int ret = 0; + + /* Prepare request */ + mutex_lock(&g_request.states_mutex); + /* Wait a little for daemon to connect */ + while ((g_request.server_state == NOT_CONNECTED) && counter--) { + mutex_unlock(&g_request.states_mutex); + ssleep(1); + mutex_lock(&g_request.states_mutex); + } + + WARN_ON(g_request.client_state != IDLE); + if (g_request.server_state != READY) { + mutex_unlock(&g_request.states_mutex); + if (g_request.server_state != NOT_CONNECTED) { + mc_dev_err("invalid daemon state %d", + g_request.server_state); + ret = -EPROTO; + goto end; + } else { + mc_dev_err("daemon not connected"); + ret = -EHOSTUNREACH; + goto end; + } + } + + memset(&g_request.request, 0, sizeof(g_request.request)); + memset(&g_request.response, 0, sizeof(g_request.response)); + /* + * Do not update the request ID until it is dealt with, in case the + * daemon arrives later. + */ + g_request.request.request_id = g_request.request_id; + g_request.request.command = command; + if (uuid) + memcpy(&g_request.request.uuid, uuid, sizeof(*uuid)); + else + memset(&g_request.request.uuid, 0, sizeof(*uuid)); + + g_request.request.is_gp = is_gp; + g_request.request.spid = spid; + g_request.client_state = REQUEST_SENT; + mutex_unlock(&g_request.states_mutex); + + /* Send request */ + complete(&g_request.client_complete); + mc_dev_devel("request sent"); + + /* Wait for header */ + do { + ret = wait_for_completion_interruptible( + &g_request.server_complete); + if (!ret) + break; + /* we may have to freeze now */ + check_freezing_ongoing(); + /* freezing happened or was canceled, + * let's sleep and try again + */ + msleep(500); + } while (1); + mc_dev_devel("response received"); + + /* Server should be waiting with some data for us */ + mutex_lock(&g_request.states_mutex); + switch (g_request.server_state) { + case NOT_CONNECTED: + /* Daemon gone */ + ret = -EPIPE; + break; + case READY: + /* No data to come, likely an error */ + ret = -g_request.response.error_no; + break; + case RESPONSE_SENT: + case DATA_SENT: + /* Normal case, data to come */ + ret = 0; + break; + case REQUEST_RECEIVED: + /* Should not happen as complete means the state changed */ + mc_dev_err("daemon is in a bad state: %d", + g_request.server_state); + ret = -EPIPE; + break; + } + + mutex_unlock(&g_request.states_mutex); + +end: + if (ret) + request_cancel(); + + mc_dev_devel("%s ret=%d", __func__, ret); + return ret; +} + +static int request_receive(void *address, u32 size) +{ + /* + * At this point we have received the header and prepared some buffers + * to receive data that we know are coming from the server. + */ + + /* Check server state */ + bool server_ok; + + mutex_lock(&g_request.states_mutex); + server_ok = (g_request.server_state == RESPONSE_SENT) || + (g_request.server_state == DATA_SENT); + mutex_unlock(&g_request.states_mutex); + if (!server_ok) { + mc_dev_err("expected server state %d or %d, not %d", + RESPONSE_SENT, DATA_SENT, g_request.server_state); + request_cancel(); + return -EPIPE; + } + + /* Setup reception buffer */ + g_request.buffer = address; + g_request.size = size; + client_state_change(BUFFERS_READY); + + /* Unlock write of data */ + complete(&g_request.client_complete); + + /* Wait for data */ + do { + int ret = 0; + + ret = wait_for_completion_interruptible( + &g_request.server_complete); + if (!ret) + break; + /* We may have to freeze now */ + check_freezing_ongoing(); + /* freezing happened or was canceled, + * let's sleep and try again + */ + msleep(500); + } while (1); + + /* Reset reception buffer */ + g_request.buffer = NULL; + g_request.size = 0; + + /* Return to idle state */ + client_state_change(IDLE); + return 0; +} + +/* Must be called instead of request_receive() to cancel a pending request */ +static void request_cancel(void) +{ + /* Unlock write of data */ + mutex_lock(&g_request.states_mutex); + if (g_request.server_state == DATA_SENT) + complete(&g_request.client_complete); + + /* Return to idle state */ + g_request.client_state = IDLE; + mutex_unlock(&g_request.states_mutex); +} + +static int admin_get_root_container(void *address) +{ + int ret = 0; + + /* Lock communication channel */ + channel_lock(); + + /* Send request and wait for header */ + ret = request_send(MC_DRV_GET_ROOT_CONTAINER, NULL, 0, 0); + if (ret) + goto end; + + /* Check length against max */ + if (g_request.response.length >= MAX_SO_CONT_SIZE) { + request_cancel(); + mc_dev_err("response length exceeds maximum"); + ret = EREMOTEIO; + goto end; + } + + /* Get data */ + ret = request_receive(address, g_request.response.length); + if (!ret) + ret = g_request.response.length; + +end: + channel_unlock(); + return ret; +} + +static int admin_get_sp_container(void *address, u32 spid) +{ + int ret = 0; + + /* Lock communication channel */ + channel_lock(); + + /* Send request and wait for header */ + ret = request_send(MC_DRV_GET_SP_CONTAINER, NULL, 0, spid); + if (ret) + goto end; + + /* Check length against max */ + if (g_request.response.length >= MAX_SO_CONT_SIZE) { + request_cancel(); + mc_dev_err("response length exceeds maximum"); + ret = EREMOTEIO; + goto end; + } + + /* Get data */ + ret = request_receive(address, g_request.response.length); + if (!ret) + ret = g_request.response.length; + +end: + channel_unlock(); + return ret; +} + +static int admin_get_trustlet_container(void *address, + const struct mc_uuid_t *uuid, u32 spid) +{ + int ret = 0; + + /* Lock communication channel */ + channel_lock(); + + /* Send request and wait for header */ + ret = request_send(MC_DRV_GET_TRUSTLET_CONTAINER, uuid, 0, spid); + if (ret) + goto end; + + /* Check length against max */ + if (g_request.response.length >= MAX_SO_CONT_SIZE) { + request_cancel(); + mc_dev_err("response length exceeds maximum"); + ret = EREMOTEIO; + goto end; + } + + /* Get data */ + ret = request_receive(address, g_request.response.length); + if (!ret) + ret = g_request.response.length; + +end: + channel_unlock(); + return ret; +} + +static struct tee_object *admin_get_trustlet(const struct mc_uuid_t *uuid, + bool is_gp, u32 *spid) +{ + struct tee_object *obj = NULL; + bool is_sp_tl; + int ret = 0; + + /* Lock communication channel */ + channel_lock(); + + /* Send request and wait for header */ + ret = request_send(MC_DRV_GET_TRUSTLET, uuid, is_gp, 0); + if (ret) + goto end; + + /* Allocate memory */ + is_sp_tl = g_request.response.service_type == SERVICE_TYPE_SP_TRUSTLET; + obj = tee_object_alloc(is_sp_tl, g_request.response.length); + if (!obj) { + request_cancel(); + ret = -ENOMEM; + goto end; + } + + /* Get data */ + ret = request_receive(&obj->data[obj->header_length], obj->length); + *spid = g_request.response.spid; + +end: + channel_unlock(); + if (ret) + return ERR_PTR(ret); + + return obj; +} + +static void mc_admin_sendcrashdump(void) +{ + int ret = 0; + + /* Prevent daemon reconnection */ + admin_ctx.last_start_ret = -EHOSTUNREACH; + + /* Lock communication channel */ + channel_lock(); + + /* Send request and wait for header */ + ret = request_send(MC_DRV_SIGNAL_CRASH, NULL, false, 0); + if (ret) + goto end; + + /* Done */ + request_cancel(); + +end: + channel_unlock(); +} + +static int tee_object_make(u32 spid, struct tee_object *obj) +{ + struct mc_blob_len_info *l_info = (struct mc_blob_len_info *)obj->data; + u8 *address = &obj->data[obj->header_length + obj->length]; + struct mclf_header_v2 *thdr; + int ret; + + /* Get root container */ + ret = admin_get_root_container(address); + if (ret < 0) + goto err; + + l_info->root_size = ret; + address += ret; + + /* Get SP container */ + ret = admin_get_sp_container(address, spid); + if (ret < 0) + goto err; + + l_info->sp_size = ret; + address += ret; + + /* Get trustlet container */ + thdr = (struct mclf_header_v2 *)&obj->data[obj->header_length]; + ret = admin_get_trustlet_container(address, &thdr->uuid, spid); + if (ret < 0) + goto err; + + l_info->ta_size = ret; + address += ret; + + /* Setup lengths information */ + l_info->magic = MC_TLBLOBLEN_MAGIC; + obj->length += sizeof(*l_info); + obj->length += l_info->root_size + l_info->sp_size + l_info->ta_size; + ret = 0; + +err: + return ret; +} + +struct tee_object *tee_object_copy(uintptr_t address, size_t length) +{ + struct tee_object *obj; + + /* Allocate memory */ + obj = tee_object_alloc(false, length); + if (!obj) + return ERR_PTR(-ENOMEM); + + /* Copy trustlet */ + memcpy(obj->data, (void *)address, length); + return obj; +} + +struct tee_object *tee_object_read(u32 spid, uintptr_t address, size_t length) +{ + char __user *addr = (char __user *)address; + struct tee_object *obj; + u8 *data; + struct mclf_header_v2 thdr; + int ret; + + /* Check length */ + if (length < sizeof(thdr)) { + mc_dev_err("buffer shorter than header size"); + return ERR_PTR(-EFAULT); + } + + /* Read header */ + if (copy_from_user(&thdr, addr, sizeof(thdr))) { + mc_dev_err("header: copy_from_user failed"); + return ERR_PTR(-EFAULT); + } + + /* Allocate memory */ + obj = tee_object_alloc(thdr.service_type == SERVICE_TYPE_SP_TRUSTLET, + length); + if (!obj) + return ERR_PTR(-ENOMEM); + + /* Copy header */ + data = &obj->data[obj->header_length]; + memcpy(data, &thdr, sizeof(thdr)); + /* Copy the rest of the data */ + data += sizeof(thdr); + if (copy_from_user(data, &addr[sizeof(thdr)], length - sizeof(thdr))) { + mc_dev_err("data: copy_from_user failed"); + vfree(obj); + return ERR_PTR(-EFAULT); + } + + if (obj->header_length) { + ret = tee_object_make(spid, obj); + if (ret) { + vfree(obj); + return ERR_PTR(ret); + } + } + + return obj; +} + +struct tee_object *tee_object_select(const struct mc_uuid_t *uuid) +{ + struct tee_object *obj; + struct mclf_header_v2 *thdr; + + obj = tee_object_alloc(false, sizeof(*thdr)); + if (!obj) + return ERR_PTR(-ENOMEM); + + thdr = (struct mclf_header_v2 *)&obj->data[obj->header_length]; + memcpy(&thdr->uuid, uuid, sizeof(thdr->uuid)); + return obj; +} + +struct tee_object *tee_object_get(const struct mc_uuid_t *uuid, bool is_gp) +{ + struct tee_object *obj; + u32 spid = 0; + + /* admin_get_trustlet creates the right object based on service type */ + obj = admin_get_trustlet(uuid, is_gp, &spid); + if (IS_ERR(obj)) + return obj; + + /* SP trustlet: create full secure object with all containers */ + if (obj->header_length) { + int ret; + + /* Do not return EINVAL in this case as SPID was not found */ + if (!spid) { + vfree(obj); + return ERR_PTR(-ENOENT); + } + + ret = tee_object_make(spid, obj); + if (ret) { + vfree(obj); + return ERR_PTR(ret); + } + } + + return obj; +} + +static inline int load_driver(struct tee_client *client, + struct mc_admin_load_info *info) +{ + struct tee_object *obj; + struct mclf_header_v2 *thdr; + struct mc_identity identity = { + .login_type = LOGIN_PUBLIC, + }; + uintptr_t dci = 0; + u32 dci_len = 0; + u32 sid; + int ret; + + obj = tee_object_read(info->spid, info->address, info->length); + if (IS_ERR(obj)) + return PTR_ERR(obj); + + thdr = (struct mclf_header_v2 *)&obj->data[obj->header_length]; + if (!(thdr->flags & MC_SERVICE_HEADER_FLAGS_NO_CONTROL_INTERFACE)) { + /* + * The driver requires a DCI, although we won't be able to use + * it to communicate. + */ + dci_len = PAGE_SIZE; + ret = client_cbuf_create(client, dci_len, &dci, NULL); + if (ret) + goto end; + } + + /* Open session */ + ret = client_add_session(client, obj, dci, dci_len, &sid, false, + &identity, -1); + if (!ret) + mc_dev_devel("driver loaded with sid %x", sid); + + /* + * Always 'free' the buffer (will remain as long as used), never freed + * otherwise + */ + client_cbuf_free(client, dci); +end: + vfree(obj); + return ret; +} + +static inline int load_token(struct mc_admin_load_info *token) +{ + struct tee_mmu *mmu; + struct mcp_buffer_map map; + struct mc_ioctl_buffer buf; + int ret; + + buf.va = (uintptr_t)token->address; + buf.len = token->length; + buf.flags = MC_IO_MAP_INPUT; + mmu = tee_mmu_create(current->mm, &buf); + if (IS_ERR(mmu)) + return PTR_ERR(mmu); + + tee_mmu_buffer(mmu, &map); + ret = mcp_load_token(token->address, &map); + tee_mmu_delete(mmu); + return ret; +} + +static inline int load_check(struct mc_admin_load_info *info) +{ + struct tee_object *obj; + struct tee_mmu *mmu; + struct mcp_buffer_map map; + struct mc_ioctl_buffer buf; + int ret; + + obj = tee_object_read(info->spid, info->address, info->length); + if (IS_ERR(obj)) + return PTR_ERR(obj); + + buf.va = (uintptr_t)obj->data; + buf.len = obj->length; + buf.flags = MC_IO_MAP_INPUT; + mmu = tee_mmu_create(NULL, &buf); + if (IS_ERR(mmu)) + return PTR_ERR(mmu); + + tee_mmu_buffer(mmu, &map); + ret = mcp_load_check(obj, &map); + tee_mmu_delete(mmu); + return ret; +} + +static ssize_t admin_write(struct file *file, const char __user *user, + size_t len, loff_t *off) +{ + int ret; + + /* No offset allowed [yet] */ + if (*off) { + mc_dev_err("offset not supported"); + g_request.response.error_no = EPIPE; + ret = -ECOMM; + goto err; + } + + if (server_state_is(REQUEST_RECEIVED)) { + /* Check client state */ + if (!client_state_is(REQUEST_SENT)) { + mc_dev_err("expected client state %d, not %d", + REQUEST_SENT, g_request.client_state); + g_request.response.error_no = EPIPE; + ret = -EPIPE; + goto err; + } + + /* Receive response header */ + if (copy_from_user(&g_request.response, user, + sizeof(g_request.response))) { + mc_dev_err("failed to get response from daemon"); + g_request.response.error_no = EPIPE; + ret = -ECOMM; + goto err; + } + + /* Check request ID */ + if (g_request.request.request_id != + g_request.response.request_id) { + mc_dev_err("expected id %d, not %d", + g_request.request.request_id, + g_request.response.request_id); + g_request.response.error_no = EPIPE; + ret = -EBADE; + goto err; + } + + /* Response header is acceptable */ + ret = sizeof(g_request.response); + if (g_request.response.length) + server_state_change(RESPONSE_SENT); + else + server_state_change(READY); + + goto end; + } else if (server_state_is(RESPONSE_SENT)) { + /* Server is waiting */ + server_state_change(DATA_SENT); + + /* Get data */ + ret = wait_for_completion_interruptible( + &g_request.client_complete); + + /* Server received a signal, let see if it tries again */ + if (ret) { + server_state_change(RESPONSE_SENT); + return ret; + } + + /* Check client state */ + if (!client_state_is(BUFFERS_READY)) { + mc_dev_err("expected client state %d, not %d", + BUFFERS_READY, g_request.client_state); + g_request.response.error_no = EPIPE; + ret = -EPIPE; + goto err; + } + + /* We do not deal with several writes */ + if (len != g_request.size) + len = g_request.size; + + ret = copy_from_user(g_request.buffer, user, len); + if (ret) { + mc_dev_err("failed to get data from daemon"); + g_request.response.error_no = EPIPE; + ret = -ECOMM; + goto err; + } + + ret = len; + server_state_change(READY); + goto end; + } else { + ret = -ECOMM; + goto err; + } + +err: + server_state_change(READY); +end: + complete(&g_request.server_complete); + return ret; +} + +static long admin_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *uarg = (void __user *)arg; + int ret = -EINVAL; + + mc_dev_devel("%u from %s", _IOC_NR(cmd), current->comm); + + switch (cmd) { + case MC_ADMIN_IO_GET_DRIVER_REQUEST: { + /* Update TGID as it may change (when becoming a daemon) */ + if (admin_ctx.admin_tgid != current->tgid) { + admin_ctx.admin_tgid = current->tgid; + mc_dev_info("daemon PID changed to %d", + admin_ctx.admin_tgid); + } + + /* Block until a request is available */ + server_state_change(READY); + ret = wait_for_completion_interruptible( + &g_request.client_complete); + if (ret) + /* Interrupted by signal */ + break; + + /* Check client state */ + if (!client_state_is(REQUEST_SENT)) { + mc_dev_err("expected client state %d, not %d", + REQUEST_SENT, g_request.client_state); + g_request.response.error_no = EPIPE; + complete(&g_request.server_complete); + ret = -EPIPE; + break; + } + + /* Send request (the driver request mutex is held) */ + ret = copy_to_user(uarg, &g_request.request, + sizeof(g_request.request)); + if (ret) { + server_state_change(READY); + complete(&g_request.server_complete); + ret = -EPROTO; + break; + } + + /* Now that the daemon got it, update the request ID */ + g_request.request_id++; + + server_state_change(REQUEST_RECEIVED); + break; + } + case MC_ADMIN_IO_GET_INFO: { + struct mc_admin_driver_info info; + + info.drv_version = MC_VERSION(MCDRVMODULEAPI_VERSION_MAJOR, + MCDRVMODULEAPI_VERSION_MINOR); + info.initial_cmd_id = g_request.request_id; + ret = copy_to_user(uarg, &info, sizeof(info)); + break; + } + case MC_ADMIN_IO_LOAD_DRIVER: { + struct tee_client *client = file->private_data; + struct mc_admin_load_info info; + + if (copy_from_user(&info, uarg, sizeof(info))) { + ret = -EFAULT; + break; + } + + /* Make sure we have a local client */ + if (!client) { + client = client_create(true); + /* Store client for future use/close */ + file->private_data = client; + } + + if (!client) { + ret = -ENOMEM; + break; + } + + ret = load_driver(client, &info); + break; + } + case MC_ADMIN_IO_LOAD_TOKEN: { + struct mc_admin_load_info info; + + if (copy_from_user(&info, uarg, sizeof(info))) { + ret = -EFAULT; + break; + } + + ret = load_token(&info); + break; + } + case MC_ADMIN_IO_LOAD_CHECK: { + struct mc_admin_load_info info; + + if (copy_from_user(&info, uarg, sizeof(info))) { + ret = -EFAULT; + break; + } + + ret = load_check(&info); + break; + } + default: + ret = -ENOIOCTLCMD; + } + + return ret; +} + +/* + * mc_fd_release() - This function will be called from user space as close(...) + * The client data are freed and the associated memory pages are unreserved. + * + * @inode + * @file + * + * Returns 0 + */ +static int admin_release(struct inode *inode, struct file *file) +{ + /* Close client if any */ + if (file->private_data) + client_close((struct tee_client *)file->private_data); + + /* Requests from driver to daemon */ + mutex_lock(&g_request.states_mutex); + mc_dev_devel("server state changes from %d to %d", + g_request.server_state, NOT_CONNECTED); + g_request.server_state = NOT_CONNECTED; + /* A non-zero command indicates that a thread is waiting */ + if (g_request.client_state != IDLE) { + g_request.response.error_no = ESHUTDOWN; + complete(&g_request.server_complete); + } + mutex_unlock(&g_request.states_mutex); + mc_dev_info("daemon connection closed, TGID %d", + admin_ctx.admin_tgid); + admin_ctx.admin_tgid = 0; + + /* + * ret is quite irrelevant here as most apps don't care about the + * return value from close() and it's quite difficult to recover + */ + return 0; +} + +static int admin_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + /* Only one connection allowed to admin interface */ + mutex_lock(&admin_ctx.admin_tgid_mutex); + if (admin_ctx.admin_tgid) { + mc_dev_err("daemon connection already open, PID %d", + admin_ctx.admin_tgid); + ret = -EBUSY; + } else { + admin_ctx.admin_tgid = current->tgid; + } + mutex_unlock(&admin_ctx.admin_tgid_mutex); + if (ret) + return ret; + + /* Setup the usual variables */ + mc_dev_devel("accept %s as daemon", current->comm); + + /* + * daemon is connected so now we can safely suppose + * the secure world is loaded too + */ + if (admin_ctx.last_start_ret > 0) + admin_ctx.last_start_ret = admin_ctx.tee_start_cb(); + + /* Failed to start the TEE, either now or before */ + if (admin_ctx.last_start_ret) { + mutex_lock(&admin_ctx.admin_tgid_mutex); + admin_ctx.admin_tgid = 0; + mutex_unlock(&admin_ctx.admin_tgid_mutex); + return admin_ctx.last_start_ret; + } + + reinit_completion_local(&g_request.client_complete); + reinit_completion_local(&g_request.server_complete); + /* Requests from driver to daemon */ + mc_dev_info("daemon connection open, TGID %d", admin_ctx.admin_tgid); + return 0; +} + +/* function table structure of this device driver. */ +static const struct file_operations mc_admin_fops = { + .owner = THIS_MODULE, + .open = admin_open, + .release = admin_release, + .unlocked_ioctl = admin_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = admin_ioctl, +#endif + .write = admin_write, +}; + +int mc_admin_init(struct cdev *cdev, int (*tee_start_cb)(void), + void (*tee_stop_cb)(void)) +{ + mutex_init(&admin_ctx.admin_tgid_mutex); + /* Requests from driver to daemon */ + mutex_init(&g_request.mutex); + mutex_init(&g_request.states_mutex); + g_request.request_id = 42; + init_completion(&g_request.client_complete); + init_completion(&g_request.server_complete); + nq_register_crash_handler(mc_admin_sendcrashdump); + /* Create char device */ + cdev_init(cdev, &mc_admin_fops); + /* Register the call back for starting the secure world */ + admin_ctx.tee_start_cb = tee_start_cb; + admin_ctx.tee_stop_cb = tee_stop_cb; + admin_ctx.last_start_ret = TEE_START_NOT_TRIGGERED; + return 0; +} + +void mc_admin_exit(void) +{ + if (!admin_ctx.last_start_ret) + admin_ctx.tee_stop_cb(); +} diff --git a/drivers/gud/MobiCoreDriver/admin.h b/drivers/gud/MobiCoreDriver/admin.h new file mode 100644 index 000000000000..8f8667ecb145 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/admin.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013-2017 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. + */ + +#ifndef _MC_ADMIN_H_ +#define _MC_ADMIN_H_ + +struct cdev; +struct mc_uuid_t; +struct tee_object; + +int mc_admin_init(struct cdev *cdev, int (*tee_start_cb)(void), + void (*tee_stop_cb)(void)); +void mc_admin_exit(void); + +struct tee_object *tee_object_select(const struct mc_uuid_t *uuid); +struct tee_object *tee_object_get(const struct mc_uuid_t *uuid, bool is_gp); +struct tee_object *tee_object_copy(uintptr_t address, size_t length); +struct tee_object *tee_object_read(u32 spid, uintptr_t address, size_t length); +void tee_object_free(struct tee_object *object); + +#endif /* _MC_ADMIN_H_ */ diff --git a/drivers/gud/MobiCoreDriver/arm.h b/drivers/gud/MobiCoreDriver/arm.h new file mode 100644 index 000000000000..85ff70125e12 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/arm.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2013-2016 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. + */ +#ifndef _MC_ARM_H_ +#define _MC_ARM_H_ + +#include "main.h" + +#ifdef CONFIG_ARM64 +static inline bool has_security_extensions(void) +{ + return true; +} + +static inline bool is_secure_mode(void) +{ + return false; +} +#else +/* + * ARM Trustzone specific masks and modes + * Vanilla Linux is unaware of TrustZone extension. + * I.e. arch/arm/include/asm/ptrace.h does not define monitor mode. + * Also TZ bits in cpuid are not defined, ARM port uses magic numbers, + * see arch/arm/kernel/setup.c + */ +#define ARM_MONITOR_MODE (0x16) /*(0b10110)*/ +#define ARM_SECURITY_EXTENSION_MASK (0x30) + +/* check if CPU supports the ARM TrustZone Security Extensions */ +static inline bool has_security_extensions(void) +{ + u32 fea = 0; + + asm volatile( + "mrc p15, 0, %[fea], cr0, cr1, 0" : + [fea]"=r" (fea)); + + mc_dev_devel("CPU Features: 0x%X", fea); + + /* + * If the CPU features ID has 0 for security features then the CPU + * doesn't support TrustZone at all! + */ + if ((fea & ARM_SECURITY_EXTENSION_MASK) == 0) + return false; + + return true; +} + +/* check if running in secure mode */ +static inline bool is_secure_mode(void) +{ + u32 cpsr = 0; + u32 nsacr = 0; + + asm volatile( + "mrc p15, 0, %[nsacr], cr1, cr1, 2\n" + "mrs %[cpsr], cpsr\n" : + [nsacr]"=r" (nsacr), + [cpsr]"=r"(cpsr)); + + mc_dev_devel("CPRS.M = set to 0x%X", cpsr & MODE_MASK); + mc_dev_devel("SCR.NS = set to 0x%X", nsacr); + + /* + * If the NSACR contains the reset value(=0) then most likely we are + * running in Secure MODE. + * If the cpsr mode is set to monitor mode then we cannot load! + */ + if (nsacr == 0 || ((cpsr & MODE_MASK) == ARM_MONITOR_MODE)) + return true; + + return false; +} +#endif + +#endif /* _MC_ARM_H_ */ diff --git a/drivers/gud/MobiCoreDriver/build_tag.h b/drivers/gud/MobiCoreDriver/build_tag.h new file mode 100644 index 000000000000..ef5ccd80cf69 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/build_tag.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2013-2016 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. + */ +#ifndef MOBICORE_COMPONENT_BUILD_TAG +#define MOBICORE_COMPONENT_BUILD_TAG \ + "t-base-QC-MSM8996-Android-400C-V002-20180809_045827_53704_83249" +#endif diff --git a/drivers/gud/MobiCoreDriver/client.c b/drivers/gud/MobiCoreDriver/client.c new file mode 100644 index 000000000000..837d76f9138c --- /dev/null +++ b/drivers/gud/MobiCoreDriver/client.c @@ -0,0 +1,1555 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include /* struct task_struct */ +#include +#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE +#include /* get_task_mm */ +#include /* put_task_struct */ +#endif +#include /* sockfd_lookup */ +#include /* fput */ + +#include "public/mc_user.h" +#include "public/mc_admin.h" + +#include "main.h" +#include "admin.h" /* tee_object* */ +#include "mmu.h" +#include "session.h" +#include "client.h" + +/* Client/context */ +struct tee_client { + /* PID of task that opened the device, 0 if kernel */ + pid_t pid; + /* Command for task*/ + char comm[TASK_COMM_LEN]; + /* Number of references kept to this object */ + struct kref kref; + /* List of contiguous buffers allocated by mcMallocWsm for the client */ + struct list_head cbufs; + struct mutex cbufs_lock; /* lock for the cbufs list */ + /* List of TA sessions opened by this client */ + struct list_head sessions; + struct list_head closing_sessions; + struct mutex sessions_lock; /* sessions list + closing */ + /* Client lock for quick WSMs and operations changes */ + struct mutex quick_lock; + /* Client lock for CWSMs release functions */ + struct mutex cwsm_release_lock; + /* List of WSMs for a client */ + struct list_head cwsms; + /* List of GP operation for a client */ + struct list_head operations; + /* The list entry to attach to "ctx.clients" list */ + struct list_head list; + /* task_struct for the client application, if going through a proxy */ + struct task_struct *task; +}; + +/* Context */ +static struct client_ctx { + /* Clients list */ + struct mutex clients_lock; + struct list_head clients; + /* Clients waiting for their last cbuf to be released */ + struct mutex closing_clients_lock; + struct list_head closing_clients; +} client_ctx; + +/* Buffer shared with SWd at client level */ +struct cwsm { + /* Client this cbuf belongs to */ + struct tee_client *client; + /* Buffer info */ + struct gp_shared_memory memref; + /* MMU L2 table */ + struct tee_mmu *mmu; + /* Buffer SWd addr */ + u32 sva; + /* Number of references kept to this object */ + struct kref kref; + /* The list entry for the client to list its WSMs */ + struct list_head list; +}; + +/* + * Contiguous buffer allocated to TLCs. + * These buffers are used as world shared memory (wsm) to share with + * secure world. + */ +struct cbuf { + /* Client this cbuf belongs to */ + struct tee_client *client; + /* List element for client's list of cbuf's */ + struct list_head list; + /* Number of references kept to this buffer */ + struct kref kref; + /* virtual Kernel start address */ + uintptr_t addr; + /* virtual Userspace start address */ + uintptr_t uaddr; + /* physical start address */ + phys_addr_t phys; + /* 2^order = number of pages allocated */ + unsigned int order; + /* Length of memory mapped to user */ + u32 len; + /* Has been freed via the API */ + bool api_freed; +}; + +static inline void cbuf_get(struct cbuf *cbuf) +{ + kref_get(&cbuf->kref); +} + +/* Must only be called by tee_cbuf_put */ +static void cbuf_release(struct kref *kref) +{ + struct cbuf *cbuf = container_of(kref, struct cbuf, kref); + struct tee_client *client = cbuf->client; + + /* Unlist from client */ + list_del_init(&cbuf->list); + /* Release client token */ + client_put(client); + /* Free */ + free_pages(cbuf->addr, cbuf->order); + mc_dev_devel("freed cbuf %p: client %p addr %lx uaddr %lx len %u", + cbuf, client, cbuf->addr, cbuf->uaddr, cbuf->len); + kfree(cbuf); + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_cbufs); +} + +static inline void cbuf_put(struct cbuf *cbuf) +{ + struct tee_client *client = cbuf->client; + + mutex_lock(&client->cbufs_lock); + kref_put(&cbuf->kref, cbuf_release); + mutex_unlock(&client->cbufs_lock); +} + +/* + * Map a kernel contiguous buffer to user space + */ +static int cbuf_map(struct vm_area_struct *vmarea, uintptr_t addr, u32 len, + uintptr_t *uaddr) +{ + int ret; + + if (!uaddr) + return -EINVAL; + + if (!vmarea) + return -EINVAL; + + if (!addr) + return -EINVAL; + + if (len != (u32)(vmarea->vm_end - vmarea->vm_start)) { + mc_dev_err("cbuf incompatible with vma"); + return -EINVAL; + } + + vmarea->vm_flags |= VM_IO; + ret = remap_pfn_range(vmarea, vmarea->vm_start, + page_to_pfn(virt_to_page(addr)), + vmarea->vm_end - vmarea->vm_start, + vmarea->vm_page_prot); + if (ret) { + *uaddr = 0; + mc_dev_err("User mapping failed"); + return ret; + } + + *uaddr = vmarea->vm_start; + return 0; +} + +/* + * Returns true if client is a kernel object. + */ +static inline bool client_is_kernel(struct tee_client *client) +{ + return !client->pid; +} + +/* + * The proxy gives us the fd of its server-side socket, so we can find out the + * task then the mm of its client. mmput() must be called to free the resource. + */ +static struct mm_struct *get_mm_from_client_fd(int client_fd) +{ + struct mm_struct *mm = NULL; + struct socket *sock; + int err; + + if (client_fd < 0) + return get_task_mm(current); + + sock = sockfd_lookup(client_fd, &err); + if (!sock) + return NULL; + + if (sock->sk && sock->sk->sk_peer_pid) { + struct task_struct *task; + + rcu_read_lock(); + task = pid_task(sock->sk->sk_peer_pid, PIDTYPE_PID); + if (task) + mm = get_task_mm(task); + + rcu_read_unlock(); + } + + sockfd_put(sock); + return mm; +} + +static struct cwsm *cwsm_create(struct tee_client *client, + const struct gp_shared_memory *memref, + struct gp_return *gp_ret, int client_fd) +{ + struct cwsm *cwsm; + struct mcp_buffer_map map; + struct mc_ioctl_buffer buf; + int ret; + + cwsm = kzalloc(sizeof(*cwsm), GFP_KERNEL); + if (!cwsm) + return ERR_PTR(iwp_set_ret(-ENOMEM, gp_ret)); + + buf.va = (uintptr_t)memref->buffer; + buf.len = memref->size; + buf.flags = memref->flags; + if (client_is_kernel(client)) { + cwsm->mmu = tee_mmu_create(NULL, &buf); + } else { + struct mm_struct *mm = get_mm_from_client_fd(client_fd); + + if (!mm) { + mc_dev_err("can't get mm from client fd %d", client_fd); + ret = -EPERM; + goto err_cwsm; + } + + /* Build MMU table for buffer */ + cwsm->mmu = tee_mmu_create(mm, &buf); + mmput(mm); + } + + if (IS_ERR(cwsm->mmu)) { + ret = iwp_set_ret(PTR_ERR(cwsm->mmu), gp_ret); + goto err_cwsm; + } + + /* Initialise maps */ + memset(&map, 0, sizeof(map)); + tee_mmu_buffer(cwsm->mmu, &map); + /* FIXME Flags must be stored in MMU (needs recent trunk change) */ + map.flags = memref->flags; + ret = iwp_register_shared_mem(&map, gp_ret); + if (ret) + goto err_mmu; + + cwsm->client = client; + memcpy(&cwsm->memref, memref, sizeof(cwsm->memref)); + cwsm->sva = map.secure_va; + kref_init(&cwsm->kref); + INIT_LIST_HEAD(&cwsm->list); + /* Get a token on the client */ + client_get(client); + /* Add buffer to list */ + mutex_lock(&client->quick_lock); + list_add_tail(&cwsm->list, &client->cwsms); + mutex_unlock(&client->quick_lock); + mc_dev_devel("created cwsm %p: client %p", cwsm, client); + /* Increment debug counter */ + atomic_inc(&g_ctx.c_cwsms); + return cwsm; + +err_mmu: + tee_mmu_delete(cwsm->mmu); +err_cwsm: + kfree(cwsm); + return ERR_PTR(ret); +} + +static inline void cwsm_get(struct cwsm *cwsm) +{ + kref_get(&cwsm->kref); +} + +/* Must only be called by cwsm_put */ +static void cwsm_release(struct kref *kref) +{ + struct cwsm *cwsm = container_of(kref, struct cwsm, kref); + struct tee_client *client = cwsm->client; + struct mcp_buffer_map map; + + /* Unlist from client */ + list_del_init(&cwsm->list); + /* Unmap buffer from SWd (errors ignored) */ + tee_mmu_buffer(cwsm->mmu, &map); + map.secure_va = cwsm->sva; + iwp_release_shared_mem(&map); + /* Release MMU */ + tee_mmu_delete(cwsm->mmu); + /* Release client token */ + client_put(client); + /* Free */ + mc_dev_devel("freed cwsm %p: client %p", cwsm, client); + kfree(cwsm); + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_cwsms); +} + +static inline void cwsm_put(struct cwsm *cwsm) +{ + struct tee_client *client = cwsm->client; + + mutex_lock(&client->quick_lock); + kref_put(&cwsm->kref, cwsm_release); + mutex_unlock(&client->quick_lock); +} + +static inline struct cwsm *cwsm_find(struct tee_client *client, + const struct gp_shared_memory *memref) +{ + struct cwsm *cwsm = NULL, *candidate; + + mc_dev_devel("find shared mem for buf %llx size %llu flags %x", + memref->buffer, memref->size, memref->flags); + mutex_lock(&client->quick_lock); + list_for_each_entry(candidate, &client->cwsms, list) { + mc_dev_devel("candidate buf %llx size %llu flags %x", + candidate->memref.buffer, candidate->memref.size, + candidate->memref.flags); + if (candidate->memref.buffer == memref->buffer && + candidate->memref.size == memref->size && + candidate->memref.flags == memref->flags) { + cwsm = candidate; + cwsm_get(cwsm); + mc_dev_devel("match"); + break; + } + } + mutex_unlock(&client->quick_lock); + return cwsm; +} + +static inline struct cwsm *cwsm_find_by_sva(struct tee_client *client, u32 sva) +{ + struct cwsm *cwsm = NULL, *candidate; + + mutex_lock(&client->quick_lock); + list_for_each_entry(candidate, &client->cwsms, list) + if (candidate->sva == sva) { + cwsm = candidate; + cwsm_get(cwsm); + break; + } + mutex_unlock(&client->quick_lock); + return cwsm; +} + +/* + * Returns the secure virtual address from a registered mem + */ +u32 client_get_cwsm_sva(struct tee_client *client, + const struct gp_shared_memory *memref) +{ + struct cwsm *cwsm = cwsm_find(client, memref); + + if (!cwsm) + return 0; + + return cwsm->sva; +} + +void client_get(struct tee_client *client) +{ + kref_get(&client->kref); +} + +void client_put_cwsm_sva(struct tee_client *client, u32 sva) +{ + struct cwsm *cwsm; + + mutex_lock(&client->cwsm_release_lock); + cwsm = cwsm_find_by_sva(client, sva); + if (!cwsm) + goto end; + + /* Release reference taken by cwsm_find_by_sva */ + cwsm_put(cwsm); + cwsm_put(cwsm); +end: + mutex_unlock(&client->cwsm_release_lock); +} + +/* + * Allocate and initialize a client object + */ +struct tee_client *client_create(bool is_from_kernel) +{ + struct tee_client *client; + + /* Allocate client structure */ + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return NULL; + + /* Increment debug counter */ + atomic_inc(&g_ctx.c_clients); + /* initialize members */ + client->pid = is_from_kernel ? 0 : current->pid; + memcpy(client->comm, current->comm, sizeof(client->comm)); + kref_init(&client->kref); + INIT_LIST_HEAD(&client->cbufs); + mutex_init(&client->cbufs_lock); + INIT_LIST_HEAD(&client->sessions); + INIT_LIST_HEAD(&client->closing_sessions); + mutex_init(&client->sessions_lock); + INIT_LIST_HEAD(&client->list); + mutex_init(&client->quick_lock); + mutex_init(&client->cwsm_release_lock); + INIT_LIST_HEAD(&client->cwsms); + INIT_LIST_HEAD(&client->operations); + /* Add client to list of clients */ + mutex_lock(&client_ctx.clients_lock); + list_add_tail(&client->list, &client_ctx.clients); + mutex_unlock(&client_ctx.clients_lock); + mc_dev_devel("created client %p", client); + return client; +} + +/* Must only be called by client_put */ +static void client_release(struct kref *kref) +{ + struct tee_client *client; + + client = container_of(kref, struct tee_client, kref); + /* Client is closed, remove from closing list */ + list_del(&client->list); + mc_dev_devel("freed client %p", client); + if (client->task) + put_task_struct(client->task); + + kfree(client); + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_clients); +} + +int client_put(struct tee_client *client) +{ + int ret; + + mutex_lock(&client_ctx.closing_clients_lock); + ret = kref_put(&client->kref, client_release); + mutex_unlock(&client_ctx.closing_clients_lock); + return ret; +} + +/* + * Set client "closing" state, only if it contains no session. + * Once in "closing" state, system "close" can be called. + * Return: 0 if this state could be set. + */ +bool client_has_sessions(struct tee_client *client) +{ + bool ret; + + /* Check for sessions */ + mutex_lock(&client->sessions_lock); + ret = !list_empty(&client->sessions); + mutex_unlock(&client->sessions_lock); + mc_dev_devel("client %p, exit with %d", client, ret); + return ret; +} + +static inline void client_put_session(struct tee_client *client, + struct tee_session *session) +{ + /* Remove session from client's closing list */ + mutex_lock(&client->sessions_lock); + list_del(&session->list); + mutex_unlock(&client->sessions_lock); + /* Release the ref we took on creation */ + session_put(session); +} + +/* + * At this point, nobody has access to the client anymore, so no new sessions + * are being created. + */ +static void client_close_sessions(struct tee_client *client) +{ + struct tee_session *session; + + mutex_lock(&client->sessions_lock); + while (!list_empty(&client->sessions)) { + session = list_first_entry(&client->sessions, + struct tee_session, list); + + /* Move session to closing sessions list */ + list_move(&session->list, &client->closing_sessions); + /* Call session_close without lock */ + mutex_unlock(&client->sessions_lock); + if (!session_close(session)) + client_put_session(client, session); + mutex_lock(&client->sessions_lock); + } + + mutex_unlock(&client->sessions_lock); +} + +/* + * At this point, nobody has access to the client anymore, so no new contiguous + * buffers are being created. + */ +static void client_close_kernel_cbufs(struct tee_client *client) +{ + /* Put buffers allocated and not freed via the kernel API */ + if (!client_is_kernel(client)) + return; + + /* Look for cbufs that the client has not freed and put them */ + while (true) { + struct cbuf *cbuf = NULL, *candidate; + + mutex_lock(&client->cbufs_lock); + list_for_each_entry(candidate, &client->cbufs, list) { + if (!candidate->api_freed) { + candidate->api_freed = true; + cbuf = candidate; + break; + } + } + mutex_unlock(&client->cbufs_lock); + + if (!cbuf) + break; + + cbuf_put(cbuf); + } +} + +/* Client is closing: make sure all CSMs are gone */ +static void client_release_cwsms(struct tee_client *client) +{ + /* Look for cbufs that the client has not freed and put them */ + while (!list_empty(&client->cwsms)) { + struct cwsm *cwsm; + + cwsm = list_first_entry(&client->cwsms, struct cwsm, list); + cwsm_put(cwsm); + } +} + +/* Client is closing: make sure all cancelled operations are gone */ +static void client_release_gp_operations(struct tee_client *client) +{ + struct client_gp_operation *op, *nop; + + mutex_lock(&client->quick_lock); + list_for_each_entry_safe(op, nop, &client->operations, list) { + /* Only cancelled operations are kzalloc'd */ + mc_dev_devel("flush cancelled operation %p for started %u", + op, op->started); + if (op->cancelled) + kfree(op); + } + mutex_unlock(&client->quick_lock); +} + +/* + * Release a client and the session+cbuf objects it contains. + * @param client_t client + * @return driver error code + */ +void client_close(struct tee_client *client) +{ + /* Move client from active clients to closing clients for debug */ + mutex_lock(&client_ctx.clients_lock); + mutex_lock(&client_ctx.closing_clients_lock); + list_move(&client->list, &client_ctx.closing_clients); + mutex_unlock(&client_ctx.closing_clients_lock); + mutex_unlock(&client_ctx.clients_lock); + client_close_kernel_cbufs(client); + /* Close all remaining sessions */ + client_close_sessions(client); + /* Release all cwsms, no need to lock as sessions are closed */ + client_release_cwsms(client); + client_release_gp_operations(client); + client_put(client); + mc_dev_devel("client %p closed", client); +} + +/* + * The TEE is going to die, so get rid of whatever is shared with it + */ +void clients_kill_sessions(void) +{ + struct tee_client *client; + + mutex_lock(&client_ctx.clients_lock); + list_for_each_entry(client, &client_ctx.clients, list) { + /* + * session_kill() will put the session which should get freed + * and free its wsms/mmus and put any cbuf concerned + */ + mutex_lock(&client->sessions_lock); + while (!list_empty(&client->sessions)) { + struct tee_session *session; + + session = list_first_entry(&client->sessions, + struct tee_session, list); + list_del(&session->list); + session_kill(session); + } + mutex_unlock(&client->sessions_lock); + } + mutex_unlock(&client_ctx.clients_lock); +} + +/* + * Open TA for given client. TA binary is provided by the daemon. + * @param + * @return driver error code + */ +int client_open_session(struct tee_client *client, u32 *session_id, + const struct mc_uuid_t *uuid, uintptr_t tci, + size_t tci_len, bool is_gp_uuid, + struct mc_identity *identity, int client_fd) +{ + int err = 0; + u32 sid = 0; + struct tee_object *obj; + + /* Get secure object */ + obj = tee_object_get(uuid, is_gp_uuid); + if (IS_ERR(obj)) { + /* Try to select secure object inside the SWd if not found */ + if ((PTR_ERR(obj) == -ENOENT) && g_ctx.f_ta_auth) + obj = tee_object_select(uuid); + + if (IS_ERR(obj)) { + err = PTR_ERR(obj); + goto end; + } + } + + /* Open session */ + err = client_add_session(client, obj, tci, tci_len, &sid, is_gp_uuid, + identity, client_fd); + /* Fill in return parameter */ + if (!err) + *session_id = sid; + + /* Delete secure object */ + tee_object_free(obj); + +end: + + mc_dev_devel("session %x, exit with %d", sid, err); + return err; +} + +/* + * Open TA for given client. TA binary is provided by the client. + * @param + * @return driver error code + */ +int client_open_trustlet(struct tee_client *client, u32 *session_id, u32 spid, + uintptr_t trustlet, size_t trustlet_len, + uintptr_t tci, size_t tci_len, int client_fd) +{ + struct tee_object *obj; + struct mc_identity identity = { + .login_type = LOGIN_PUBLIC, + }; + u32 sid = 0; + int err = 0; + + if (client_is_kernel(client)) + /* Create secure object from kernel-space trustlet binary */ + obj = tee_object_copy(trustlet, trustlet_len); + else + /* Create secure object from user-space trustlet binary */ + obj = tee_object_read(spid, trustlet, trustlet_len); + if (IS_ERR(obj)) { + err = PTR_ERR(obj); + goto end; + } + + /* Open session */ + err = client_add_session(client, obj, tci, tci_len, &sid, false, + &identity, client_fd); + /* Fill in return parameter */ + if (!err) + *session_id = sid; + + /* Delete secure object */ + tee_object_free(obj); + +end: + mc_dev_devel("session %x, exit with %d", sid, err); + return err; +} + +/* + * Opens a TA and add corresponding session object to given client + * return: driver error code + */ +int client_add_session(struct tee_client *client, const struct tee_object *obj, + uintptr_t tci, size_t len, u32 *session_id, bool is_gp, + struct mc_identity *identity, int client_fd) +{ + struct tee_session *session = NULL; + struct tee_mmu *obj_mmu = NULL; + struct mc_ioctl_buffer buf; + int ret = 0; + + /* + * Create session object with temp sid=0 BEFORE session is started, + * otherwise if a GP TA is started and NWd session object allocation + * fails, we cannot handle the potentially delayed GP closing. + * Adding session to list must be done AFTER it is started (once we have + * sid), therefore it cannot be done within session_create(). + */ + session = session_create(client, is_gp, identity, client_fd); + if (IS_ERR(session)) + return PTR_ERR(session); + + /* Create blob L2 table (blob is allocated by driver, so task=NULL) */ + buf.va = (uintptr_t)obj->data; + buf.len = obj->length; + buf.flags = MC_IO_MAP_INPUT; + obj_mmu = tee_mmu_create(NULL, &buf); + if (IS_ERR(obj_mmu)) { + ret = PTR_ERR(obj_mmu); + goto err; + } + + /* Open session */ + ret = session_open(session, obj, obj_mmu, tci, len, client_fd); + /* Blob table no more needed in any case */ + tee_mmu_delete(obj_mmu); + if (ret) + goto err; + + mutex_lock(&client->sessions_lock); + /* Add session to client */ + list_add_tail(&session->list, &client->sessions); + /* Set sid returned by SWd */ + *session_id = session->mcp_session.sid; + mutex_unlock(&client->sessions_lock); + +err: + /* Close or free session on error */ + if (ret == -ENODEV) { + /* The session must enter the closing process... */ + list_add_tail(&session->list, &client->closing_sessions); + if (!session_close(session)) + client_put_session(client, session); + } else if (ret) { + session_put(session); + } + + return ret; +} + +/* + * Opens a TA and add corresponding session object to given client + * return: driver error code + */ +static int client_add_gp_session(struct tee_client *client, + const struct tee_object *obj, + u32 *session_id, + struct gp_operation *operation, + struct mc_identity *identity, + int client_fd, struct gp_return *gp_ret) +{ + struct tee_session *session = NULL; + struct tee_mmu *obj_mmu = NULL; + struct mc_ioctl_buffer buf; + int ret = 0; + + /* + * Create session object with temp sid=0 BEFORE session is started, + * otherwise if a GP TA is started and NWd session object allocation + * fails, we cannot handle the potentially delayed GP closing. + * Adding session to list must be done AFTER it is started (once we have + * sid), therefore it cannot be done within session_create(). + */ + session = session_create(client, true, identity, client_fd); + if (IS_ERR(session)) + return iwp_set_ret(PTR_ERR(session), gp_ret); + + /* Create blob L2 table (blob is allocated by driver, so task=NULL) */ + buf.va = (uintptr_t)obj->data; + buf.len = obj->length; + buf.flags = MC_IO_MAP_INPUT; + obj_mmu = tee_mmu_create(NULL, &buf); + if (IS_ERR(obj_mmu)) { + ret = PTR_ERR(obj_mmu); + goto end; + } + + /* Open session */ + ret = session_gp_open_session(session, obj, obj_mmu, operation, gp_ret, + client_fd); + /* Blob table no more needed in any case */ + tee_mmu_delete(obj_mmu); + if (ret) + goto end; + + mutex_lock(&client->sessions_lock); + /* Add session to client */ + list_add_tail(&session->list, &client->sessions); + mutex_unlock(&client->sessions_lock); + /* Set sid returned by SWd */ + *session_id = session->iwp_session.sid; + +end: + if (ret) + session_put(session); + + return ret; +} + +/* + * Remove a session object from client and close corresponding TA + * Return: true if session was found and closed + */ +int client_remove_session(struct tee_client *client, u32 session_id) +{ + struct tee_session *session = NULL, *candidate; + int ret; + + /* Move session from main list to closing list */ + mutex_lock(&client->sessions_lock); + list_for_each_entry(candidate, &client->sessions, list) { + if (candidate->mcp_session.sid == session_id) { + session = candidate; + list_move(&session->list, &client->closing_sessions); + break; + } + } + + mutex_unlock(&client->sessions_lock); + if (!session) + return -ENXIO; + + /* Close session */ + ret = session_close(session); + if (!ret) + client_put_session(client, session); + + return ret; +} + +/* + * Find a session object and increment its reference counter. + * Object cannot be freed until its counter reaches 0. + * return: pointer to the object, NULL if not found. + */ +static struct tee_session *client_get_session(struct tee_client *client, + u32 session_id) +{ + struct tee_session *session = NULL, *candidate; + + mutex_lock(&client->sessions_lock); + list_for_each_entry(candidate, &client->sessions, list) { + if (candidate->mcp_session.sid == session_id) { + session = candidate; + session_get(session); + break; + } + } + + mutex_unlock(&client->sessions_lock); + if (!session) + mc_dev_err("session %x not found", session_id); + + return session; +} + +/* + * Send a notification to TA + * @return driver error code + */ +int client_notify_session(struct tee_client *client, u32 session_id) +{ + struct tee_session *session; + int ret; + + /* Find/get session */ + session = client_get_session(client, session_id); + if (!session) + return -ENXIO; + + /* Send command to SWd */ + ret = session_notify_swd(session); + /* Put session */ + session_put(session); + mc_dev_devel("session %x, exit with %d", session_id, ret); + return ret; +} + +/* + * Wait for a notification from TA + * @return driver error code + */ +int client_waitnotif_session(struct tee_client *client, u32 session_id, + s32 timeout, bool silent_expiry) +{ + struct tee_session *session; + int ret; + + /* Find/get session */ + session = client_get_session(client, session_id); + if (!session) + return -ENXIO; + + ret = session_waitnotif(session, timeout, silent_expiry); + /* Put session */ + session_put(session); + mc_dev_devel("session %x, exit with %d", session_id, ret); + return ret; +} + +/* + * Read session exit/termination code + */ +int client_get_session_exitcode(struct tee_client *client, u32 session_id, + s32 *exit_code) +{ + struct tee_session *session; + + /* Find/get session */ + session = client_get_session(client, session_id); + if (!session) + return -ENXIO; + + /* Retrieve error */ + *exit_code = session_exitcode(session); + /* Put session */ + session_put(session); + mc_dev_devel("session %x, exit code %d", session_id, *exit_code); + return 0; +} + +/* Share a buffer with given TA in SWd */ +int client_map_session_wsms(struct tee_client *client, u32 session_id, + struct mc_ioctl_buffer *bufs, int client_fd) +{ + struct tee_session *session; + int ret; + + /* Find/get session */ + session = client_get_session(client, session_id); + if (!session) + return -ENXIO; + + /* Add buffer to the session */ + ret = session_map(session, bufs, client_fd); + /* Put session */ + session_put(session); + mc_dev_devel("session %x, exit with %d", session_id, ret); + return ret; +} + +/* Stop sharing a buffer with SWd */ +int client_unmap_session_wsms(struct tee_client *client, u32 session_id, + const struct mc_ioctl_buffer *bufs) +{ + struct tee_session *session; + int ret; + + /* Find/get session */ + session = client_get_session(client, session_id); + if (!session) + return -ENXIO; + + /* Remove buffer from session */ + ret = session_unmap(session, bufs); + /* Put session */ + session_put(session); + mc_dev_devel("session %x, exit with %d", session_id, ret); + return ret; +} + +int client_gp_initialize_context(struct tee_client *client, + struct gp_return *gp_ret) +{ + return iwp_set_ret(0, gp_ret); +} + +int client_gp_register_shared_mem(struct tee_client *client, + const struct gp_shared_memory *memref, + struct gp_return *gp_ret, int client_fd) +{ + struct cwsm *cwsm; + + /* cwsm_find automatically takes a reference */ + cwsm = cwsm_find(client, memref); + if (!cwsm) { + cwsm = cwsm_create(client, memref, gp_ret, client_fd); + if (IS_ERR(cwsm)) + return iwp_set_ret(PTR_ERR(cwsm), gp_ret); + } + + return iwp_set_ret(0, gp_ret); +} + +int client_gp_release_shared_mem(struct tee_client *client, + const struct gp_shared_memory *memref) +{ + struct cwsm *cwsm; + int ret = 0; + + mutex_lock(&client->cwsm_release_lock); + cwsm = cwsm_find(client, memref); + if (!cwsm) { + ret = -ENOENT; + goto end; + } + + /* Release reference taken by cwsm_find */ + cwsm_put(cwsm); + cwsm_put(cwsm); +end: + mutex_unlock(&client->cwsm_release_lock); + return ret; +} + +/* + * Open TA for given client. TA binary is provided by the daemon. + * @param + * @return driver error code + */ +int client_gp_open_session(struct tee_client *client, u32 *session_id, + const struct mc_uuid_t *uuid, + struct gp_operation *operation, + struct mc_identity *identity, + struct gp_return *gp_ret, int client_fd) +{ + struct tee_object *obj; + int ret = 0; + + /* Get secure object */ + obj = tee_object_get(uuid, true); + if (IS_ERR(obj)) { + /* Try to select secure object inside the SWd if not found */ + if ((PTR_ERR(obj) == -ENOENT) && g_ctx.f_ta_auth) + obj = tee_object_select(uuid); + + if (IS_ERR(obj)) { + ret = PTR_ERR(obj); + goto end; + } + } + + /* Open session */ + ret = client_add_gp_session(client, obj, session_id, operation, + identity, client_fd, gp_ret); + + /* Delete secure object */ + tee_object_free(obj); + +end: + mc_dev_devel("gp session %x, exit with %d", *session_id, ret); + return ret; +} + +int client_gp_close_session(struct tee_client *client, u32 session_id) +{ + struct tee_session *session = NULL, *candidate; + int ret = 0; + + /* Move session from main list to closing list */ + mutex_lock(&client->sessions_lock); + list_for_each_entry(candidate, &client->sessions, list) { + if (candidate->iwp_session.sid == session_id) { + session = candidate; + list_move(&session->list, &client->closing_sessions); + break; + } + } + + mutex_unlock(&client->sessions_lock); + if (!session) + return -ENXIO; + + ret = session_close(session); + if (!ret) + client_put_session(client, session); + + return ret; +} + +/* + * Send a command to the TA + * @param + * @return driver error code + */ +int client_gp_invoke_command(struct tee_client *client, u32 session_id, + u32 command_id, + struct gp_operation *operation, + struct gp_return *gp_ret, int client_fd) +{ + struct tee_session *session; + int ret = 0; + + session = client_get_session(client, session_id); + if (!session) + return iwp_set_ret(-ENXIO, gp_ret); + + ret = session_gp_invoke_command(session, command_id, operation, gp_ret, + client_fd); + + /* Put session */ + session_put(session); + return ret; +} + +void client_gp_request_cancellation(struct tee_client *client, u32 started) +{ + struct client_gp_operation *op; + u32 slot; + bool found = false; + + /* Look for operation */ + mutex_lock(&client->quick_lock); + list_for_each_entry(op, &client->operations, list) + if (op->started == started) { + slot = op->slot; + found = true; + mc_dev_devel("found to operation cancel for started %u", + started); + break; + } + + /* Operation not found: assume it is coming */ + if (!found) { + op = kzalloc(sizeof(*op), GFP_KERNEL); + if (op) { + op->started = started; + op->cancelled = true; + list_add_tail(&op->list, &client->operations); + mc_dev_devel("add cancelled operation %p for started %u" + , op, op->started); + } + } + mutex_unlock(&client->quick_lock); + + if (found) + session_gp_request_cancellation(slot); +} + +/* + * This callback is called on remap + */ +static void cbuf_vm_open(struct vm_area_struct *vmarea) +{ + struct cbuf *cbuf = vmarea->vm_private_data; + + cbuf_get(cbuf); +} + +/* + * This callback is called on unmap + */ +static void cbuf_vm_close(struct vm_area_struct *vmarea) +{ + struct cbuf *cbuf = vmarea->vm_private_data; + + cbuf_put(cbuf); +} + +static const struct vm_operations_struct cbuf_vm_ops = { + .open = cbuf_vm_open, + .close = cbuf_vm_close, +}; + +/* + * Create a cbuf object and add it to client + */ +int client_cbuf_create(struct tee_client *client, u32 len, uintptr_t *addr, + struct vm_area_struct *vmarea) +{ + int err = 0; + struct cbuf *cbuf = NULL; + unsigned int order; + + if (!client) + return -EINVAL; + + if (!len || len > BUFFER_LENGTH_MAX) + return -EINVAL; + + order = get_order(len); + if (order > MAX_ORDER) { + mc_dev_err("Buffer size too large"); + return -ENOMEM; + } + + /* Allocate buffer descriptor structure */ + cbuf = kzalloc(sizeof(*cbuf), GFP_KERNEL); + if (!cbuf) + return -ENOMEM; + + /* Increment debug counter */ + atomic_inc(&g_ctx.c_cbufs); + /* Allocate buffer */ + cbuf->addr = __get_free_pages(GFP_USER | __GFP_ZERO, order); + if (!cbuf->addr) { + kfree(cbuf); + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_cbufs); + return -ENOMEM; + } + + /* Map to user space if applicable */ + if (!client_is_kernel(client)) { + err = cbuf_map(vmarea, cbuf->addr, len, &cbuf->uaddr); + if (err) { + free_pages(cbuf->addr, order); + kfree(cbuf); + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_cbufs); + return err; + } + } + + /* Init descriptor members */ + cbuf->client = client; + cbuf->phys = virt_to_phys((void *)cbuf->addr); + cbuf->len = len; + cbuf->order = order; + kref_init(&cbuf->kref); + INIT_LIST_HEAD(&cbuf->list); + + /* Keep cbuf in VMA private data for refcounting (user-space clients) */ + if (vmarea) { + vmarea->vm_private_data = cbuf; + vmarea->vm_ops = &cbuf_vm_ops; + } + + /* Fill return parameter for k-api */ + if (addr) + *addr = cbuf->addr; + + /* Get a token on the client */ + client_get(client); + + /* Add buffer to list */ + mutex_lock(&client->cbufs_lock); + list_add_tail(&cbuf->list, &client->cbufs); + mutex_unlock(&client->cbufs_lock); + mc_dev_devel("created cbuf %p: client %p addr %lx uaddr %lx len %u", + cbuf, client, cbuf->addr, cbuf->uaddr, cbuf->len); + return err; +} + +/* + * Find a contiguous buffer (cbuf) in the cbuf list of given client that + * contains given address and take a reference on it. + * Return pointer to the object, or NULL if not found. + */ +static struct cbuf *cbuf_get_by_addr(struct tee_client *client, uintptr_t addr) +{ + struct cbuf *cbuf = NULL, *candidate; + bool is_kernel = client_is_kernel(client); + + mutex_lock(&client->cbufs_lock); + list_for_each_entry(candidate, &client->cbufs, list) { + /* Compare to kernel VA or user VA depending on client type */ + uintptr_t start = is_kernel ? + candidate->addr : candidate->uaddr; + uintptr_t end = start + candidate->len; + + /* Check that (user) cbuf has not been unmapped */ + if (!start) + break; + + if (addr >= start && addr < end) { + cbuf = candidate; + break; + } + } + + if (cbuf) + cbuf_get(cbuf); + + mutex_unlock(&client->cbufs_lock); + return cbuf; +} + +/* + * Remove a cbuf object from client, and mark it for freeing. + * Freeing will happen once all current references are released. + * + * Note: this function could be subject to the same race condition as + * client_gp_release_shared_mem() and client_put_cwsm_sva(), but it is trusted + * as it can only be called by kernel drivers. So no lock around + * cbuf_get_by_addr() and the two tee_cbuf_put(). + */ +int client_cbuf_free(struct tee_client *client, uintptr_t addr) +{ + struct cbuf *cbuf = cbuf_get_by_addr(client, addr); + + if (!cbuf) { + mc_dev_err("cbuf %lu not found", addr); + return -EINVAL; + } + + /* Release reference taken by cbuf_get_by_addr */ + cbuf_put(cbuf); + mutex_lock(&client->cbufs_lock); + cbuf->api_freed = true; + mutex_unlock(&client->cbufs_lock); + cbuf_put(cbuf); + return 0; +} + +bool client_gp_operation_add(struct tee_client *client, + struct client_gp_operation *operation) +{ + struct client_gp_operation *op; + bool found = false; + + mutex_lock(&client->quick_lock); + list_for_each_entry(op, &client->operations, list) + if (op->started == operation->started && op->cancelled) { + found = true; + break; + } + + if (found) { + list_del(&op->list); + mc_dev_devel("found cancelled operation %p for started %u", + op, op->started); + kfree(op); + } else { + list_add_tail(&operation->list, &client->operations); + mc_dev_devel("add operation for started %u", + operation->started); + } + mutex_unlock(&client->quick_lock); + return !found; +} + +void client_gp_operation_remove(struct tee_client *client, + struct client_gp_operation *operation) +{ + mutex_lock(&client->quick_lock); + list_del(&operation->list); + mutex_unlock(&client->quick_lock); +} + +struct tee_mmu *client_mmu_create(struct tee_client *client, + const struct mc_ioctl_buffer *buf_in, + struct cbuf **cbuf_p, int client_fd) +{ + /* Check if buffer is contained in a cbuf */ + struct mc_ioctl_buffer buf = *buf_in; + struct cbuf *cbuf = cbuf_get_by_addr(client, buf.va); + struct mm_struct *mm = NULL; + struct tee_mmu *mmu; + + *cbuf_p = cbuf; + if (cbuf) { + uintptr_t offset; + + if (client_is_kernel(client)) { + offset = buf.va - cbuf->addr; + } else { + offset = buf.va - cbuf->uaddr; + /* Update va to point to kernel address */ + buf.va = cbuf->addr + offset; + } + + if ((offset + buf.len) > cbuf->len) { + mc_dev_err("crosses cbuf boundary"); + cbuf_put(cbuf); + return ERR_PTR(-EINVAL); + } + } else if (!client_is_kernel(client)) { + mm = get_mm_from_client_fd(client_fd); + if (!mm) { + mc_dev_err("can't get mm from client fd %d", client_fd); + return ERR_PTR(-EPERM); + } + } + + /* Build MMU table for buffer */ + mmu = tee_mmu_create(mm, &buf); + if (mm) + mmput(mm); + + if (IS_ERR_OR_NULL(mmu) && cbuf) + cbuf_put(cbuf); + + return mmu; +} + +void client_mmu_free(struct tee_client *client, uintptr_t va, + struct tee_mmu *mmu, struct cbuf *cbuf) +{ + tee_mmu_delete(mmu); + if (cbuf) + cbuf_put(cbuf); +} + +void client_init(void) +{ + INIT_LIST_HEAD(&client_ctx.clients); + mutex_init(&client_ctx.clients_lock); + + INIT_LIST_HEAD(&client_ctx.closing_clients); + mutex_init(&client_ctx.closing_clients_lock); +} + +static inline int cbuf_debug_structs(struct kasnprintf_buf *buf, + struct cbuf *cbuf) +{ + return kasnprintf(buf, + "\tcbuf %pK [%d]: addr %pK uaddr %pK len %u\n", + cbuf, kref_read(&cbuf->kref), (void *)cbuf->addr, + (void *)cbuf->uaddr, cbuf->len); +} + +static inline int cwsm_debug_structs(struct kasnprintf_buf *buf, + struct cwsm *cwsm) +{ + return kasnprintf(buf, + "\tcwsm %pK [%d]: buf %pK len %llu flags 0x%x\n", + cwsm, kref_read(&cwsm->kref), + (void *)(uintptr_t)cwsm->memref.buffer, + cwsm->memref.size, cwsm->memref.flags); +} + +static int client_debug_structs(struct kasnprintf_buf *buf, + struct tee_client *client, bool is_closing) +{ + struct cbuf *cbuf; + struct cwsm *cwsm; + struct tee_session *session; + int ret; + + if (client->pid) + ret = kasnprintf(buf, "client %pK [%d]: %s (%d)%s\n", + client, kref_read(&client->kref), + client->comm, client->pid, + is_closing ? " " : ""); + else + ret = kasnprintf(buf, "client %pK [%d]: [kernel]%s\n", + client, kref_read(&client->kref), + is_closing ? " " : ""); + + if (ret < 0) + return ret; + + /* Buffers */ + mutex_lock(&client->cbufs_lock); + if (list_empty(&client->cbufs)) + goto done_cbufs; + + list_for_each_entry(cbuf, &client->cbufs, list) { + ret = cbuf_debug_structs(buf, cbuf); + if (ret < 0) + goto done_cbufs; + } + +done_cbufs: + mutex_unlock(&client->cbufs_lock); + if (ret < 0) + return ret; + + /* WMSs */ + mutex_lock(&client->quick_lock); + if (list_empty(&client->cwsms)) + goto done_cwsms; + + list_for_each_entry(cwsm, &client->cwsms, list) { + ret = cwsm_debug_structs(buf, cwsm); + if (ret < 0) + goto done_cwsms; + } + +done_cwsms: + mutex_unlock(&client->quick_lock); + if (ret < 0) + return ret; + + /* Sessions */ + mutex_lock(&client->sessions_lock); + list_for_each_entry(session, &client->sessions, list) { + ret = session_debug_structs(buf, session, false); + if (ret < 0) + goto done_sessions; + } + + list_for_each_entry(session, &client->closing_sessions, list) { + ret = session_debug_structs(buf, session, true); + if (ret < 0) + goto done_sessions; + } + +done_sessions: + mutex_unlock(&client->sessions_lock); + + if (ret < 0) + return ret; + + return 0; +} + +int clients_debug_structs(struct kasnprintf_buf *buf) +{ + struct tee_client *client; + ssize_t ret = 0; + + mutex_lock(&client_ctx.clients_lock); + list_for_each_entry(client, &client_ctx.clients, list) { + ret = client_debug_structs(buf, client, false); + if (ret < 0) + break; + } + mutex_unlock(&client_ctx.clients_lock); + + if (ret < 0) + return ret; + + mutex_lock(&client_ctx.closing_clients_lock); + list_for_each_entry(client, &client_ctx.closing_clients, list) { + ret = client_debug_structs(buf, client, true); + if (ret < 0) + break; + } + mutex_unlock(&client_ctx.closing_clients_lock); + + return ret; +} diff --git a/drivers/gud/MobiCoreDriver/client.h b/drivers/gud/MobiCoreDriver/client.h new file mode 100644 index 000000000000..ff8c6edffa66 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/client.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2013-2017 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. + */ + +#ifndef _CLIENT_H_ +#define _CLIENT_H_ + +#include +#include /* TASK_COMM_LEN */ + +struct tee_object; +struct tee_client; + +/* Client */ +struct tee_client *client_create(bool is_from_kernel); +void client_get(struct tee_client *client); +int client_put(struct tee_client *client); +bool client_has_sessions(struct tee_client *client); +void client_close(struct tee_client *client); + +/* All clients */ +void clients_kill_sessions(void); + +/* MC */ +int client_open_session(struct tee_client *client, u32 *session_id, + const struct mc_uuid_t *uuid, uintptr_t tci, + size_t tci_len, bool is_gp_uuid, + struct mc_identity *identity, int client_fd); +int client_open_trustlet(struct tee_client *client, u32 *session_id, u32 spid, + uintptr_t trustlet, size_t trustlet_len, + uintptr_t tci, size_t tci_len, int client_fd); +int client_add_session(struct tee_client *client, + const struct tee_object *obj, uintptr_t tci, size_t len, + u32 *p_sid, bool is_gp_uuid, + struct mc_identity *identity, int client_fd); +int client_remove_session(struct tee_client *client, u32 session_id); +int client_notify_session(struct tee_client *client, u32 session_id); +int client_waitnotif_session(struct tee_client *client, u32 session_id, + s32 timeout, bool silent_expiry); +int client_get_session_exitcode(struct tee_client *client, u32 session_id, + s32 *exit_code); +int client_map_session_wsms(struct tee_client *client, u32 session_id, + struct mc_ioctl_buffer *bufs, int client_fd); +int client_unmap_session_wsms(struct tee_client *client, u32 session_id, + const struct mc_ioctl_buffer *bufs); + +/* GP */ +int client_gp_initialize_context(struct tee_client *client, + struct gp_return *gp_ret); +int client_gp_register_shared_mem(struct tee_client *client, + const struct gp_shared_memory *memref, + struct gp_return *gp_ret, int client_fd); +int client_gp_release_shared_mem(struct tee_client *client, + const struct gp_shared_memory *memref); +int client_gp_open_session(struct tee_client *client, u32 *session_id, + const struct mc_uuid_t *uuid, + struct gp_operation *operation, + struct mc_identity *identity, + struct gp_return *gp_ret, int client_fd); +int client_gp_close_session(struct tee_client *client, u32 session_id); +int client_gp_invoke_command(struct tee_client *client, u32 session_id, + u32 command_id, + struct gp_operation *operation, + struct gp_return *gp_ret, int client_fd); +void client_gp_request_cancellation(struct tee_client *client, u32 started); + +/* Contiguous buffer */ +int client_cbuf_create(struct tee_client *client, u32 len, uintptr_t *addr, + struct vm_area_struct *vmarea); +int client_cbuf_free(struct tee_client *client, uintptr_t addr); + +/* GP internal */ +struct client_gp_operation { + u32 started; + u32 slot; + bool cancelled; + struct list_head list; +}; + +/* Called from session when a new operation starts/ends */ +bool client_gp_operation_add(struct tee_client *client, + struct client_gp_operation *operation); +void client_gp_operation_remove(struct tee_client *client, + struct client_gp_operation *operation); + +/* MMU */ +struct cbuf; + +struct tee_mmu *client_mmu_create(struct tee_client *client, + const struct mc_ioctl_buffer *buf_in, + struct cbuf **cbuf_p, int client_fd); +void client_mmu_free(struct tee_client *client, uintptr_t buf, + struct tee_mmu *mmu, struct cbuf *cbuf); + +/* Buffer shared with SWd at client level */ +u32 client_get_cwsm_sva(struct tee_client *client, + const struct gp_shared_memory *memref); +void client_put_cwsm_sva(struct tee_client *client, u32 sva); + +/* Global */ +void client_init(void); + +/* Debug */ +int clients_debug_structs(struct kasnprintf_buf *buf); + +#endif /* _CLIENT_H_ */ diff --git a/drivers/gud/MobiCoreDriver/clientlib.c b/drivers/gud/MobiCoreDriver/clientlib.c new file mode 100644 index 000000000000..44f90e75c717 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/clientlib.c @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2013-2017 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 +#include +#include +#include +#include +#include + +#include "public/mc_user.h" +#include "public/mc_admin.h" +#include "public/mobicore_driver_api.h" + +#include "main.h" +#include "client.h" + +static enum mc_result convert(int err) +{ + switch (-err) { + case 0: + return MC_DRV_OK; + case ENOMSG: + return MC_DRV_NO_NOTIFICATION; + case EBADMSG: + return MC_DRV_ERR_NOTIFICATION; + case EAGAIN: + return MC_DRV_ERR_OUT_OF_RESOURCES; + case EHOSTDOWN: + return MC_DRV_ERR_INIT; + case ENODEV: + return MC_DRV_ERR_UNKNOWN_DEVICE; + case ENXIO: + return MC_DRV_ERR_UNKNOWN_SESSION; + case EPERM: + return MC_DRV_ERR_INVALID_OPERATION; + case EBADE: + return MC_DRV_ERR_INVALID_RESPONSE; + case ETIME: + return MC_DRV_ERR_TIMEOUT; + case ENOMEM: + return MC_DRV_ERR_NO_FREE_MEMORY; + case EUCLEAN: + return MC_DRV_ERR_FREE_MEMORY_FAILED; + case ENOTEMPTY: + return MC_DRV_ERR_SESSION_PENDING; + case EHOSTUNREACH: + return MC_DRV_ERR_DAEMON_UNREACHABLE; + case ENOENT: + return MC_DRV_ERR_INVALID_DEVICE_FILE; + case EINVAL: + return MC_DRV_ERR_INVALID_PARAMETER; + case EPROTO: + return MC_DRV_ERR_KERNEL_MODULE; + case ECOMM: + return MC_DRV_INFO_NOTIFICATION; + case EUNATCH: + return MC_DRV_ERR_NQ_FAILED; + case ERESTARTSYS: + return MC_DRV_ERR_INTERRUPTED_BY_SIGNAL; + default: + mc_dev_devel("error is %d", err); + return MC_DRV_ERR_UNKNOWN; + } +} + +static inline bool is_valid_device(u32 device_id) +{ + return device_id == MC_DEVICE_ID_DEFAULT; +} + +static struct tee_client *client; +static int open_count; +static DEFINE_MUTEX(dev_mutex); /* Lock for the device */ + +static bool clientlib_client_get(void) +{ + int ret = true; + + mutex_lock(&dev_mutex); + if (!client) + ret = false; + else + client_get(client); + + mutex_unlock(&dev_mutex); + return ret; +} + +static void clientlib_client_put(void) +{ + mutex_lock(&dev_mutex); + if (client_put(client)) + client = NULL; + + mutex_unlock(&dev_mutex); +} + +enum mc_result mc_open_device(u32 device_id) +{ + enum mc_result mc_result = MC_DRV_OK; + int ret; + + /* Check parameters */ + if (!is_valid_device(device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + mutex_lock(&dev_mutex); + /* Make sure TEE was started */ + ret = mc_wait_tee_start(); + if (ret) { + mc_dev_err("TEE failed to start, now or in the past"); + mc_result = MC_DRV_ERR_INVALID_DEVICE_FILE; + goto end; + } + + if (!open_count) + client = client_create(true); + + if (client) { + open_count++; + mc_dev_devel("successfully opened the device"); + } else { + mc_result = MC_DRV_ERR_INVALID_DEVICE_FILE; + mc_dev_err("could not open device"); + } + +end: + mutex_unlock(&dev_mutex); + return mc_result; +} +EXPORT_SYMBOL(mc_open_device); + +enum mc_result mc_close_device(u32 device_id) +{ + enum mc_result mc_result = MC_DRV_OK; + + /* Check parameters */ + if (!is_valid_device(device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + mutex_lock(&dev_mutex); + if (open_count > 1) { + open_count--; + goto end; + } + + /* Check sessions and freeze client */ + if (client_has_sessions(client)) { + mc_result = MC_DRV_ERR_SESSION_PENDING; + goto end; + } + + /* Close the device */ + client_close(client); + open_count = 0; + +end: + mutex_unlock(&dev_mutex); + clientlib_client_put(); + return mc_result; +} +EXPORT_SYMBOL(mc_close_device); + +enum mc_result mc_open_session(struct mc_session_handle *session, + const struct mc_uuid_t *uuid, u8 *tci, u32 len) +{ + struct mc_identity identity = { + .login_type = LOGIN_PUBLIC, + }; + enum mc_result ret; + + /* Check parameters */ + if (!session || !uuid) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + ret = convert(client_open_session(client, &session->session_id, uuid, + (uintptr_t)tci, len, false, + &identity, -1)); + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_open_session); + +enum mc_result mc_open_trustlet(struct mc_session_handle *session, u32 spid, + u8 *trustlet, u32 trustlet_len, + u8 *tci, u32 len) +{ + enum mc_result ret; + + /* Check parameters */ + if (!session || !trustlet) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + ret = convert(client_open_trustlet(client, &session->session_id, spid, + (uintptr_t)trustlet, trustlet_len, + (uintptr_t)tci, len, -1)); + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_open_trustlet); + +enum mc_result mc_close_session(struct mc_session_handle *session) +{ + enum mc_result ret; + + /* Check parameters */ + if (!session) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + ret = convert(client_remove_session(client, session->session_id)); + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_close_session); + +enum mc_result mc_notify(struct mc_session_handle *session) +{ + enum mc_result ret; + + /* Check parameters */ + if (!session) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + ret = convert(client_notify_session(client, session->session_id)); + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_notify); + +enum mc_result mc_wait_notification(struct mc_session_handle *session, + s32 timeout) +{ + enum mc_result ret; + + /* Check parameters */ + if (!session) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + do { + ret = convert(client_waitnotif_session(client, + session->session_id, + timeout, false)); + } while ((timeout == MC_INFINITE_TIMEOUT) && + (ret == MC_DRV_ERR_INTERRUPTED_BY_SIGNAL)); + + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_wait_notification); + +enum mc_result mc_malloc_wsm(u32 device_id, u32 align, u32 len, u8 **wsm, + u32 wsm_flags) +{ + enum mc_result ret; + uintptr_t va; + + /* Check parameters */ + if (!is_valid_device(device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!len) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!wsm) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + ret = convert(client_cbuf_create(client, len, &va, NULL)); + if (ret == MC_DRV_OK) + *wsm = (u8 *)va; + + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_malloc_wsm); + +enum mc_result mc_free_wsm(u32 device_id, u8 *wsm) +{ + enum mc_result ret; + uintptr_t va = (uintptr_t)wsm; + + /* Check parameters */ + if (!is_valid_device(device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + ret = convert(client_cbuf_free(client, va)); + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_free_wsm); + +enum mc_result mc_map(struct mc_session_handle *session, void *address, + u32 length, struct mc_bulk_map *map_info) +{ + enum mc_result ret; + struct mc_ioctl_buffer bufs[MC_MAP_MAX]; + u32 i; + + /* Check parameters */ + if (!session) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!map_info) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + bufs[0].va = (uintptr_t)address; + bufs[0].len = length; + for (i = 1; i < MC_MAP_MAX; i++) + bufs[i].va = 0; + + ret = convert(client_map_session_wsms(client, session->session_id, + bufs, -1)); + if (ret == MC_DRV_OK) { + map_info->secure_virt_addr = bufs[0].sva; + map_info->secure_virt_len = bufs[0].len; + } + + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_map); + +enum mc_result mc_unmap(struct mc_session_handle *session, void *address, + struct mc_bulk_map *map_info) +{ + enum mc_result ret; + struct mc_ioctl_buffer bufs[MC_MAP_MAX]; + u32 i; + + /* Check parameters */ + if (!session) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!map_info) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + bufs[0].va = (uintptr_t)address; + bufs[0].len = map_info->secure_virt_len; + bufs[0].sva = map_info->secure_virt_addr; + for (i = 1; i < MC_MAP_MAX; i++) + bufs[i].va = 0; + + ret = convert(client_unmap_session_wsms(client, session->session_id, + bufs)); + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_unmap); + +enum mc_result mc_get_session_error_code(struct mc_session_handle *session, + s32 *exit_code) +{ + enum mc_result ret; + + /* Check parameters */ + if (!session) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!is_valid_device(session->device_id)) + return MC_DRV_ERR_UNKNOWN_DEVICE; + + if (!exit_code) + return MC_DRV_ERR_INVALID_PARAMETER; + + if (!clientlib_client_get()) + return MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN; + + /* Call core api */ + ret = convert(client_get_session_exitcode(client, session->session_id, + exit_code)); + clientlib_client_put(); + return ret; +} +EXPORT_SYMBOL(mc_get_session_error_code); diff --git a/drivers/gud/MobiCoreDriver/clock.c b/drivers/gud/MobiCoreDriver/clock.c new file mode 100644 index 000000000000..32119f5d1217 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/clock.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2013-2016 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 "platform.h" + +#ifdef MC_CRYPTO_CLOCK_MANAGEMENT + +#include +#include +#include +#include + +#include "main.h" +#include "clock.h" + +static struct clk_context { + struct clk *mc_ce_iface_clk; + struct clk *mc_ce_core_clk; + struct clk *mc_ce_bus_clk; + struct clk *mc_ce_core_src_clk; +} clk_ctx; + +int mc_clock_init(void) +{ + int ret = 0; +#ifdef MC_CLOCK_CORESRC_DEFAULTRATE + int core_src_rate = MC_CLOCK_CORESRC_DEFAULTRATE; +#ifdef MC_CRYPTO_CLOCK_CORESRC_PROPNAME + u32 of_core_src_rate = MC_CLOCK_CORESRC_DEFAULTRATE; +#endif + /* Get core clk src */ + clk_ctx.mc_ce_core_src_clk = clk_get(g_ctx.mcd, "core_clk_src"); + if (IS_ERR(clk_ctx.mc_ce_core_src_clk)) { + ret = PTR_ERR(clk_ctx.mc_ce_core_src_clk); + mc_dev_err("cannot get core src clock: %d", ret); + goto error; + } + +#ifdef MC_CRYPTO_CLOCK_CORESRC_PROPNAME + if (of_property_read_u32(g_ctx.mcd->of_node, + MC_CRYPTO_CLOCK_CORESRC_PROPNAME, + &of_core_src_rate)) { + core_src_rate = MC_CLOCK_CORESRC_DEFAULTRATE; + mc_dev_err("cannot get ce clock frequency from DT, use %d", + core_src_rate); + } else { + core_src_rate = of_core_src_rate; + } +#endif /* MC_CRYPTO_CLOCK_CORESRC_PROPNAME */ + + ret = clk_set_rate(clk_ctx.mc_ce_core_src_clk, core_src_rate); + if (ret) { + clk_put(clk_ctx.mc_ce_core_src_clk); + clk_ctx.mc_ce_core_src_clk = NULL; + mc_dev_err("cannot set core clock src rate: %d", ret); + ret = -EIO; + goto error; + } +#endif /* MC_CLOCK_CORESRC_DEFAULTRATE */ + + /* Get core clk */ + clk_ctx.mc_ce_core_clk = clk_get(g_ctx.mcd, "core_clk"); + if (IS_ERR(clk_ctx.mc_ce_core_clk)) { + ret = PTR_ERR(clk_ctx.mc_ce_core_clk); + mc_dev_err("cannot get core clock: %d", ret); + goto error; + } + /* Get Interface clk */ + clk_ctx.mc_ce_iface_clk = clk_get(g_ctx.mcd, "iface_clk"); + if (IS_ERR(clk_ctx.mc_ce_iface_clk)) { + clk_put(clk_ctx.mc_ce_core_clk); + ret = PTR_ERR(clk_ctx.mc_ce_iface_clk); + mc_dev_err("cannot get iface clock: %d", ret); + goto error; + } + /* Get AXI clk */ + clk_ctx.mc_ce_bus_clk = clk_get(g_ctx.mcd, "bus_clk"); + if (IS_ERR(clk_ctx.mc_ce_bus_clk)) { + clk_put(clk_ctx.mc_ce_iface_clk); + clk_put(clk_ctx.mc_ce_core_clk); + ret = PTR_ERR(clk_ctx.mc_ce_bus_clk); + mc_dev_err("cannot get AXI bus clock: %d", ret); + goto error; + } + return ret; + +error: + clk_ctx.mc_ce_core_clk = NULL; + clk_ctx.mc_ce_iface_clk = NULL; + clk_ctx.mc_ce_bus_clk = NULL; + clk_ctx.mc_ce_core_src_clk = NULL; + return ret; +} + +void mc_clock_exit(void) +{ + if (clk_ctx.mc_ce_iface_clk) + clk_put(clk_ctx.mc_ce_iface_clk); + + if (clk_ctx.mc_ce_core_clk) + clk_put(clk_ctx.mc_ce_core_clk); + + if (clk_ctx.mc_ce_bus_clk) + clk_put(clk_ctx.mc_ce_bus_clk); + + if (clk_ctx.mc_ce_core_src_clk) + clk_put(clk_ctx.mc_ce_core_src_clk); +} + +int mc_clock_enable(void) +{ + int rc; + + rc = clk_prepare_enable(clk_ctx.mc_ce_core_clk); + if (rc) { + mc_dev_err("cannot enable core clock"); + goto err_core; + } + + rc = clk_prepare_enable(clk_ctx.mc_ce_iface_clk); + if (rc) { + mc_dev_err("cannot enable interface clock"); + goto err_iface; + } + + rc = clk_prepare_enable(clk_ctx.mc_ce_bus_clk); + if (rc) { + mc_dev_err("cannot enable bus clock"); + goto err_bus; + } + + return 0; + +err_bus: + clk_disable_unprepare(clk_ctx.mc_ce_iface_clk); +err_iface: + clk_disable_unprepare(clk_ctx.mc_ce_core_clk); +err_core: + return rc; +} + +void mc_clock_disable(void) +{ + if (clk_ctx.mc_ce_iface_clk) + clk_disable_unprepare(clk_ctx.mc_ce_iface_clk); + + if (clk_ctx.mc_ce_core_clk) + clk_disable_unprepare(clk_ctx.mc_ce_core_clk); + + if (clk_ctx.mc_ce_bus_clk) + clk_disable_unprepare(clk_ctx.mc_ce_bus_clk); +} + +#endif /* MC_CRYPTO_CLOCK_MANAGEMENT */ diff --git a/drivers/gud/MobiCoreDriver/clock.h b/drivers/gud/MobiCoreDriver/clock.h new file mode 100644 index 000000000000..21095499efb5 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/clock.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#ifndef _MC_CLOCK_H_ +#define _MC_CLOCK_H_ + +#include "platform.h" /* MC_CRYPTO_CLOCK_MANAGEMENT */ + +#ifdef MC_CRYPTO_CLOCK_MANAGEMENT + +/* Initialize secure crypto clocks */ +int mc_clock_init(void); +/* Free secure crypto clocks */ +void mc_clock_exit(void); +/* Enable secure crypto clocks */ +int mc_clock_enable(void); +/* Disable secure crypto clocks */ +void mc_clock_disable(void); + +#else /* MC_CRYPTO_CLOCK_MANAGEMENT */ + +static inline int mc_clock_init(void) +{ + return 0; +} + +static inline void mc_clock_exit(void) +{ +} + +static inline int mc_clock_enable(void) +{ + return 0; +} + +static inline void mc_clock_disable(void) +{ +} + +#endif /* !MC_CRYPTO_CLOCK_MANAGEMENT */ + +#endif /* _MC_CLOCK_H_ */ diff --git a/drivers/gud/MobiCoreDriver/fastcall.c b/drivers/gud/MobiCoreDriver/fastcall.c new file mode 100644 index 000000000000..85c3ef19928f --- /dev/null +++ b/drivers/gud/MobiCoreDriver/fastcall.c @@ -0,0 +1,670 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include +#include /* local_clock */ +#include +#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE +#include /* local_clock */ +#endif +#include + +#include "public/mc_user.h" +#include "public/mc_linux_api.h" + +#include "mci/mcifc.h" + +#include "platform.h" /* MC_FASTCALL_WORKER_THREAD and more */ +#include "main.h" +#include "clock.h" /* mc_clock_enable, mc_clock_disable */ +#include "fastcall.h" + +#if KERNEL_VERSION(3, 15, 0) > LINUX_VERSION_CODE +#define MIN_NICE -20 +#endif + +struct fastcall_work { +#ifdef MC_FASTCALL_WORKER_THREAD + struct kthread_work work; +#else + struct work_struct work; +#endif + void *data; +}; + +/* generic fast call parameters */ +union mc_fc_generic { + struct mc_fc_as_in { + u32 cmd; + u32 param[3]; + } as_in; + struct { + u32 resp; + u32 ret; + u32 param[2]; + } as_out; +}; + +/* fast call init */ +union mc_fc_init { + union mc_fc_generic as_generic; + struct { + u32 cmd; + u32 base; + u32 nq_info; + u32 mcp_info; + } as_in; + struct { + u32 resp; + u32 ret; + u32 flags; + u32 rfu; + } as_out; +}; + +/* fast call info parameters */ +union mc_fc_info { + union mc_fc_generic as_generic; + struct { + u32 cmd; + u32 ext_info_id; + u32 rfu[2]; + } as_in; + struct { + u32 resp; + u32 ret; + u32 state; + u32 ext_info; + } as_out; +}; + +#ifdef TBASE_CORE_SWITCHER +/* fast call switch Core parameters */ +union mc_fc_swich_core { + union mc_fc_generic as_generic; + struct { + u32 cmd; + u32 core_id; + u32 rfu[2]; + } as_in; + struct { + u32 resp; + u32 ret; + u32 state; + u32 ext_info; + } as_out; +}; +#endif + +#ifdef MC_FASTCALL_WORKER_THREAD +static struct task_struct *fastcall_thread; +static DEFINE_KTHREAD_WORKER(fastcall_worker); +#endif + +/* Structure to log SMC calls */ +struct smc_log_entry { + u64 cpu_clk; + struct mc_fc_as_in as_in; +}; + +#define SMC_LOG_SIZE 256 +static struct smc_log_entry smc_log[SMC_LOG_SIZE]; +static int smc_log_index; + +/* + * _smc() - fast call to MobiCore + * + * @data: pointer to fast call data + */ +static inline int _smc(union mc_fc_generic *mc_fc_generic) +{ + if (!mc_fc_generic) + return -EINVAL; + + /* Log SMC call */ + smc_log[smc_log_index].cpu_clk = local_clock(); + smc_log[smc_log_index].as_in = mc_fc_generic->as_in; + if (++smc_log_index >= SMC_LOG_SIZE) + smc_log_index = 0; + +#ifdef MC_SMC_FASTCALL + return smc_fastcall(mc_fc_generic, sizeof(*mc_fc_generic)); +#else /* MC_SMC_FASTCALL */ + { +#ifdef CONFIG_ARM64 + /* SMC expect values in x0-x3 */ + register u64 reg0 __asm__("x0") = mc_fc_generic->as_in.cmd; + register u64 reg1 __asm__("x1") = mc_fc_generic->as_in.param[0]; + register u64 reg2 __asm__("x2") = mc_fc_generic->as_in.param[1]; + register u64 reg3 __asm__("x3") = mc_fc_generic->as_in.param[2]; + + /* + * According to AARCH64 SMC Calling Convention (ARM DEN 0028A), + * section 3.1: registers x4-x17 are unpredictable/scratch + * registers. So we have to make sure that the compiler does + * not allocate any of those registers by letting him know that + * the asm code might clobber them. + */ + __asm__ volatile ( + "smc #0\n" + : "+r"(reg0), "+r"(reg1), "+r"(reg2), "+r"(reg3) + : + : "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", + "x12", "x13", "x14", "x15", "x16", "x17" + ); +#else /* CONFIG_ARM64 */ + /* SMC expect values in r0-r3 */ + register u32 reg0 __asm__("r0") = mc_fc_generic->as_in.cmd; + register u32 reg1 __asm__("r1") = mc_fc_generic->as_in.param[0]; + register u32 reg2 __asm__("r2") = mc_fc_generic->as_in.param[1]; + register u32 reg3 __asm__("r3") = mc_fc_generic->as_in.param[2]; + + __asm__ volatile ( +#ifdef MC_ARCH_EXTENSION_SEC + /* + * This pseudo op is supported and required from + * binutils 2.21 on + */ + ".arch_extension sec\n" +#endif /* MC_ARCH_EXTENSION_SEC */ + "smc #0\n" + : "+r"(reg0), "+r"(reg1), "+r"(reg2), "+r"(reg3) + ); + +#ifdef __ARM_VE_A9X4_QEMU__ + /* + * Qemu does not return to the address following the SMC + * instruction so we have to insert several nop instructions to + * workaround this Qemu bug. + */ + __asm__ volatile ( + "nop\n" + "nop\n" + "nop\n" + "nop" + ); +#endif /* __ARM_VE_A9X4_QEMU__ */ +#endif /* !CONFIG_ARM64 */ + + /* set response */ + mc_fc_generic->as_out.resp = reg0; + mc_fc_generic->as_out.ret = reg1; + mc_fc_generic->as_out.param[0] = reg2; + mc_fc_generic->as_out.param[1] = reg3; + } + return 0; +#endif /* !MC_SMC_FASTCALL */ +} + +#ifdef TBASE_CORE_SWITCHER +static int active_cpu; + +#ifdef MC_FASTCALL_WORKER_THREAD +static int mc_cpu_offline(int cpu) +{ + int i; + + if (active_cpu != cpu) { + mc_dev_devel("not active CPU, no action taken"); + return 0; + } + + /* Chose the first online CPU and switch! */ + for_each_online_cpu(i) { + if (cpu != i) { + mc_dev_info("CPU %d is dying, switching to %d", + cpu, i); + return mc_switch_core(i); + } + + mc_dev_devel("Skipping CPU %d", cpu); + } + + return 0; +} + +#if KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE +static int mobicore_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + int cpu = (int)(uintptr_t)hcpu; + + switch (action) { + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: + mc_dev_devel("Cpu %d is going to die", cpu); + mc_cpu_offline(cpu); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block mobicore_cpu_notifer = { + .notifier_call = mobicore_cpu_callback, +}; +#else +static int nq_cpu_down_prep(unsigned int cpu) +{ + mc_dev_info("CPU #%d is going to die", cpu); + return mc_cpu_offline(cpu); +} +#endif +#endif /* MC_FASTCALL_WORKER_THREAD */ + +static cpumask_t mc_exec_core_switch(union mc_fc_generic *mc_fc_generic) +{ + cpumask_t cpu; + int new_cpu; + u32 cpu_id[] = CPU_IDS; + + new_cpu = mc_fc_generic->as_in.param[0]; + mc_fc_generic->as_in.param[0] = cpu_id[mc_fc_generic->as_in.param[0]]; + + if (_smc(mc_fc_generic) != 0 || mc_fc_generic->as_out.ret != 0) { + mc_dev_err("CoreSwap failed %d -> %d (cpu %d still active)", + raw_smp_processor_id(), new_cpu, + raw_smp_processor_id()); + } else { + active_cpu = new_cpu; + mc_dev_devel("CoreSwap ok %d -> %d", + raw_smp_processor_id(), active_cpu); + } + cpumask_clear(&cpu); + cpumask_set_cpu(active_cpu, &cpu); + return cpu; +} + +static ssize_t debug_coreswitch_write(struct file *file, + const char __user *buffer, + size_t buffer_len, loff_t *x) +{ + int new_cpu = 0; + + /* Invalid data, nothing to do */ + if (buffer_len < 1) + return -EINVAL; + + if (kstrtoint_from_user(buffer, buffer_len, 0, &new_cpu)) + return -EINVAL; + + mc_dev_devel("Set active cpu to %d", new_cpu); + mc_switch_core(new_cpu); + return buffer_len; +} + +static ssize_t debug_coreswitch_read(struct file *file, char __user *buffer, + size_t buffer_len, loff_t *ppos) +{ + char cpu_str[8]; + int ret = 0; + + ret = snprintf(cpu_str, sizeof(cpu_str), "%d\n", mc_active_core()); + if (ret < 0) + return -EINVAL; + + return simple_read_from_buffer(buffer, buffer_len, ppos, + cpu_str, ret); +} + +static const struct file_operations mc_debug_coreswitch_ops = { + .write = debug_coreswitch_write, + .read = debug_coreswitch_read, +}; +#else /* TBASE_CORE_SWITCHER */ +static inline cpumask_t mc_exec_core_switch(union mc_fc_generic *mc_fc_generic) +{ + return CPU_MASK_CPU0; +} +#endif /* !TBASE_CORE_SWITCHER */ + +#ifdef MC_SMC_FASTCALL +static inline int nq_set_cpus_allowed(struct task_struct *p, cpumask_t new_mask) +{ + return 0; +} +#else /* MC_SMC_FASTCALL */ +static inline int nq_set_cpus_allowed(struct task_struct *p, cpumask_t new_mask) +{ + return set_cpus_allowed_ptr(p, &new_mask); +} +#endif /* ! MC_SMC_FASTCALL */ + +#ifdef MC_FASTCALL_WORKER_THREAD +static void fastcall_work_func(struct kthread_work *work) +#else +static void fastcall_work_func(struct work_struct *work) +#endif +{ + struct fastcall_work *fc_work = + container_of(work, struct fastcall_work, work); + union mc_fc_generic *mc_fc_generic = fc_work->data; + + if (!mc_fc_generic) + return; + + mc_clock_enable(); + + if (mc_fc_generic->as_in.cmd == MC_FC_SWAP_CPU) { +#ifdef MC_FASTCALL_WORKER_THREAD + cpumask_t new_msk = mc_exec_core_switch(mc_fc_generic); + + nq_set_cpus_allowed(fastcall_thread, new_msk); +#else + mc_exec_core_switch(mc_fc_generic); +#endif + } else { + _smc(mc_fc_generic); + } + + mc_clock_disable(); +} + +static bool mc_fastcall(void *data) +{ +#ifdef MC_FASTCALL_WORKER_THREAD + struct fastcall_work fc_work = { + KTHREAD_WORK_INIT(fc_work.work, fastcall_work_func), + .data = data, + }; + +#if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE + if (!kthread_queue_work(&fastcall_worker, &fc_work.work)) + return false; + + /* If work is queued or executing, wait for it to finish execution */ + kthread_flush_work(&fc_work.work); +#else + if (!queue_kthread_work(&fastcall_worker, &fc_work.work)) + return false; + + /* If work is queued or executing, wait for it to finish execution */ + flush_kthread_work(&fc_work.work); +#endif +#else + struct fastcall_work fc_work = { + .data = data, + }; + INIT_WORK_ONSTACK(&fc_work.work, fastcall_work_func); + + if (!schedule_work_on(0, &fc_work.work)) + return false; + + flush_work(&fc_work.work); +#endif + return true; +} + +int mc_fastcall_init(void) +{ + int ret = mc_clock_init(); + + if (ret) + return ret; + +#ifdef MC_FASTCALL_WORKER_THREAD + fastcall_thread = kthread_create(kthread_worker_fn, &fastcall_worker, + "tee_fastcall"); + if (IS_ERR(fastcall_thread)) { + ret = PTR_ERR(fastcall_thread); + fastcall_thread = NULL; + mc_dev_err("cannot create fastcall wq: %d", ret); + return ret; + } + + set_user_nice(fastcall_thread, MIN_NICE); + + /* this thread MUST run on CPU 0 at startup */ + nq_set_cpus_allowed(fastcall_thread, CPU_MASK_CPU0); + + wake_up_process(fastcall_thread); +#ifdef TBASE_CORE_SWITCHER +#if KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE + ret = register_cpu_notifier(&mobicore_cpu_notifer); +#else + ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, + "tee/trustonic:online", + NULL, nq_cpu_down_prep); +#endif + if (ret < 0) { + mc_dev_err("cpu online callback setup failed: %d", ret); + return ret; + } + + /* Create debugfs structs entry */ + debugfs_create_file("active_cpu", 0600, g_ctx.debug_dir, NULL, + &mc_debug_coreswitch_ops); +#endif +#endif /* MC_FASTCALL_WORKER_THREAD */ + return 0; +} + +void mc_fastcall_exit(void) +{ +#ifdef MC_FASTCALL_WORKER_THREAD + if (!IS_ERR_OR_NULL(fastcall_thread)) { +#ifdef TBASE_CORE_SWITCHER +#if KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE + unregister_cpu_notifier(&mobicore_cpu_notifer); +#else + cpuhp_remove_state_nocalls(CPUHP_AP_ONLINE_DYN); +#endif +#endif + kthread_stop(fastcall_thread); + fastcall_thread = NULL; + } +#endif /* MC_FASTCALL_WORKER_THREAD */ + mc_clock_exit(); +} + +/* + * convert fast call return code to linux driver module error code + */ +static int convert_fc_ret(u32 ret) +{ + switch (ret) { + case MC_FC_RET_OK: + return 0; + case MC_FC_RET_ERR_INVALID: + return -EINVAL; + case MC_FC_RET_ERR_ALREADY_INITIALIZED: + return -EBUSY; + default: + return -EFAULT; + } +} + +int mc_fc_init(uintptr_t base_pa, ptrdiff_t off, size_t q_len, size_t buf_len) +{ +#ifdef CONFIG_ARM64 + u32 base_high = (u32)(base_pa >> 32); +#else + u32 base_high = 0; +#endif + union mc_fc_init fc_init; + + /* Call the INIT fastcall to setup MobiCore initialization */ + memset(&fc_init, 0, sizeof(fc_init)); + fc_init.as_in.cmd = MC_FC_INIT; + /* base address of mci buffer PAGE_SIZE (default is 4KB) aligned */ + fc_init.as_in.base = (u32)base_pa; + /* notification buffer start/length [16:16] [start, length] */ + fc_init.as_in.nq_info = + (u32)(((base_high & 0xFFFF) << 16) | (q_len & 0xFFFF)); + /* mcp buffer start/length [16:16] [start, length] */ + fc_init.as_in.mcp_info = (u32)((off << 16) | (buf_len & 0xFFFF)); + mc_dev_devel("cmd=%d, base=0x%08x,nq_info=0x%08x, mcp_info=0x%08x", + fc_init.as_in.cmd, fc_init.as_in.base, + fc_init.as_in.nq_info, fc_init.as_in.mcp_info); + mc_fastcall(&fc_init.as_generic); + mc_dev_devel("out cmd=0x%08x, ret=0x%08x", fc_init.as_out.resp, + fc_init.as_out.ret); + if (fc_init.as_out.flags & MC_FC_INIT_FLAG_LPAE) + g_ctx.f_lpae = true; + + return convert_fc_ret(fc_init.as_out.ret); +} + +int mc_fc_info(u32 ext_info_id, u32 *state, u32 *ext_info) +{ + union mc_fc_info fc_info; + int ret = 0; + + memset(&fc_info, 0, sizeof(fc_info)); + fc_info.as_in.cmd = MC_FC_INFO; + fc_info.as_in.ext_info_id = ext_info_id; + mc_fastcall(&fc_info.as_generic); + ret = convert_fc_ret(fc_info.as_out.ret); + if (ret) { + if (state) + *state = MC_STATUS_NOT_INITIALIZED; + + if (ext_info) + *ext_info = 0; + + mc_dev_err("code %d for idx %d", ret, ext_info_id); + } else { + if (state) + *state = fc_info.as_out.state; + + if (ext_info) + *ext_info = fc_info.as_out.ext_info; + } + + return ret; +} + +int mc_fc_mem_trace(phys_addr_t buffer, u32 size) +{ + union mc_fc_generic mc_fc_generic; + + memset(&mc_fc_generic, 0, sizeof(mc_fc_generic)); + mc_fc_generic.as_in.cmd = MC_FC_MEM_TRACE; + mc_fc_generic.as_in.param[0] = (u32)buffer; +#ifdef CONFIG_ARM64 + mc_fc_generic.as_in.param[1] = (u32)(buffer >> 32); +#endif + mc_fc_generic.as_in.param[2] = size; + mc_fastcall(&mc_fc_generic); + return convert_fc_ret(mc_fc_generic.as_out.ret); +} + +int mc_fc_nsiq(void) +{ + union mc_fc_generic fc; + int ret; + + memset(&fc, 0, sizeof(fc)); + fc.as_in.cmd = MC_SMC_N_SIQ; + mc_fastcall(&fc); + ret = convert_fc_ret(fc.as_out.ret); + if (ret) + mc_dev_err("failed: %d", ret); + + return ret; +} + +int mc_fc_yield(void) +{ + union mc_fc_generic fc; + int ret; + + memset(&fc, 0, sizeof(fc)); + fc.as_in.cmd = MC_SMC_N_YIELD; + mc_fastcall(&fc); + ret = convert_fc_ret(fc.as_out.ret); + if (ret) + mc_dev_err("failed: %d", ret); + + return ret; +} + +static int show_smc_log_entry(struct kasnprintf_buf *buf, + struct smc_log_entry *entry) +{ + return kasnprintf(buf, "%20llu %10d 0x%08x 0x%08x 0x%08x\n", + entry->cpu_clk, (s32)entry->as_in.cmd, + entry->as_in.param[0], entry->as_in.param[1], + entry->as_in.param[2]); +} + +/* + * Dump SMC log circular buffer, starting from oldest command. It is assumed + * nothing goes in any more at this point. + */ +int mc_fastcall_debug_smclog(struct kasnprintf_buf *buf) +{ + int i, ret = 0; + + ret = kasnprintf(buf, "%20s %10s %-10s %-10s %-10s\n", + "CPU clock", "command", "param1", "param2", "param3"); + if (ret < 0) + return ret; + + if (smc_log[smc_log_index].cpu_clk) + /* Buffer has wrapped around, dump end (oldest records) */ + for (i = smc_log_index; i < SMC_LOG_SIZE; i++) { + ret = show_smc_log_entry(buf, &smc_log[i]); + if (ret < 0) + return ret; + } + + /* Dump first records */ + for (i = 0; i < smc_log_index; i++) { + ret = show_smc_log_entry(buf, &smc_log[i]); + if (ret < 0) + return ret; + } + + return ret; +} + +#ifdef TBASE_CORE_SWITCHER +int mc_active_core(void) +{ + return active_cpu; +} + +int mc_switch_core(int cpu) +{ + s32 ret = 0; + union mc_fc_swich_core fc_switch_core; + + if (cpu >= nr_cpu_ids) + return -EINVAL; + + if (!cpu_online(cpu)) + return 1; + + memset(&fc_switch_core, 0, sizeof(fc_switch_core)); + fc_switch_core.as_in.cmd = MC_FC_SWAP_CPU; + if (cpu < COUNT_OF_CPUS) + fc_switch_core.as_in.core_id = cpu; + else + fc_switch_core.as_in.core_id = 0; + + mc_dev_devel("<- cmd=0x%08x, core_id=0x%08x", + fc_switch_core.as_in.cmd, fc_switch_core.as_in.core_id); + mc_dev_devel("<- cpu=0x%08x, active_cpu=0x%08x", + cpu, active_cpu); + mc_fastcall(&fc_switch_core.as_generic); + ret = convert_fc_ret(fc_switch_core.as_out.ret); + mc_dev_devel("exit with %d/0x%08X", ret, ret); + return ret; +} +#endif diff --git a/drivers/gud/MobiCoreDriver/fastcall.h b/drivers/gud/MobiCoreDriver/fastcall.h new file mode 100644 index 000000000000..805ca03b47cf --- /dev/null +++ b/drivers/gud/MobiCoreDriver/fastcall.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef _TBASE_FASTCALL_H_ +#define _TBASE_FASTCALL_H_ + +/* Use the arch_extension sec pseudo op before switching to secure world */ +#if defined(__GNUC__) && \ + defined(__GNUC_MINOR__) && \ + defined(__GNUC_PATCHLEVEL__) && \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)) \ + >= 40502 +#ifndef CONFIG_ARM64 +#define MC_ARCH_EXTENSION_SEC +#endif +#endif + +int mc_fc_init(uintptr_t base_pa, ptrdiff_t off, size_t q_len, size_t buf_len); +int mc_fc_info(u32 ext_info_id, u32 *state, u32 *ext_info); +int mc_fc_mem_trace(phys_addr_t buffer, u32 size); +int mc_fc_nsiq(void); +int mc_fc_yield(void); + +int mc_fastcall_init(void); +void mc_fastcall_exit(void); + +int mc_fastcall_debug_smclog(struct kasnprintf_buf *buf); + +#endif /* _TBASE_FASTCALL_H_ */ diff --git a/drivers/gud/MobiCoreDriver/iwp.c b/drivers/gud/MobiCoreDriver/iwp.c new file mode 100644 index 000000000000..db9a99ad6f62 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/iwp.c @@ -0,0 +1,716 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "public/GP/tee_client_api.h" /* GP error codes/origins FIXME move */ +#include "public/mc_user.h" +#include "public/mc_admin.h" + +#include "mci/mcimcp.h" +#include "mci/mcifc.h" +#include "mci/mcinq.h" /* SID_MCP */ +#include "mci/mcitime.h" /* struct mcp_time */ +#include "mci/mciiwp.h" + +#include "platform.h" /* IRQ number */ +#include "main.h" +#include "fastcall.h" +#include "logging.h" +#include "nq.h" +#include "iwp.h" + +#define IWP_RETRIES 5 +#define IWP_TIMEOUT 10 +#define INVALID_IWS_SLOT 0xFFFFFFFF + +/* Macros */ +#define _TEEC_GET_PARAM_TYPE(t, i) (((t) >> (4 * (i))) & 0xF) + +/* Parameter number */ +#define _TEEC_PARAMETER_NUMBER 4 + +struct iws { + struct list_head list; + u32 offset; +}; + +static struct { + struct interworld_session *iws; + /* Interworld lists lock */ + struct mutex iws_list_lock; + /* InterWorld lists */ + struct iws *iws_list_pool; + struct list_head free_iws; + struct list_head allocd_iws; + /* Sessions */ + struct mutex sessions_lock; + struct list_head sessions; +} l_ctx; + +static void iwp_notif_handler(u32 id, u32 payload) +{ + struct iwp_session *iwp_session = NULL, *candidate; + + mutex_lock(&l_ctx.sessions_lock); + list_for_each_entry(candidate, &l_ctx.sessions, list) { + mc_dev_devel("candidate->slot [%08x]", + candidate->slot); + /* If id is SID_CANCEL_OPERATION, there is pseudo session */ + if (candidate->slot == payload && + (id != SID_CANCEL_OPERATION || candidate->sid == id)) { + iwp_session = candidate; + break; + } + } + mutex_unlock(&l_ctx.sessions_lock); + + if (!iwp_session) { + mc_dev_err("IWP no session found for id=0x%x slot=0x%x", + id, payload); + return; + } + + mc_dev_devel("IWP: iwp_session [%p] id [%08x] slot [%08x]", + iwp_session, id, payload); + complete(&iwp_session->completion); +} + +void iwp_session_init(struct iwp_session *iwp_session, + const struct identity *identity) +{ + nq_session_init(&iwp_session->nq_session, true); + iwp_session->sid = SID_INVALID; + iwp_session->slot = (u32)-1; /* 0 is a valid slot */ + INIT_LIST_HEAD(&iwp_session->list); + mutex_init(&iwp_session->notif_wait_lock); + init_completion(&iwp_session->completion); + mutex_init(&iwp_session->exit_code_lock); + iwp_session->exit_code = 0; + mutex_init(&iwp_session->iws_lock); + iwp_session->state = IWP_SESSION_RUNNING; + if (identity) + iwp_session->client_identity = *identity; +} + +static u32 iws_slot_get(void) +{ + struct iws *iws; + u32 ret = INVALID_IWS_SLOT; + + mutex_lock(&l_ctx.iws_list_lock); + if (!list_empty(&l_ctx.free_iws)) { + iws = list_first_entry(&l_ctx.free_iws, struct iws, list); + ret = iws->offset; + list_move(&iws->list, &l_ctx.allocd_iws); + atomic_inc(&g_ctx.c_slots); + } + mutex_unlock(&l_ctx.iws_list_lock); + return ret; +} + +/* Passing INVALID_IWS_SLOT is supported */ +static void iws_slot_put(u32 offset) +{ + struct iws *iws; + + mutex_lock(&l_ctx.iws_list_lock); + list_for_each_entry(iws, &l_ctx.allocd_iws, list) { + if (offset == iws->offset) { + list_move(&iws->list, &l_ctx.free_iws); + atomic_dec(&g_ctx.c_slots); + break; + } + } + mutex_unlock(&l_ctx.iws_list_lock); +} + +static inline struct interworld_session *slot_to_iws(u32 slot) +{ + return (struct interworld_session *)((uintptr_t)l_ctx.iws + slot); +} + +/* + * IWP command functions + */ +static int iwp_cmd(struct iwp_session *iwp_session, u32 id) +{ + struct completion *completion; + int ret; + + mc_dev_devel("psid [%08x], sid [%08x]", id, iwp_session->sid); + ret = nq_session_notify(&iwp_session->nq_session, id, + iwp_session->slot); + if (ret) { + mc_dev_err("sid [%08x]: sending failed, ret = %d", + iwp_session->sid, ret); + return ret; + } + + completion = &iwp_session->completion; + + /* + * NB: Wait cannot be interruptible as we need an answer from SWd. It's + * up to the user-space to request a cancellation (for open session and + * command invocation operations.) + */ + wait_for_completion(completion); + return 0; +} + +/* + * Convert errno into GP error and set origin to COMMS. + * Note: -ECHILD is used to tell the caller that we have a GP error in value, so + * we return 0 on success and -ECHILD on error. If -ECHILD is given, we assume + * that value is already correctly set. + */ +int iwp_set_ret(int ret, struct gp_return *gp_ret) +{ + if (ret == -ECHILD) { + /* Already set */ + return ret; + } + + gp_ret->origin = TEEC_ORIGIN_COMMS; + switch (ret) { + case 0: + gp_ret->origin = TEEC_ORIGIN_TRUSTED_APP; + gp_ret->value = TEEC_SUCCESS; + return 0; + case -EACCES: + gp_ret->value = TEEC_ERROR_ACCESS_DENIED; + break; + case -EBUSY: + gp_ret->value = TEEC_ERROR_BUSY; + break; + case -ECANCELED: + gp_ret->value = TEEC_ERROR_CANCEL; + break; + case -EINVAL: + case -EFAULT: + gp_ret->value = TEEC_ERROR_BAD_PARAMETERS; + break; + case -EKEYREJECTED: + gp_ret->value = TEEC_ERROR_SECURITY; + break; + case -ENOENT: + gp_ret->value = TEEC_ERROR_ITEM_NOT_FOUND; + break; + case -ENOMEM: + gp_ret->value = TEEC_ERROR_OUT_OF_MEMORY; + break; + case -ENXIO: + /* Session not found or not running */ + gp_ret->value = TEEC_ERROR_BAD_STATE; + break; + default: + gp_ret->value = TEEC_ERROR_GENERIC; + } + return -ECHILD; +} + +int iwp_register_shared_mem(struct mcp_buffer_map *map, + struct gp_return *gp_ret) +{ + int ret = 0; + + ret = mcp_map(SID_MEMORY_REFERENCE, map); + /* iwp_set_ret would override the origin if called after */ + ret = iwp_set_ret(ret, gp_ret); + if (ret) + gp_ret->origin = TEEC_ORIGIN_TEE; + + return ret; +} + +int iwp_release_shared_mem(struct mcp_buffer_map *map) +{ + return mcp_unmap(SID_MEMORY_REFERENCE, map); +} + +static int iwp_operation_to_iws(struct gp_operation *operation, + struct interworld_session *iws, + struct mc_ioctl_buffer *bufs, + struct gp_shared_memory **parents) +{ + int param_type, i; + + iws->param_types = 0; + for (i = 0; i < _TEEC_PARAMETER_NUMBER; i++) { + /* Reset reference for temporary memory */ + bufs[i].va = 0; + /* Reset reference for registered memory */ + parents[i] = NULL; + param_type = _TEEC_GET_PARAM_TYPE(operation->param_types, i); + + switch (param_type) { + case TEEC_NONE: + case TEEC_VALUE_OUTPUT: + break; + case TEEC_VALUE_INPUT: + case TEEC_VALUE_INOUT: + iws->params[i].value.a = operation->params[i].value.a; + iws->params[i].value.b = operation->params[i].value.b; + break; + case TEEC_MEMREF_TEMP_INPUT: + case TEEC_MEMREF_TEMP_OUTPUT: + case TEEC_MEMREF_TEMP_INOUT: + if (operation->params[i].tmpref.buffer) { + /* Prepare buffer to map */ + bufs[i].va = operation->params[i].tmpref.buffer; + bufs[i].len = operation->params[i].tmpref.size; + if (param_type == TEEC_MEMREF_TEMP_INPUT) + bufs[i].flags = MC_IO_MAP_INPUT; + else if (param_type == TEEC_MEMREF_TEMP_OUTPUT) + bufs[i].flags = MC_IO_MAP_OUTPUT; + else + bufs[i].flags = MC_IO_MAP_INPUT_OUTPUT; + } else { + if (operation->params[i].tmpref.size) + return -EINVAL; + + /* Null buffer, won't get mapped */ + iws->params[i].tmpref.physical_address = 0; + iws->params[i].tmpref.size = 0; + iws->params[i].tmpref.offset = 0; + iws->params[i].tmpref.wsm_type = WSM_INVALID; + } + break; + case TEEC_MEMREF_WHOLE: + parents[i] = &operation->params[i].memref.parent; + iws->params[i].memref.offset = 0; + iws->params[i].memref.size = + operation->params[i].memref.parent.size; + break; + case TEEC_MEMREF_PARTIAL_INPUT: + case TEEC_MEMREF_PARTIAL_OUTPUT: + case TEEC_MEMREF_PARTIAL_INOUT: + parents[i] = &operation->params[i].memref.parent; + iws->params[i].memref.offset = + operation->params[i].memref.offset; + iws->params[i].memref.size = + operation->params[i].memref.size; + break; + default: + return -EINVAL; + } + + iws->param_types |= (u32)(param_type << (i * 4)); + } + + return 0; +} + +static inline void iwp_iws_set_tmpref(struct interworld_session *iws, int i, + const struct mcp_buffer_map *map) +{ + iws->params[i].tmpref.physical_address = map->phys_addr; + iws->params[i].tmpref.size = map->length; + iws->params[i].tmpref.offset = map->offset; + iws->params[i].tmpref.wsm_type = map->type; +} + +static inline void iwp_iws_set_memref(struct interworld_session *iws, int i, + u32 sva) +{ + iws->params[i].memref.memref_handle = sva; +} + +static inline void iwp_iws_set_refs(struct interworld_session *iws, + const struct iwp_buffer_map *maps) +{ + int i; + + for (i = 0; i < _TEEC_PARAMETER_NUMBER; i++) + if (maps[i].sva) + iwp_iws_set_memref(iws, i, maps[i].sva); + else if (maps[i].map.phys_addr) + iwp_iws_set_tmpref(iws, i, &maps[i].map); +} + +static void iwp_iws_to_operation(const struct interworld_session *iws, + struct gp_operation *operation) +{ + int i; + + for (i = 0; i < _TEEC_PARAMETER_NUMBER; i++) { + switch (_TEEC_GET_PARAM_TYPE(operation->param_types, i)) { + case TEEC_VALUE_OUTPUT: + case TEEC_VALUE_INOUT: + operation->params[i].value.a = iws->params[i].value.a; + operation->params[i].value.b = iws->params[i].value.b; + break; + case TEEC_MEMREF_TEMP_OUTPUT: + case TEEC_MEMREF_TEMP_INOUT: + operation->params[i].tmpref.size = + iws->params[i].tmpref.size; + break; + case TEEC_MEMREF_WHOLE: + if (operation->params[i].memref.parent.flags != + TEEC_MEM_INPUT) + operation->params[i].memref.size = + iws->params[i].tmpref.size; + break; + case TEEC_MEMREF_PARTIAL_OUTPUT: + case TEEC_MEMREF_PARTIAL_INOUT: + operation->params[i].memref.size = + iws->params[i].tmpref.size; + break; + case TEEC_NONE: + case TEEC_VALUE_INPUT: + case TEEC_MEMREF_TEMP_INPUT: + case TEEC_MEMREF_PARTIAL_INPUT: + break; + default: + /* Error caught by iwp_operation_to_iws() */ + break; + } + } +} + +static inline void mcuuid_to_tee_uuid(const struct mc_uuid_t *in, + struct teec_uuid *out) +{ + /* + * Warning: this code works only on little-endian platforms. + */ + out->time_low = in->value[3] + + (in->value[2] << 8) + + (in->value[1] << 16) + + (in->value[0] << 24); + out->time_mid = in->value[5] + + (in->value[4] << 8); + out->time_hi_and_version = in->value[7] + + (in->value[6] << 8); + memcpy(out->clock_seq_and_node, in->value + 8, 8); +} + +int iwp_open_session_prepare( + struct iwp_session *iwp_session, + const struct tee_object *obj, + struct gp_operation *operation, + struct mc_ioctl_buffer *bufs, + struct gp_shared_memory **parents, + struct gp_return *gp_ret) +{ + struct interworld_session *iws; + union mclf_header *header; + u32 slot, temp_slot; + int ret; + + /* Get session final slot */ + slot = iws_slot_get(); + mc_dev_devel("slot [%08x]", slot); + if (slot == INVALID_IWS_SLOT) { + mc_dev_err("can't get slot"); + return iwp_set_ret(-ENOMEM, gp_ret); + } + + /* Get session temporary slot */ + temp_slot = iws_slot_get(); + mc_dev_devel("temp_slot [%08x]", temp_slot); + if (temp_slot == INVALID_IWS_SLOT) { + mc_dev_err("can't get temp_slot"); + iws_slot_put(slot); + return iwp_set_ret(-ENOMEM, gp_ret); + } + + mutex_lock(&iwp_session->iws_lock); + + /* Prepare final session: refer to temporary slot in final one */ + iwp_session->slot = slot; + iws = slot_to_iws(slot); + memset(iws, 0, sizeof(*iws)); + iws->command_id = temp_slot; + + /* Prepare temporary session */ + iws = slot_to_iws(temp_slot); + memset(iws, 0, sizeof(*iws)); + header = (union mclf_header *)(obj->data + obj->header_length); + mcuuid_to_tee_uuid(&header->mclf_header_v2.uuid, &iws->target_uuid); + iws->login = iwp_session->client_identity.login_type; + mc_dev_devel("iws->login [%08x]", iws->login); + memcpy(&iws->client_uuid, iwp_session->client_identity.login_data, + sizeof(iws->client_uuid)); + ret = iwp_operation_to_iws(operation, iws, bufs, parents); + if (ret) + iwp_open_session_abort(iwp_session); + + return iwp_set_ret(ret, gp_ret); +} + +void iwp_open_session_abort(struct iwp_session *iwp_session) +{ + struct interworld_session *iws = slot_to_iws(iwp_session->slot); + + iws_slot_put(iws->command_id); + iws_slot_put(iwp_session->slot); + mutex_unlock(&iwp_session->iws_lock); +} + +/* + * Like open session except we pass the TA blob from NWd to SWd + */ +int iwp_open_session( + struct iwp_session *iwp_session, + struct gp_operation *operation, + struct mcp_buffer_map *ta_map, + const struct iwp_buffer_map *maps, + struct gp_return *gp_ret) +{ + struct interworld_session *iws = slot_to_iws(iwp_session->slot); + /* command_id is zero'd by the SWd */ + u32 temp_slot = iws->command_id; + struct interworld_session *temp_iws = slot_to_iws(temp_slot); + int ret; + + /* Put ingoing operation in temporary IWS */ + iwp_iws_set_refs(temp_iws, maps); + + /* TA blob handling */ + iws->param_types = TEEC_MEMREF_TEMP_INPUT; + iws->session_handle = 0; + iwp_iws_set_tmpref(iws, 0, ta_map); + + mc_dev_devel("wsm_type [%04x], offset [%04x]", ta_map->type, + ta_map->offset); + mc_dev_devel("size [%08x], physical_address [%08x %08x]", + ta_map->length, (u32)(ta_map->phys_addr >> 32), + (u32)(ta_map->phys_addr & 0xFFFFFFFF)); + + /* Add to local list of sessions so we can receive the notification */ + mutex_lock(&l_ctx.sessions_lock); + list_add_tail(&iwp_session->list, &l_ctx.sessions); + mutex_unlock(&l_ctx.sessions_lock); + + /* Send IWP open command */ + ret = iwp_cmd(iwp_session, SID_OPEN_TA); + /* Temporary slot is not needed any more */ + iws_slot_put(temp_slot); + /* Treat remote errors as errors, just use a specific errno */ + if (!ret && iws->status != TEEC_SUCCESS) { + gp_ret->origin = iws->return_origin; + gp_ret->value = iws->status; + ret = -ECHILD; + } + + if (!ret) { + /* set unique identifier for list search */ + iwp_session->sid = iws->session_handle; + /* Get outgoing operation from main IWS */ + iwp_iws_to_operation(iws, operation); + } else { + /* Remove from list of sessions */ + mutex_lock(&l_ctx.sessions_lock); + list_del(&iwp_session->list); + mutex_unlock(&l_ctx.sessions_lock); + iws_slot_put(iwp_session->slot); + mc_dev_devel("failed with ret [%08x]", ret); + } + + mutex_unlock(&iwp_session->iws_lock); + return iwp_set_ret(ret, gp_ret); +} + +static void iwp_session_release( + struct iwp_session *iwp_session) +{ + iwp_session->state = IWP_SESSION_CLOSED; + + /* Remove from list of sessions */ + mutex_lock(&l_ctx.sessions_lock); + list_del(&iwp_session->list); + mutex_unlock(&l_ctx.sessions_lock); + + nq_session_exit(&iwp_session->nq_session); + iws_slot_put(iwp_session->slot); +} + +/* + * Legacy and GP TAs close differently: + * - GP TAs always send a notification with payload, whether on close or crash + * - GP TAs may take time to close + */ +int iwp_close_session( + struct iwp_session *iwp_session) +{ + int ret; + + /* state is either IWP_SESSION_RUNNING or IWP_SESSION_CLOSING_GP */ + mutex_lock(&iwp_session->iws_lock); + if (iwp_session->state == IWP_SESSION_RUNNING) + iwp_session->state = IWP_SESSION_CLOSE_REQUESTED; + + /* Send IWP open command */ + ret = iwp_cmd(iwp_session, SID_CLOSE_SESSION); + mutex_unlock(&iwp_session->iws_lock); + iwp_session_release(iwp_session); + mc_dev_devel("close session %x ret %d state %d", iwp_session->sid, + ret, iwp_session->state); + return ret; +} + +int iwp_invoke_command_prepare( + struct iwp_session *iwp_session, + u32 command_id, + struct gp_operation *operation, + struct mc_ioctl_buffer *bufs, + struct gp_shared_memory **parents, + struct gp_return *gp_ret) +{ + struct interworld_session *iws; + int ret; + + if (iwp_session->state != IWP_SESSION_RUNNING) + return iwp_set_ret(-EBADFD, gp_ret); + + mutex_lock(&iwp_session->iws_lock); + iws = slot_to_iws(iwp_session->slot); + memset(iws, 0, sizeof(*iws)); + iws->session_handle = iwp_session->sid; + iws->command_id = command_id; + ret = iwp_operation_to_iws(operation, iws, bufs, parents); + if (ret) + iwp_invoke_command_abort(iwp_session); + + return iwp_set_ret(ret, gp_ret); +} + +void iwp_invoke_command_abort( + struct iwp_session *iwp_session) +{ + mutex_unlock(&iwp_session->iws_lock); +} + +int iwp_invoke_command( + struct iwp_session *iwp_session, + struct gp_operation *operation, + const struct iwp_buffer_map *maps, + struct gp_return *gp_ret) +{ + struct interworld_session *iws = slot_to_iws(iwp_session->slot); + int ret = 0; + + /* Update IWS with operation maps */ + iwp_iws_set_refs(iws, maps); + ret = iwp_cmd(iwp_session, SID_INVOKE_COMMAND); + /* Treat remote errors as errors, just use a specific errno */ + if (!ret && iws->status != TEEC_SUCCESS) + ret = -ECHILD; + + iwp_iws_to_operation(iws, operation); + if (ret && (ret != -ECHILD)) { + ret = iwp_set_ret(ret, gp_ret); + mc_dev_devel("failed with ret [%08x]", ret); + } else { + gp_ret->origin = iws->return_origin; + gp_ret->value = iws->status; + } + mutex_unlock(&iwp_session->iws_lock); + return ret; +} + +int iwp_request_cancellation( + u32 slot) +{ + /* Pseudo IWP session for cancellation */ + struct iwp_session iwp_session; + int ret; + + iwp_session_init(&iwp_session, NULL); + /* sid is local. Set is to SID_CANCEL_OPERATION to make things clear */ + iwp_session.sid = SID_CANCEL_OPERATION; + iwp_session.slot = slot; + mutex_lock(&l_ctx.sessions_lock); + list_add_tail(&iwp_session.list, &l_ctx.sessions); + mutex_unlock(&l_ctx.sessions_lock); + ret = iwp_cmd(&iwp_session, SID_CANCEL_OPERATION); + mutex_lock(&l_ctx.sessions_lock); + list_del(&iwp_session.list); + mutex_unlock(&l_ctx.sessions_lock); + return ret; +} + +int iwp_init(void) +{ + int i; +#ifdef TA2TA_READY + u32 first_iws = INVALID_IWS_SLOT; +#endif /*TA2TA_READY*/ + + l_ctx.iws = nq_get_iwp_buffer(); + INIT_LIST_HEAD(&l_ctx.free_iws); + INIT_LIST_HEAD(&l_ctx.allocd_iws); + l_ctx.iws_list_pool = kcalloc(MAX_IW_SESSION, sizeof(struct iws), + GFP_KERNEL); + if (!l_ctx.iws_list_pool) + return -ENOMEM; + +#ifndef TA2TA_READY + for (i = 0; i < MAX_IW_SESSION; i++) { + l_ctx.iws_list_pool[i].offset = + i * sizeof(struct interworld_session); + list_add(&l_ctx.iws_list_pool[i].list, &l_ctx.free_iws); + } +#else + for (i = MAX_IW_SESSION - 1; i >= 0; i--) { + l_ctx.iws_list_pool[i].offset = + i * sizeof(struct interworld_session); + list_add(&l_ctx.iws_list_pool[i].list, &l_ctx.free_iws); + } +#endif /*TA2TA_READY*/ + + mutex_init(&l_ctx.iws_list_lock); + INIT_LIST_HEAD(&l_ctx.sessions); + mutex_init(&l_ctx.sessions_lock); + +#ifdef TA2TA_READY + /* allocate special session for request from SWd */ + first_iws = iws_slot_get(); + if (first_iws != 0) { + mc_dev_err("first_iws [%08x]", first_iws); + return -ERANGE; + } +#endif /*TA2TA_READY*/ + + nq_register_notif_handler(iwp_notif_handler, true); + return 0; +} + +void iwp_exit(void) +{ +} + +int iwp_start(void) +{ + return 0; +} + +void iwp_stop(void) +{ +} diff --git a/drivers/gud/MobiCoreDriver/iwp.h b/drivers/gud/MobiCoreDriver/iwp.h new file mode 100644 index 000000000000..bb5d92ca76b6 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/iwp.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef _MC_IWP_H_ +#define _MC_IWP_H_ + +#include "mci/mcloadformat.h" /* struct identity */ + +#include "mcp.h" /* mcp_buffer_map FIXME move to nq? */ + +struct iwp_session { + /* Notification queue session (MUST BE FIRST) */ + struct nq_session nq_session; + /* Session ID */ + u32 sid; + /* IWS slot */ + u32 slot; + /* Sessions list (protected by iwp sessions_lock) */ + struct list_head list; + /* Notification waiter lock */ + struct mutex notif_wait_lock; /* Only one at a time */ + /* Notification received */ + struct completion completion; + /* Notification lock */ + struct mutex exit_code_lock; + /* Last notification */ + s32 exit_code; + /* Interworld struct lock */ + struct mutex iws_lock; + /* Session state (protected by iwp sessions_lock) */ + enum iwp_session_state { + IWP_SESSION_RUNNING, + IWP_SESSION_CLOSE_FAILED, + IWP_SESSION_CLOSE_REQUESTED, + IWP_SESSION_CLOSE_NOTIFIED, + IWP_SESSION_CLOSING, + IWP_SESSION_CLOSED, + IWP_SESSION_DYING, + IWP_SESSION_PROXY_CLOSING, + IWP_SESSION_DESTROYED, + } state; + /* GP TAs have login information */ + struct identity client_identity; +}; + +struct iwp_buffer_map { + struct mcp_buffer_map map; + u32 sva; +}; + +/* Private to iwp_session structure */ +void iwp_session_init(struct iwp_session *session, + const struct identity *identity); + +/* Getters */ +static inline u32 iwp_session_id(struct iwp_session *session) +{ + return session->sid; +} + +static inline u32 iwp_session_slot(struct iwp_session *session) +{ + return session->slot; +} + +/* Convert local errno to GP return values */ +int iwp_set_ret(int ret, struct gp_return *gp_ret); + +/* Commands */ +int iwp_register_shared_mem( + struct mcp_buffer_map *map, + struct gp_return *gp_ret); +int iwp_release_shared_mem( + struct mcp_buffer_map *map); +int iwp_open_session_prepare( + struct iwp_session *session, + const struct tee_object *obj, + struct gp_operation *operation, + struct mc_ioctl_buffer *bufs, + struct gp_shared_memory **parents, + struct gp_return *gp_ret); +void iwp_open_session_abort( + struct iwp_session *iwp_session); +int iwp_open_session( + struct iwp_session *iwp_session, + struct gp_operation *operation, + struct mcp_buffer_map *ta_map, + const struct iwp_buffer_map *maps, + struct gp_return *gp_ret); +int iwp_close_session( + struct iwp_session *iwp_session); +int iwp_invoke_command_prepare( + struct iwp_session *iwp_session, + u32 command_id, + struct gp_operation *operation, + struct mc_ioctl_buffer *bufs, + struct gp_shared_memory **parents, + struct gp_return *gp_ret); +void iwp_invoke_command_abort( + struct iwp_session *iwp_session); +int iwp_invoke_command( + struct iwp_session *iwp_session, + struct gp_operation *operation, + const struct iwp_buffer_map *maps, + struct gp_return *gp_ret); +int iwp_request_cancellation( + u32 slot); + +/* Initialisation/cleanup */ +int iwp_init(void); +void iwp_exit(void); +int iwp_start(void); +void iwp_stop(void); + +#endif /* _MC_IWP_H_ */ diff --git a/drivers/gud/MobiCoreDriver/logging.c b/drivers/gud/MobiCoreDriver/logging.c new file mode 100644 index 000000000000..80c35fd3d0f8 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/logging.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include + +#include "main.h" +#include "fastcall.h" +#include "logging.h" +#include "platform.h" + +/* Supported log buffer version */ +#define MC_LOG_VERSION 2 + +/* Default length of the log ring buffer 256KiB */ +#define LOG_BUF_ORDER 6 + +/* Max Len of a log line for printing */ +#define LOG_LINE_SIZE 256 + +/* Definitions for log version 2 */ +#define LOG_TYPE_MASK (0x0007) +#define LOG_TYPE_CHAR 0 +#define LOG_TYPE_INTEGER 1 + +/* Field length */ +#define LOG_LENGTH_MASK (0x00F8) +#define LOG_LENGTH_SHIFT 3 + +/* Extra attributes */ +#define LOG_EOL (0x0100) +#define LOG_INTEGER_DECIMAL (0x0200) +#define LOG_INTEGER_SIGNED (0x0400) + +/* active cpu id */ +#define LOG_CPUID_MASK (0xF000) +#define LOG_CPUID_SHIFT 12 + +struct mc_logmsg { + u16 ctrl; /* Type and format of data */ + u16 source; /* Unique value for each event source */ + u32 log_data; /* Value, if any */ +}; + +/* MobiCore internal trace buffer structure. */ +struct mc_trace_buf { + u32 version; /* version of trace buffer */ + u32 length; /* length of buff */ + u32 head; /* last write position */ + u8 buff[]; /* start of the log buffer */ +}; + +static struct logging_ctx { + struct work_struct work; + union { + struct mc_trace_buf *trace_buf; /* Circular log buffer */ + unsigned long trace_page; + }; + bool buffer_is_shared; /* Log buffer cannot be freed */ + u32 tail; /* MobiCore log read position */ + int thread_err; + u16 prev_source; /* Previous Log source */ + char line[LOG_LINE_SIZE + 1];/* Log Line buffer */ + u32 line_len; /* Log Line buffer current length */ +#if KERNEL_VERSION(4, 4, 0) > LINUX_VERSION_CODE + u32 enabled; /* Log can be disabled via debugfs */ +#else + bool enabled; /* Log can be disabled via debugfs */ +#endif + bool dead; +} log_ctx; + +static inline void log_eol(u16 source, u32 cpuid) +{ + if (!log_ctx.line_len) + return; + + if (log_ctx.prev_source) + /* TEE user-space */ +#ifdef TBASE_CORE_SWITCHER + dev_info(g_ctx.mcd, "%03x(%u)|%s\n", log_ctx.prev_source, + cpuid, log_ctx.line); +#else + dev_info(g_ctx.mcd, "%03x|%s\n", log_ctx.prev_source, + log_ctx.line); +#endif + else + /* TEE kernel */ +#ifdef TBASE_CORE_SWITCHER + dev_info(g_ctx.mcd, "mtk(%u)|%s\n", cpuid, log_ctx.line); +#else + dev_info(g_ctx.mcd, "mtk|%s\n", log_ctx.line); +#endif + log_ctx.line[0] = '\0'; + log_ctx.line_len = 0; +} + +/* + * Collect chars in log_ctx.line buffer and output the buffer when it is full. + * No locking needed because only "mobicore_log" thread updates this buffer. + */ +static inline void log_char(char ch, u16 source, u32 cpuid) +{ + if (ch == '\0') + return; + + if (ch == '\n' || ch == '\r') { + log_eol(source, cpuid); + return; + } + + if (log_ctx.line_len >= LOG_LINE_SIZE || source != log_ctx.prev_source) + log_eol(source, cpuid); + + log_ctx.line[log_ctx.line_len++] = ch; + log_ctx.line[log_ctx.line_len] = 0; + log_ctx.prev_source = source; +} + +static inline void log_string(u32 ch, u16 source, u32 cpuid) +{ + while (ch) { + log_char(ch & 0xFF, source, cpuid); + ch >>= 8; + } +} + +static inline void log_number(u32 format, u32 value, u16 source, u32 cpuid) +{ + int width = (format & LOG_LENGTH_MASK) >> LOG_LENGTH_SHIFT; + char fmt[16]; + char buffer[32]; + const char *reader = buffer; + + if (format & LOG_INTEGER_DECIMAL) + if (format & LOG_INTEGER_SIGNED) + snprintf(fmt, sizeof(fmt), "%%%ud", width); + else + snprintf(fmt, sizeof(fmt), "%%%uu", width); + else + snprintf(fmt, sizeof(fmt), "%%0%ux", width); + + snprintf(buffer, sizeof(buffer), fmt, value); + while (*reader) + log_char(*reader++, source, cpuid); +} + +static inline int log_msg(void *data) +{ + struct mc_logmsg *msg = (struct mc_logmsg *)data; + int log_type = msg->ctrl & LOG_TYPE_MASK; + int cpuid = ((msg->ctrl & LOG_CPUID_MASK) >> LOG_CPUID_SHIFT); + + switch (log_type) { + case LOG_TYPE_CHAR: + log_string(msg->log_data, msg->source, cpuid); + break; + case LOG_TYPE_INTEGER: + log_number(msg->ctrl, msg->log_data, msg->source, cpuid); + break; + } + if (msg->ctrl & LOG_EOL) + log_eol(msg->source, cpuid); + + return sizeof(*msg); +} + +static void log_worker(struct work_struct *work) +{ + static DEFINE_MUTEX(local_mutex); + + mutex_lock(&local_mutex); + while (log_ctx.trace_buf->head != log_ctx.tail) { + if (log_ctx.trace_buf->version != MC_LOG_VERSION) { + mc_dev_err("Bad log data v%d (exp. v%d), stop", + log_ctx.trace_buf->version, MC_LOG_VERSION); + log_ctx.dead = true; + break; + } + + log_ctx.tail += log_msg(&log_ctx.trace_buf->buff[log_ctx.tail]); + /* Wrap over if no space left for a complete message */ + if ((log_ctx.tail + sizeof(struct mc_logmsg)) > + log_ctx.trace_buf->length) + log_ctx.tail = 0; + } + mutex_unlock(&local_mutex); +} + +/* + * Wake up the log reader thread + * This should be called from the places where calls into MobiCore have + * generated some logs(eg, yield, SIQ...) + */ +void mc_logging_run(void) +{ + if (log_ctx.enabled && !log_ctx.dead && + log_ctx.trace_buf->head != log_ctx.tail) + schedule_work(&log_ctx.work); +} + +int mc_logging_start(void) +{ + int ret = mc_fc_mem_trace(virt_to_phys((void *)(log_ctx.trace_page)), + BIT(LOG_BUF_ORDER) * PAGE_SIZE); + + if (ret) { + mc_dev_err("shared traces setup failed"); + return ret; + } + + log_ctx.buffer_is_shared = true; + mc_dev_devel("fc_log version %u", log_ctx.trace_buf->version); + mc_logging_run(); + return 0; +} + +void mc_logging_stop(void) +{ + if (!mc_fc_mem_trace(0, 0)) + log_ctx.buffer_is_shared = false; + + mc_logging_run(); + flush_work(&log_ctx.work); +} + +/* + * Setup MobiCore kernel log. It assumes it's running on CORE 0! + * The fastcall will complain is that is not the case! + */ +int mc_logging_init(void) +{ + /* + * We are going to map this buffer into virtual address space in SWd. + * To reduce complexity there, we use a contiguous buffer. + */ + log_ctx.trace_page = __get_free_pages(GFP_KERNEL | __GFP_ZERO, + LOG_BUF_ORDER); + if (!log_ctx.trace_page) + return -ENOMEM; + + INIT_WORK(&log_ctx.work, log_worker); + log_ctx.enabled = true; + debugfs_create_bool("swd_debug", 0600, g_ctx.debug_dir, + &log_ctx.enabled); + return 0; +} + +void mc_logging_exit(void) +{ + /* + * This is not racey as the only caller for mc_logging_run is the + * scheduler which gets stopped before us, and long before we exit. + */ + if (!log_ctx.buffer_is_shared) + free_pages(log_ctx.trace_page, LOG_BUF_ORDER); + else + mc_dev_err("log buffer unregister not supported"); +} diff --git a/drivers/gud/MobiCoreDriver/logging.h b/drivers/gud/MobiCoreDriver/logging.h new file mode 100644 index 000000000000..5fdc03826992 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/logging.h @@ -0,0 +1,23 @@ +/* + * 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. + */ +#ifndef _MC_LOGGING_H_ +#define _MC_LOGGING_H_ + +void mc_logging_run(void); +int mc_logging_init(void); +void mc_logging_exit(void); +int mc_logging_start(void); +void mc_logging_stop(void); + +#endif /* _MC_LOGGING_H_ */ diff --git a/drivers/gud/MobiCoreDriver/main.c b/drivers/gud/MobiCoreDriver/main.c new file mode 100644 index 000000000000..72239d5ac926 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/main.c @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2013-2016 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 +#include +#include +#include +#include +#include +#include + +#include "public/mc_user.h" +#include "public/mc_admin.h" /* MC_ADMIN_DEVNODE */ + +#include "platform.h" /* MC_PM_RUNTIME */ +#include "main.h" +#include "fastcall.h" +#include "arm.h" +#include "mmu.h" +#include "scheduler.h" +#include "pm.h" +#include "logging.h" +#include "admin.h" +#include "user.h" +#include "mcp.h" +#include "iwp.h" +#include "nq.h" +#include "client.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 +}; + +static struct { + /* TEE start return code */ + struct mutex start_mutex; + /* TEE start return code */ + int start_ret; +#ifdef MC_PM_RUNTIME + /* Whether hibernation succeeded */ + bool did_hibernate; + /* Reboot notifications */ + struct notifier_block reboot_notifier; + /* PM notifications */ + struct notifier_block pm_notifier; +#endif + /* Devices */ + dev_t device; + struct class *class; + /* Admin device */ + struct cdev admin_cdev; + /* User device */ + dev_t user_dev; + struct cdev user_cdev; + /* Debug counters */ + struct mutex struct_counters_buf_mutex; + char struct_counters_buf[256]; + int struct_counters_buf_len; +} main_ctx; + +static int mobicore_start(void); +static void mobicore_stop(void); + +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 inline void kasnprintf_buf_reset(struct kasnprintf_buf *buf) +{ + kfree(buf->buf); + buf->buf = NULL; + buf->size = 0; + buf->off = 0; +} + +ssize_t debug_generic_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos, + int (*function)(struct kasnprintf_buf *buf)) +{ + struct kasnprintf_buf *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + /* Add/update buffer */ + if (!*ppos) { + kasnprintf_buf_reset(buf); + ret = function(buf); + if (ret < 0) { + kasnprintf_buf_reset(buf); + goto end; + } + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf->buf, + buf->off); + +end: + mutex_unlock(&buf->mutex); + return ret; +} + +int debug_generic_open(struct inode *inode, struct file *file) +{ + struct kasnprintf_buf *buf; + + file->private_data = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!file->private_data) + return -ENOMEM; + + buf = file->private_data; + mutex_init(&buf->mutex); + buf->gfp = GFP_KERNEL; + return 0; +} + +int debug_generic_release(struct inode *inode, struct file *file) +{ + struct kasnprintf_buf *buf = file->private_data; + + if (!buf) + return 0; + + kasnprintf_buf_reset(buf); + kfree(buf); + return 0; +} + +static ssize_t debug_structs_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + return debug_generic_read(file, user_buf, count, ppos, + clients_debug_structs); +} + +static const struct file_operations mc_debug_structs_ops = { + .read = debug_structs_read, + .llseek = default_llseek, + .open = debug_generic_open, + .release = debug_generic_release, +}; + +static ssize_t debug_struct_counters_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + if (!*ppos) { + int ret; + + mutex_lock(&main_ctx.struct_counters_buf_mutex); + ret = snprintf(main_ctx.struct_counters_buf, + sizeof(main_ctx.struct_counters_buf), + "clients: %d\n" + "cbufs: %d\n" + "cwsms: %d\n" + "sessions: %d\n" + "swsms: %d\n" + "mmus: %d\n" + "maps: %d\n" + "slots: %d\n", + atomic_read(&g_ctx.c_clients), + atomic_read(&g_ctx.c_cbufs), + atomic_read(&g_ctx.c_cwsms), + atomic_read(&g_ctx.c_sessions), + atomic_read(&g_ctx.c_wsms), + atomic_read(&g_ctx.c_mmus), + atomic_read(&g_ctx.c_maps), + atomic_read(&g_ctx.c_slots)); + mutex_unlock(&main_ctx.struct_counters_buf_mutex); + if (ret > 0) + main_ctx.struct_counters_buf_len = ret; + } + + return simple_read_from_buffer(user_buf, count, ppos, + main_ctx.struct_counters_buf, + main_ctx.struct_counters_buf_len); +} + +static const struct file_operations mc_debug_struct_counters_ops = { + .read = debug_struct_counters_read, + .llseek = default_llseek, +}; + +static inline int device_user_init(void) +{ + struct device *dev; + int ret = 0; + + main_ctx.user_dev = MKDEV(MAJOR(main_ctx.device), 1); + /* Create the user node */ + mc_user_init(&main_ctx.user_cdev); + ret = cdev_add(&main_ctx.user_cdev, main_ctx.user_dev, 1); + if (ret) { + mc_dev_err("user cdev_add failed"); + return ret; + } + + main_ctx.user_cdev.owner = THIS_MODULE; + dev = device_create(main_ctx.class, NULL, main_ctx.user_dev, NULL, + MC_USER_DEVNODE); + if (IS_ERR(dev)) { + cdev_del(&main_ctx.user_cdev); + mc_dev_err("user device_create failed"); + return PTR_ERR(dev); + } + + /* Create debugfs structs entry */ + debugfs_create_file("structs", 0400, g_ctx.debug_dir, NULL, + &mc_debug_structs_ops); + + return 0; +} + +static inline void device_user_exit(void) +{ + device_destroy(main_ctx.class, main_ctx.user_dev); + cdev_del(&main_ctx.user_cdev); +} + +#ifdef MC_PM_RUNTIME +static int reboot_notifier(struct notifier_block *nb, unsigned long event, + void *dummy) +{ + switch (event) { + case SYS_HALT: + case SYS_POWER_OFF: + main_ctx.did_hibernate = true; + break; + } + + return 0; +} + +static int suspend_notifier(struct notifier_block *nb, unsigned long event, + void *dummy) +{ + int ret = 0; + + main_ctx.did_hibernate = false; + switch (event) { + case PM_SUSPEND_PREPARE: + return mc_scheduler_suspend(); + case PM_POST_SUSPEND: + return mc_scheduler_resume(); + case PM_HIBERNATION_PREPARE: + /* Try to stop the TEE nicely (ignore failure) */ + mc_scheduler_suspend(); + /* Make sure the TEE cannot run anymore */ + mc_scheduler_stop(); + /* Flush log buffer */ + mc_logging_run(); + break; + case PM_POST_HIBERNATION: + if (main_ctx.did_hibernate) { + /* Really did hibernate */ + clients_kill_sessions(); + main_ctx.start_ret = TEE_START_NOT_TRIGGERED; + return mobicore_start(); + } + + /* Did not hibernate, just restart the TEE */ + ret = mc_scheduler_start(); + if (!ret) + ret = mc_scheduler_resume(); + } + + return ret; +} +#endif /* MC_PM_RUNTIME */ + +static int mobicore_start(void) +{ + struct mc_version_info version_info; +#ifdef CONFIG_TRUSTONIC_TEE_LPAE + bool dynamic_lpae = false; +#endif + int ret; + + mutex_lock(&main_ctx.start_mutex); + if (main_ctx.start_ret != TEE_START_NOT_TRIGGERED) + goto got_ret; + + ret = mc_logging_start(); + if (ret) { + mc_dev_err("Log start failed"); + goto err_log; + } + + ret = nq_start(); + if (ret) { + mc_dev_err("NQ start failed"); + goto err_nq; + } + + ret = mcp_start(); + if (ret) { + mc_dev_err("MCP start failed"); + goto err_mcp; + } + + ret = iwp_start(); + if (ret) { + mc_dev_err("IWP start failed"); + goto err_iwp; + } + + ret = mc_scheduler_start(); + if (ret) { + mc_dev_err("Scheduler start failed"); + goto err_sched; + } + + ret = mc_pm_start(); + if (ret) { + mc_dev_err("Power Management start failed"); + goto err_pm; + } + + /* Must be called before creating the user device node to avoid race */ + ret = mcp_get_version(&version_info); + if (ret) + goto err_mcp_cmd; + + /* CMP version is meaningless in this case and is thus not printed */ + mc_dev_info("\n" + " product_id = %s\n" + " version_mci = 0x%08x\n" + " version_so = 0x%08x\n" + " version_mclf = 0x%08x\n" + " version_container = 0x%08x\n" + " version_mc_config = 0x%08x\n" + " version_tl_api = 0x%08x\n" + " version_dr_api = 0x%08x\n" + " version_nwd = 0x%08x\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_nwd); + + if (MC_VERSION_MAJOR(version_info.version_mci) > 1) { + mc_dev_err("MCI too recent for this driver"); + goto err_version; + } + + if ((MC_VERSION_MAJOR(version_info.version_mci) == 0) && + (MC_VERSION_MINOR(version_info.version_mci) < 6)) { + mc_dev_err("MCI too old for this driver"); + goto err_version; + } + + /* Determine which features are supported */ + switch (version_info.version_mci) { + case MC_VERSION(1, 6): /* 400 */ + g_ctx.f_monotonic_time = true; + /* Fall through */ + case MC_VERSION(1, 5): /* 400 */ + g_ctx.f_iwp = true; + /* Fall through */ + case MC_VERSION(1, 4): /* 310 */ +#ifdef CONFIG_TRUSTONIC_TEE_LPAE + dynamic_lpae = true; +#endif + /* Fall through */ + case MC_VERSION(1, 3): + g_ctx.f_time = true; + /* Fall through */ + case MC_VERSION(1, 2): + 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; + } + +#ifdef CONFIG_TRUSTONIC_TEE_LPAE + if (!dynamic_lpae) + g_ctx.f_lpae = true; +#endif + mc_dev_info("SWd uses %sLPAE MMU table format", + g_ctx.f_lpae ? "" : "non-"); + +#ifdef MC_PM_RUNTIME + main_ctx.reboot_notifier.notifier_call = reboot_notifier; + ret = register_reboot_notifier(&main_ctx.reboot_notifier); + if (ret) { + mc_dev_err("reboot notifier register failed"); + goto err_pm_notif; + } + + main_ctx.pm_notifier.notifier_call = suspend_notifier; + ret = register_pm_notifier(&main_ctx.pm_notifier); + if (ret) { + unregister_reboot_notifier(&main_ctx.reboot_notifier); + mc_dev_err("PM notifier register failed"); + goto err_pm_notif; + } +#endif + + ret = device_user_init(); + if (ret) + goto err_create_dev_user; + + main_ctx.start_ret = 0; + goto got_ret; + +err_create_dev_user: +#ifdef MC_PM_RUNTIME + unregister_reboot_notifier(&main_ctx.reboot_notifier); + unregister_pm_notifier(&main_ctx.pm_notifier); +err_pm_notif: +#endif +err_version: +err_mcp_cmd: + mc_pm_stop(); +err_pm: + mc_scheduler_stop(); +err_sched: + iwp_stop(); +err_iwp: + mcp_stop(); +err_mcp: + nq_stop(); +err_nq: + mc_logging_stop(); +err_log: + main_ctx.start_ret = ret; +got_ret: + mutex_unlock(&main_ctx.start_mutex); + return main_ctx.start_ret; +} + +static void mobicore_stop(void) +{ + device_user_exit(); +#ifdef MC_PM_RUNTIME + unregister_reboot_notifier(&main_ctx.reboot_notifier); + unregister_pm_notifier(&main_ctx.pm_notifier); +#endif + mc_pm_stop(); + mc_scheduler_stop(); + mc_logging_stop(); + iwp_stop(); + mcp_stop(); + nq_stop(); +} + +int mc_wait_tee_start(void) +{ + int ret; + + mutex_lock(&main_ctx.start_mutex); + while (main_ctx.start_ret == TEE_START_NOT_TRIGGERED) { + mutex_unlock(&main_ctx.start_mutex); + ssleep(1); + mutex_lock(&main_ctx.start_mutex); + } + + ret = main_ctx.start_ret; + mutex_unlock(&main_ctx.start_mutex); + return ret; +} + +static ssize_t debug_sessions_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + return debug_generic_read(file, user_buf, count, ppos, + mcp_debug_sessions); +} + +static const struct file_operations mc_debug_sessions_ops = { + .read = debug_sessions_read, + .llseek = default_llseek, + .open = debug_generic_open, + .release = debug_generic_release, +}; + +static ssize_t debug_mcpcmds_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + return debug_generic_read(file, user_buf, count, ppos, + mcp_debug_mcpcmds); +} + +static const struct file_operations mc_debug_mcpcmds_ops = { + .read = debug_mcpcmds_read, + .llseek = default_llseek, + .open = debug_generic_open, + .release = debug_generic_release, +}; + +static inline int device_admin_init(void) +{ + struct device *dev; + int ret = 0; + + ret = alloc_chrdev_region(&main_ctx.device, 0, 2, "trustonic_tee"); + if (ret) { + mc_dev_err("alloc_chrdev_region failed"); + return ret; + } + + main_ctx.class = class_create(THIS_MODULE, "trustonic_tee"); + if (IS_ERR(main_ctx.class)) { + mc_dev_err("class_create failed"); + ret = PTR_ERR(main_ctx.class); + goto err_class; + } + + /* Create the ADMIN node */ + ret = mc_admin_init(&main_ctx.admin_cdev, mobicore_start, + mobicore_stop); + if (ret) + goto err_init; + + ret = cdev_add(&main_ctx.admin_cdev, main_ctx.device, 1); + if (ret) { + mc_dev_err("admin cdev_add failed"); + goto err_cdev; + } + + main_ctx.admin_cdev.owner = THIS_MODULE; + dev = device_create(main_ctx.class, NULL, main_ctx.device, NULL, + MC_ADMIN_DEVNODE); + if (IS_ERR(dev)) { + mc_dev_err("admin device_create failed"); + ret = PTR_ERR(dev); + goto err_device; + } + + /* Create debugfs sessions and MCP commands entries */ + debugfs_create_file("sessions", 0400, g_ctx.debug_dir, NULL, + &mc_debug_sessions_ops); + debugfs_create_file("last_mcp_commands", 0400, g_ctx.debug_dir, NULL, + &mc_debug_mcpcmds_ops); + return 0; + +err_device: + cdev_del(&main_ctx.admin_cdev); +err_cdev: + mc_admin_exit(); +err_init: + class_destroy(main_ctx.class); +err_class: + unregister_chrdev_region(main_ctx.device, 2); + return ret; +} + +static inline void device_admin_exit(void) +{ + device_destroy(main_ctx.class, main_ctx.device); + cdev_del(&main_ctx.admin_cdev); + mc_admin_exit(); + class_destroy(main_ctx.class); + unregister_chrdev_region(main_ctx.device, 2); +} + +/* + * 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_probe(struct platform_device *pdev) +{ + int err = 0; + + if (pdev) + g_ctx.mcd->of_node = pdev->dev.of_node; + +#ifdef MOBICORE_COMPONENT_BUILD_TAG + mc_dev_info("MobiCore %s", MOBICORE_COMPONENT_BUILD_TAG); +#endif + /* Hardware does not support ARM TrustZone -> Cannot continue! */ + if (!has_security_extensions()) { + mc_dev_err("Hardware doesn't support ARM TrustZone!"); + return -ENODEV; + } + + /* Running in secure mode -> Cannot load the driver! */ + if (is_secure_mode()) { + mc_dev_err("Running in secure MODE!"); + return -ENODEV; + } + + /* Make sure we can create debugfs entries */ + g_ctx.debug_dir = debugfs_create_dir("trustonic_tee", NULL); + + /* Initialize debug counters */ + atomic_set(&g_ctx.c_clients, 0); + atomic_set(&g_ctx.c_cbufs, 0); + atomic_set(&g_ctx.c_cwsms, 0); + atomic_set(&g_ctx.c_sessions, 0); + atomic_set(&g_ctx.c_wsms, 0); + atomic_set(&g_ctx.c_mmus, 0); + atomic_set(&g_ctx.c_maps, 0); + atomic_set(&g_ctx.c_slots, 0); + main_ctx.start_ret = TEE_START_NOT_TRIGGERED; + mutex_init(&main_ctx.start_mutex); + mutex_init(&main_ctx.struct_counters_buf_mutex); + /* Create debugfs info entry */ + debugfs_create_file("structs_counters", 0400, g_ctx.debug_dir, NULL, + &mc_debug_struct_counters_ops); + + /* Initialize common API layer */ + client_init(); + + /* Initialize plenty of nice features */ + err = mc_fastcall_init(); + if (err) { + mc_dev_err("Fastcall support init failed!"); + goto err_fastcall; + } + + err = nq_init(); + if (err) { + mc_dev_err("NQ init failed!"); + goto fail_nq_init; + } + + err = mcp_init(); + if (err) { + mc_dev_err("MCP init failed!"); + goto err_mcp; + } + + err = iwp_init(); + if (err) { + mc_dev_err("IWP init failed!"); + goto err_iwp; + } + + err = mc_logging_init(); + if (err) { + mc_dev_err("Log init failed!"); + goto err_log; + } + + err = mc_scheduler_init(); + if (err) { + mc_dev_err("Scheduler init failed!"); + goto err_sched; + } + + /* + * Create admin dev so that daemon can already communicate with + * the driver + */ + err = device_admin_init(); + if (err) + goto err_admin; + +#ifndef MC_DELAYED_TEE_START + err = mobicore_start(); +#endif + if (err) + goto err_start; + + return 0; + +err_start: + device_admin_exit(); +err_admin: + mc_scheduler_exit(); +err_sched: + mc_logging_exit(); +err_log: + iwp_exit(); +err_iwp: + mcp_exit(); +err_mcp: + nq_exit(); +fail_nq_init: + mc_fastcall_exit(); +err_fastcall: + debugfs_remove_recursive(g_ctx.debug_dir); + return err; +} + +#ifdef MC_DEVICE_PROPNAME + +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, + } +}; + +#endif /* MC_DEVICE_PROPNAME */ + +static int __init mobicore_init(void) +{ + dev_set_name(g_ctx.mcd, "TEE"); + /* + * Do not remove or change the following trace. + * The string "MobiCore" is used to detect if the TEE is in of the image + */ + mc_dev_info("MobiCore mcDrvModuleApi version is %d.%d", + MCDRVMODULEAPI_VERSION_MAJOR, + MCDRVMODULEAPI_VERSION_MINOR); +#ifdef MC_DEVICE_PROPNAME + return platform_driver_register(&mc_plat_driver); +#else + return mobicore_probe(NULL); +#endif +} + +static void __exit mobicore_exit(void) +{ +#ifdef MC_DEVICE_PROPNAME + platform_driver_unregister(&mc_plat_driver); +#endif + device_admin_exit(); + mc_scheduler_exit(); + mc_logging_exit(); + iwp_exit(); + mcp_exit(); + nq_exit(); + mc_fastcall_exit(); + debugfs_remove_recursive(g_ctx.debug_dir); +} + +module_init(mobicore_init); +module_exit(mobicore_exit); + +MODULE_AUTHOR("Trustonic Limited"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MobiCore driver"); diff --git a/drivers/gud/MobiCoreDriver/main.h b/drivers/gud/MobiCoreDriver/main.h new file mode 100644 index 000000000000..6717fc5743a9 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/main.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013-2018 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. + */ + +#ifndef _MC_MAIN_H_ +#define _MC_MAIN_H_ + +#include /* dev_* macros */ +#include /* gfp_t */ +#include /* struct inode and struct file */ +#include +#include + +#define MC_VERSION(major, minor) \ + ((((major) & 0x0000ffff) << 16) | ((minor) & 0x0000ffff)) +#define MC_VERSION_MAJOR(x) ((x) >> 16) +#define MC_VERSION_MINOR(x) ((x) & 0xffff) + +#define mc_dev_err(fmt, ...) \ + dev_err(g_ctx.mcd, "%s: " fmt "\n", __func__, ##__VA_ARGS__) + +#define mc_dev_info(fmt, ...) \ + dev_info(g_ctx.mcd, "%s: " fmt "\n", __func__, ##__VA_ARGS__) + +#ifdef DEBUG +#define mc_dev_devel(fmt, ...) \ + dev_info(g_ctx.mcd, "%s: " fmt "\n", __func__, ##__VA_ARGS__) +#else /* DEBUG */ +#define mc_dev_devel(...) do {} while (0) +#endif /* !DEBUG */ + +#define TEEC_TT_LOGIN_KERNEL 0x80000000 + +#define TEE_START_NOT_TRIGGERED 1 + +/* MobiCore Driver Kernel Module context data. */ +struct mc_device_ctx { + struct device *mcd; + /* debugfs root */ + struct dentry *debug_dir; + + /* Features */ + /* - SWd uses LPAE MMU table format */ + bool f_lpae; + /* - SWd can set a time out to get scheduled at a future time */ + bool f_timeout; + /* - SWd supports memory extension which allows for bigger TAs */ + bool f_mem_ext; + /* - SWd supports TA authorisation */ + bool f_ta_auth; + /* - SWd can map several buffers at once */ + bool f_multimap; + /* - SWd supports GP client authentication */ + bool f_client_login; + /* - SWd needs time updates */ + bool f_time; + /* - SWd supports inter-world protocol */ + bool f_iwp; + /* - SWd needs both wall and monotonic times */ + bool f_monotonic_time; + + /* Debug counters */ + atomic_t c_clients; + atomic_t c_cbufs; + atomic_t c_cwsms; + atomic_t c_sessions; + atomic_t c_wsms; + atomic_t c_mmus; + atomic_t c_maps; + atomic_t c_slots; +}; + +extern struct mc_device_ctx g_ctx; + +/* Debug stuff */ +struct kasnprintf_buf { + struct mutex mutex; /* Protect buf/size/off access */ + gfp_t gfp; + void *buf; + int size; + int off; +}; + +/* Wait for TEE to start and get status */ +int mc_wait_tee_start(void); + +extern __printf(2, 3) +int kasnprintf(struct kasnprintf_buf *buf, const char *fmt, ...); +ssize_t debug_generic_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos, + int (*function)(struct kasnprintf_buf *buf)); +int debug_generic_open(struct inode *inode, struct file *file); +int debug_generic_release(struct inode *inode, struct file *file); + +#if KERNEL_VERSION(4, 11, 0) > LINUX_VERSION_CODE +static inline unsigned int kref_read(struct kref *kref) +{ + return atomic_read(&kref->refcount); +} +#endif + +#endif /* _MC_MAIN_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mci/gptci.h b/drivers/gud/MobiCoreDriver/mci/gptci.h new file mode 100644 index 000000000000..5374d5f9ddea --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mci/gptci.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef _GP_TCI_H_ +#define _GP_TCI_H_ + +struct tee_value { + u32 a; + u32 b; +}; + +struct _teec_memory_reference_internal { + u32 sva; + u32 len; + u32 output_size; +}; + +union _teec_parameter_internal { + struct tee_value value; + struct _teec_memory_reference_internal memref; +}; + +enum _teec_tci_type { + _TA_OPERATION_OPEN_SESSION = 1, + _TA_OPERATION_INVOKE_COMMAND = 2, + _TA_OPERATION_CLOSE_SESSION = 3, +}; + +struct _teec_operation_internal { + enum _teec_tci_type type; + u32 command_id; + u32 param_types; + union _teec_parameter_internal params[4]; + bool is_cancelled; + u8 rfu_padding[3]; +}; + +struct _teec_tci { + char header[8]; + struct teec_uuid destination; + struct _teec_operation_internal operation; + u32 ready; + u32 return_origin; + u32 return_status; +}; + +/** + * Termination codes + */ +#define TA_EXIT_CODE_PANIC 300 +#define TA_EXIT_CODE_TCI 301 +#define TA_EXIT_CODE_PARAMS 302 +#define TA_EXIT_CODE_FINISHED 303 +#define TA_EXIT_CODE_SESSIONSTATE 304 +#define TA_EXIT_CODE_CREATEFAILED 305 + +#endif /* _GP_TCI_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mci/mcifc.h b/drivers/gud/MobiCoreDriver/mci/mcifc.h new file mode 100644 index 000000000000..d3674bcde788 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mci/mcifc.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2013-2014 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. + */ +#ifndef MCIFC_H_ +#define MCIFC_H_ + +#include "platform.h" + +/** @name MobiCore FastCall Defines + * Defines for the two different FastCall's. + */ +/**/ + +/* --- global ---- */ +#define MC_FC_INVALID ((u32)0) /**< Invalid FastCall ID */ + +#if (defined(CONFIG_ARM64) && !defined(MC_ARMV7_FC)) || (defined(MC_AARCH32_FC)) + +#define FASTCALL_OWNER_TZOS_32 (0xBF000000) /**Trusted OS Fastcalls SMC32 */ + +/* SMC32 Trusted OS owned Fastcalls */ +#define MC_FC_STD32_BASE ((u32)FASTCALL_OWNER_TZOS_32) +#define MC_FC_STD32(x) ((u32)(MC_FC_STD32_BASE + (x))) + +#define MC_FC_INIT MC_FC_STD32(1) /**< Initializing FastCall. */ +#define MC_FC_INFO MC_FC_STD32(2) /**< Info FastCall. */ +#define MC_FC_MEM_TRACE MC_FC_STD32(10) /**< Enable SWd tracing via memory */ +#define MC_FC_SWAP_CPU MC_FC_STD32(54) /**< Change new active Core */ + +#else + +#define MC_FC_INIT ((u32)(-1)) /**< Initializing FastCall. */ +#define MC_FC_INFO ((u32)(-2)) /**< Info FastCall. */ +#define MC_FC_MEM_TRACE ((u32)(-31)) /**< Enable SWd tracing via memory */ +#define MC_FC_SWAP_CPU ((u32)(0x84000005)) /**< Change new active Core */ + +#endif + +/** @} */ + +/** @name MobiCore SMC Defines + * Defines the different secure monitor calls (SMC) for world switching. + */ +/**< Yield to switch from NWd to SWd. */ +#define MC_SMC_N_YIELD 3 +/**< SIQ to switch from NWd to SWd. */ +#define MC_SMC_N_SIQ 4 +/** @} */ + +/** @name MobiCore status + * MobiCore status information. + */ +/**< MobiCore is not yet initialized. FastCall FcInit() to set up MobiCore.*/ +#define MC_STATUS_NOT_INITIALIZED 0 +/**< Bad parameters have been passed in FcInit(). */ +#define MC_STATUS_BAD_INIT 1 +/**< MobiCore did initialize properly. */ +#define MC_STATUS_INITIALIZED 2 +/**< MobiCore kernel halted due to an unrecoverable exception. Further + * information is available extended info + */ +#define MC_STATUS_HALT 3 +/** @} */ + +/** @name Extended Info Identifiers + * Extended info parameters for MC_FC_INFO to obtain further information + * depending on MobiCore state. + */ +/**< Version of the MobiCore Control Interface (MCI) */ +#define MC_EXT_INFO_ID_MCI_VERSION 0 +/**< MobiCore control flags */ +#define MC_EXT_INFO_ID_FLAGS 1 +/**< MobiCore halt condition code */ +#define MC_EXT_INFO_ID_HALT_CODE 2 +/**< MobiCore halt condition instruction pointer */ +#define MC_EXT_INFO_ID_HALT_IP 3 +/**< MobiCore fault counter */ +#define MC_EXT_INFO_ID_FAULT_CNT 4 +/**< MobiCore last fault cause */ +#define MC_EXT_INFO_ID_FAULT_CAUSE 5 +/**< MobiCore last fault meta */ +#define MC_EXT_INFO_ID_FAULT_META 6 +/**< MobiCore last fault threadid */ +#define MC_EXT_INFO_ID_FAULT_THREAD 7 +/**< MobiCore last fault instruction pointer */ +#define MC_EXT_INFO_ID_FAULT_IP 8 +/**< MobiCore last fault stack pointer */ +#define MC_EXT_INFO_ID_FAULT_SP 9 +/**< MobiCore last fault ARM arch information */ +#define MC_EXT_INFO_ID_FAULT_ARCH_DFSR 10 +/**< MobiCore last fault ARM arch information */ +#define MC_EXT_INFO_ID_FAULT_ARCH_ADFSR 11 +/**< MobiCore last fault ARM arch information */ +#define MC_EXT_INFO_ID_FAULT_ARCH_DFAR 12 +/**< MobiCore last fault ARM arch information */ +#define MC_EXT_INFO_ID_FAULT_ARCH_IFSR 13 +/**< MobiCore last fault ARM arch information */ +#define MC_EXT_INFO_ID_FAULT_ARCH_AIFSR 14 +/**< MobiCore last fault ARM arch information */ +#define MC_EXT_INFO_ID_FAULT_ARCH_IFAR 15 +/**< MobiCore configured by Daemon via fc_init flag */ +#define MC_EXT_INFO_ID_MC_CONFIGURED 16 +/**< MobiCore scheduling status: idle/non-idle */ +#define MC_EXT_INFO_ID_MC_SCHED_STATUS 17 +/**< MobiCore runtime status: initialized, halted */ +#define MC_EXT_INFO_ID_MC_STATUS 18 +/**< MobiCore exception handler last partner */ +#define MC_EXT_INFO_ID_MC_EXC_PARTNER 19 +/**< MobiCore exception handler last peer */ +#define MC_EXT_INFO_ID_MC_EXC_IPCPEER 20 +/**< MobiCore exception handler last IPC message */ +#define MC_EXT_INFO_ID_MC_EXC_IPCMSG 21 +/**< MobiCore exception handler last IPC data */ +#define MC_EXT_INFO_ID_MC_EXC_IPCDATA 22 +/**< MobiCore exception handler last UUID (uses 4 slots: 23 to 26) */ +#define MC_EXT_INFO_ID_MC_EXC_UUID 23 +#define MC_EXT_INFO_ID_MC_EXC_UUID1 24 +#define MC_EXT_INFO_ID_MC_EXC_UUID2 25 +#define MC_EXT_INFO_ID_MC_EXC_UUID3 26 + +/** @} */ + +/** @name FastCall return values + * Return values of the MobiCore FastCalls. + */ +/**< No error. Everything worked fine. */ +#define MC_FC_RET_OK 0 +/**< FastCall was not successful. */ +#define MC_FC_RET_ERR_INVALID 1 +/**< MobiCore has already been initialized. */ +#define MC_FC_RET_ERR_ALREADY_INITIALIZED 5 +/** @} */ + +/** @name Init FastCall flags + * Return flags of the Init FastCall. + */ +/**< SWd uses LPAE MMU table format. */ +#define MC_FC_INIT_FLAG_LPAE BIT(0) +/** @} */ + +#endif /** MCIFC_H_ */ + +/** @} */ diff --git a/drivers/gud/MobiCoreDriver/mci/mciiwp.h b/drivers/gud/MobiCoreDriver/mci/mciiwp.h new file mode 100644 index 000000000000..2f7bd2a4d943 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mci/mciiwp.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016 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. + */ + +#ifndef MCIIWP_H_ +#define MCIIWP_H_ + +#include "public/GP/tee_client_types.h" /* teec_uuid FIXME it's all mixed up! */ + +/** Max number of interworld session allocated in MCI buffer */ +#define MAX_IW_SESSION 256 + +/** Session ID for notifications for the Dragon CA-to-TA communication protocol + * + * Session ID are distinct from any valid MCP session identifier + * and from the existing pseudo-session identifiers : + * - SID_MCP = 0 + * - SID_INVALID = 0xffffffff + * + * A session ID is a thread ID, and since thread IDs have a nonzero task ID as + * their lowest 16 bits, we can use values of the form 0x????0000 + */ +#define SID_OPEN_SESSION (0x00010000) +#define SID_INVOKE_COMMAND (0x00020000) +#define SID_CLOSE_SESSION (0x00030000) +#define SID_CANCEL_OPERATION (0x00040000) +#define SID_MEMORY_REFERENCE (0x00050000) +#define SID_OPEN_TA (0x00060000) +#define SID_REQ_TA (0x00070000) + +/* To quickly detect IWP notifications */ +#define SID_IWP_NOTIFICATION \ + (SID_OPEN_SESSION | SID_INVOKE_COMMAND | SID_CLOSE_SESSION | \ + SID_CANCEL_OPERATION | SID_MEMORY_REFERENCE | SID_OPEN_TA | SID_REQ_TA) + +struct interworld_parameter_value { + u32 a; + u32 b; + u8 unused[8]; +}; + +/** The API parameter type TEEC_MEMREF_WHOLE is translated into these types + * and does not appear in the inter-world protocol. + * + * - memref_handle references a previously registered memory reference + * 'offset' bytes <= memref_handle < 'offset + size' bytes + * + * These sizes must be contained within the memory reference. + */ +struct interworld_parameter_memref { + u32 offset; + u32 size; + u32 memref_handle; + u32 unused; +}; + +/** This structure is used for the parameter types TEEC_MEMREF_TEMP_xxx. + * + * The parameter is located in World Shared Memory which is established + * for the command and torn down afterwards. + * + * The number of pages to share is 'size + offset' divided by the page + * size, rounded up. + * Inside the shared pages, the buffer starts at address 'offset' + * and ends after 'size' bytes. + * + * - wsm_type parameter may be WSM_CONTIGUOUS, WSM_L1 or WSM_L2. + * - offset must be less than the page size (4096). + * - size must be less than 0xfffff000. + */ +struct interworld_parameter_tmpref { + u16 wsm_type; + u16 offset; + u32 size; + u64 physical_address; +}; + +/** + * + */ +union interworld_parameter { + struct interworld_parameter_value value; + struct interworld_parameter_memref memref; + struct interworld_parameter_tmpref tmpref; +}; + +/** + * An inter-world session structure represents an active session between + * a normal world client and RTM. + * It is located in the MCI buffer, must be 8-byte aligned + * + * NB : since the session structure is in shared memory, it must have the + * same layout on both sides (normal world kernel and RTM). + * All types use platform endianness (specifically, the endianness used by + * the secure world). + */ +struct interworld_session { + u32 status; + u32 return_origin; + u16 session_handle; + u16 param_types; + + union { + u32 command_id; /** invoke-command only */ + u32 login; /** open-session only */ + }; + + union interworld_parameter params[4]; + + /* The following fields are only used during open-session */ + struct teec_uuid target_uuid; + struct teec_uuid client_uuid; +}; + +#endif /** MCIIWP_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mci/mcimcp.h b/drivers/gud/MobiCoreDriver/mci/mcimcp.h new file mode 100644 index 000000000000..058f6bfb6ee5 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mci/mcimcp.h @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2013-2018 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. + */ + +#ifndef MCP_H_ +#define MCP_H_ + +#include "mci/mcloadformat.h" + +/** Indicates a response */ +#define FLAG_RESPONSE BIT(31) + +/** MobiCore Return Code Defines. + * List of the possible MobiCore return codes. + */ +enum mcp_result { + /** Memory has successfully been mapped */ + MC_MCP_RET_OK = 0, + /** The session ID is invalid */ + MC_MCP_RET_ERR_INVALID_SESSION = 1, + /** The UUID of the Trustlet is unknown */ + MC_MCP_RET_ERR_UNKNOWN_UUID = 2, + /** The ID of the driver is unknown */ + MC_MCP_RET_ERR_UNKNOWN_DRIVER_ID = 3, + /** No more session are allowed */ + MC_MCP_RET_ERR_NO_MORE_SESSIONS = 4, + /** The container is invalid */ + MC_MCP_RET_ERR_CONTAINER_INVALID = 5, + /** The Trustlet is invalid */ + MC_MCP_RET_ERR_TRUSTLET_INVALID = 6, + /** The memory block has already been mapped before */ + MC_MCP_RET_ERR_ALREADY_MAPPED = 7, + /** Alignment or length error in the command parameters */ + MC_MCP_RET_ERR_INVALID_PARAM = 8, + /** No space left in the virtual address space of the session */ + MC_MCP_RET_ERR_OUT_OF_RESOURCES = 9, + /** WSM type unknown or broken WSM */ + MC_MCP_RET_ERR_INVALID_WSM = 10, + /** unknown error */ + MC_MCP_RET_ERR_UNKNOWN = 11, + /** Length of map invalid */ + MC_MCP_RET_ERR_INVALID_MAPPING_LENGTH = 12, + /** Map can only be applied to Trustlet session */ + MC_MCP_RET_ERR_MAPPING_TARGET = 13, + /** Couldn't open crypto session */ + MC_MCP_RET_ERR_OUT_OF_CRYPTO_RESOURCES = 14, + /** System Trustlet signature verification failed */ + MC_MCP_RET_ERR_SIGNATURE_VERIFICATION_FAILED = 15, + /** System Trustlet public key is wrong */ + MC_MCP_RET_ERR_WRONG_PUBLIC_KEY = 16, + /** Wrong containter type(s) */ + MC_MCP_RET_ERR_CONTAINER_TYPE_MISMATCH = 17, + /** Container is locked (or not activated) */ + MC_MCP_RET_ERR_CONTAINER_LOCKED = 18, + /** SPID is not registered with root container */ + MC_MCP_RET_ERR_SP_NO_CHILD = 19, + /** UUID is not registered with sp container */ + MC_MCP_RET_ERR_TL_NO_CHILD = 20, + /** Unwrapping of root container failed */ + MC_MCP_RET_ERR_UNWRAP_ROOT_FAILED = 21, + /** Unwrapping of service provider container failed */ + MC_MCP_RET_ERR_UNWRAP_SP_FAILED = 22, + /** Unwrapping of Trustlet container failed */ + MC_MCP_RET_ERR_UNWRAP_TRUSTLET_FAILED = 23, + /** Container version mismatch */ + MC_MCP_RET_ERR_CONTAINER_VERSION_MISMATCH = 24, + /** Decryption of service provider trustlet failed */ + MC_MCP_RET_ERR_SP_TL_DECRYPTION_FAILED = 25, + /** Hash check of service provider trustlet failed */ + MC_MCP_RET_ERR_SP_TL_HASH_CHECK_FAILED = 26, + /** Activation/starting of task failed */ + MC_MCP_RET_ERR_LAUNCH_TASK_FAILED = 27, + /** Closing of task not yet possible, try again later */ + MC_MCP_RET_ERR_CLOSE_TASK_FAILED = 28, + /**< Service is blocked and a session cannot be opened to it */ + MC_MCP_RET_ERR_SERVICE_BLOCKED = 29, + /**< Service is locked and a session cannot be opened to it */ + MC_MCP_RET_ERR_SERVICE_LOCKED = 30, + /**< Service was forcefully killed (due to an administrative command) */ + MC_MCP_RET_ERR_SERVICE_KILLED = 31, + /**< Service version is lower than the one installed. */ + MC_MCP_RET_ERR_DOWNGRADE_NOT_AUTHORIZED = 32, + /**< Filesystem not yet ready. */ + MC_MCP_RET_ERR_SYSTEM_NOT_READY = 33, + /** The command is unknown */ + MC_MCP_RET_ERR_UNKNOWN_COMMAND = 50, + /** The command data is invalid */ + MC_MCP_RET_ERR_INVALID_DATA = 51 +}; + +/** Possible MCP Command IDs + * Command ID must be between 0 and 0x7FFFFFFF. + */ +enum cmd_id { + /** Invalid command ID */ + MC_MCP_CMD_ID_INVALID = 0x00, + /** Open a session */ + MC_MCP_CMD_OPEN_SESSION = 0x01, + /** Close an existing session */ + MC_MCP_CMD_CLOSE_SESSION = 0x03, + /** Map WSM to session */ + MC_MCP_CMD_MAP = 0x04, + /** Unmap WSM from session */ + MC_MCP_CMD_UNMAP = 0x05, + /** Prepare for suspend */ + MC_MCP_CMD_SUSPEND = 0x06, + /** Resume from suspension */ + MC_MCP_CMD_RESUME = 0x07, + /** Get MobiCore version information */ + MC_MCP_CMD_GET_MOBICORE_VERSION = 0x09, + /** Close MCP and unmap MCI */ + MC_MCP_CMD_CLOSE_MCP = 0x0A, + /** Load token for device attestation */ + MC_MCP_CMD_LOAD_TOKEN = 0x0B, + /** Check that TA can be loaded */ + MC_MCP_CMD_CHECK_LOAD_TA = 0x0C, +}; + +/* + * Types of WSM known to the MobiCore. + */ +#define WSM_TYPE_MASK 0xFF +#define WSM_INVALID 0 /** Invalid memory type */ +#define WSM_L2 2 /** Buffer mapping uses L2/L3 table */ +#define WSM_L1 3 /** Buffer mapping uses fake L1 table */ + +/* + * Magic number used to identify if Open Command supports GP client + * authentication. + */ +#define MC_GP_CLIENT_AUTH_MAGIC 0x47504131 /* "GPA1" */ + +/* + * Initialisation values flags + */ +/* Set if IRQ is present */ +#define MC_IV_FLAG_IRQ BIT(0) +/* Set if GP TIME is supported */ +#define MC_IV_FLAG_TIME BIT(1) +/* Set if GP client uses interworld session */ +#define MC_IV_FLAG_IWP BIT(2) + +struct init_values { + u32 flags; + u32 irq; + u32 time_ofs; + u32 time_len; + /* interworld session buffer offset in MCI */ + u32 iws_buf_ofs; + /* interworld session buffer size */ + u32 iws_buf_size; + u8 padding[8]; +}; + +/** Command header. + * It just contains the command ID. Only values specified in cmd_id are + * allowed as command IDs. If the command ID is unspecified the MobiCore + * returns an empty response with the result set to + * MC_MCP_RET_ERR_UNKNOWN_COMMAND. + */ +struct cmd_header { + enum cmd_id cmd_id; /** Command ID of the command */ +}; + +/** Response header. + * MobiCore will reply to every MCP command with an MCP response. Like the MCP + * command the response consists of a header followed by response data. The + * response is written to the same memory location as the MCP command. + */ +struct rsp_header { + u32 rsp_id; /** Command ID | FLAG_RESPONSE */ + enum mcp_result result; /** Result of the command execution */ +}; + +/** @defgroup CMD MCP Commands + */ + +/** @defgroup ASMCMD Administrative Commands + */ + +/** @defgroup MCPGETMOBICOREVERSION GET_MOBICORE_VERSION + * Get MobiCore version info. + * + */ + +/** Get MobiCore Version Command */ +struct cmd_get_version { + struct cmd_header cmd_header; /** Command header */ +}; + +/** Get MobiCore Version Command Response */ +struct rsp_get_version { + struct rsp_header rsp_header; /** Response header */ + struct mc_version_info version_info; /** MobiCore version info */ +}; + +/** @defgroup POWERCMD Power Management Commands + */ + +/** @defgroup MCPSUSPEND SUSPEND + * Prepare MobiCore suspension. + * This command allows MobiCore and MobiCore drivers to release or clean + * resources and save device state. + * + */ + +/** Suspend Command */ +struct cmd_suspend { + struct cmd_header cmd_header; /** Command header */ +}; + +/** Suspend Command Response */ +struct rsp_suspend { + struct rsp_header rsp_header; /** Response header */ +}; + +/** @defgroup MCPRESUME RESUME + * Resume MobiCore from suspension. + * This command allows MobiCore and MobiCore drivers to reinitialize hardware + * affected by suspension. + * + */ + +/** Resume Command */ +struct cmd_resume { + struct cmd_header cmd_header; /** Command header */ +}; + +/** Resume Command Response */ +struct rsp_resume { + struct rsp_header rsp_header; /** Response header */ +}; + +/** @defgroup SESSCMD Session Management Commands + */ + +/** @defgroup MCPOPEN OPEN + * Load and open a session to a Trustlet. + * The OPEN command loads Trustlet data to the MobiCore context and opens a + * session to the Trustlet. If wsm_data_type is WSM_INVALID MobiCore tries to + * start a pre-installed Trustlet associated with the uuid passed. The uuid + * passed must match the uuid contained in the load data (if available). + * On success, MobiCore returns the session ID which can be used for further + * communication. + */ + +/** GP client authentication data */ +struct cmd_open_data { + u32 mclf_magic; /** ASCII "MCLF" on older versions */ + struct identity identity; /** Login method and data */ +}; + +/** Open Command */ +struct cmd_open { + struct cmd_header cmd_header; /** Command header */ + struct mc_uuid_t uuid; /** Service UUID */ + u8 unused[4]; /** Padding to be 64-bit aligned */ + u64 adr_tci_buffer; /** Physical address of the TCI MMU */ + u64 adr_load_data; /** Physical address of the data MMU */ + u32 ofs_tci_buffer; /** Offset to the data */ + u32 len_tci_buffer; /** Length of the TCI */ + u32 wsmtype_tci; /** Type of WSM used for the TCI */ + u32 wsm_data_type; /** Type of MMU */ + u32 ofs_load_data; /** Offset to the data */ + u32 len_load_data; /** Length of the data to load */ + union { + struct cmd_open_data cmd_open_data; /** Client login data */ + union mclf_header tl_header; /** Service header */ + }; + u32 is_gpta; /** true if looking for an SD/GP-TA */ +}; + +/** Open Command Response */ +struct rsp_open { + struct rsp_header rsp_header; /** Response header */ + u32 session_id; /** Session ID */ +}; + +/** TA Load Check Command */ +struct cmd_check_load { + struct cmd_header cmd_header; /** Command header */ + struct mc_uuid_t uuid; /** Service UUID */ + u8 unused[4]; /** Padding to be 64-bit aligned */ + u64 adr_load_data; /** Physical address of the data */ + u32 wsm_data_type; /** Type of MMU */ + u32 ofs_load_data; /** Offset to the data */ + u32 len_load_data; /** Length of the data to load */ + union mclf_header tl_header; /** Service header */ +}; + +/** TA Load Check Response */ +struct rsp_check_load { + struct rsp_header rsp_header; /** Response header */ +}; + +/** @defgroup MCPCLOSE CLOSE + * Close an existing session to a Trustlet. + * The CLOSE command terminates a session and frees all resources in the + * MobiCore system which are currently occupied by the session. Before closing + * the session, the MobiCore runtime management waits until all pending + * operations, like calls to drivers, invoked by the Trustlet have been + * terminated. Mapped memory will automatically be unmapped from the MobiCore + * context. The NWd is responsible for processing the freed memory according to + * the Rich-OS needs. + * + */ + +/** Close Command */ +struct cmd_close { + struct cmd_header cmd_header; /** Command header */ + u32 session_id; /** Session ID */ +}; + +/** Close Command Response */ +struct rsp_close { + struct rsp_header rsp_header; /** Response header */ +}; + +/** @defgroup MCPMAP MAP + * Map a portion of memory to a session. + * The MAP command provides a block of memory to the context of a service. + * The memory then becomes world-shared memory (WSM). + * The only allowed memory type here is WSM_L2. + */ + +/** Map Command */ +struct cmd_map { + struct cmd_header cmd_header; /** Command header */ + u32 session_id; /** Session ID */ + u32 wsm_type; /** Type of MMU */ + u32 ofs_buffer; /** Offset to the payload */ + u64 adr_buffer; /** Physical address of the MMU */ + u32 len_buffer; /** Length of the buffer */ + u32 flags; /** Attributes (read/write) */ +}; + +#define MCP_MAP_MAX 0x100000 /** Maximum length for MCP map */ + +/** Map Command Response */ +struct rsp_map { + struct rsp_header rsp_header; /** Response header */ + /** Virtual address the WSM is mapped to, may include an offset! */ + u32 secure_va; +}; + +/** @defgroup MCPUNMAP UNMAP + * Unmap a portion of world-shared memory from a session. + * The UNMAP command is used to unmap a previously mapped block of + * world shared memory from the context of a session. + * + * Attention: The memory block will be immediately unmapped from the specified + * session. If the service is still accessing the memory, the service will + * trigger a segmentation fault. + */ + +/** Unmap Command */ +struct cmd_unmap { + struct cmd_header cmd_header; /** Command header */ + u32 session_id; /** Session ID */ + u32 wsm_type; /** Type of WSM used of the memory */ + /** Virtual address the WSM is mapped to, may include an offset! */ + u32 secure_va; + u32 virtual_buffer_len; /** Length of virtual buffer */ +}; + +/** Unmap Command Response */ +struct rsp_unmap { + struct rsp_header rsp_header; /** Response header */ +}; + +/** @defgroup MCPLOADTOKEN + * Load a token from the normal world and share it with the TEE + * If something fails, the device attestation functionality will be disabled + */ + +/** Load Token */ +struct cmd_load_token { + struct cmd_header cmd_header; /** Command header */ + u32 wsm_data_type; /** Type of MMU */ + u64 adr_load_data; /** Physical address of the MMU */ + u64 ofs_load_data; /** Offset to the data */ + u64 len_load_data; /** Length of the data */ +}; + +/** Load Token Command Response */ +struct rsp_load_token { + struct rsp_header rsp_header; /** Response header */ +}; + +/** Structure of the MCP buffer */ +union mcp_message { + struct init_values init_values; /** Initialisation values */ + struct cmd_header cmd_header; /** Command header */ + struct rsp_header rsp_header; + struct cmd_open cmd_open; /** Load and open service */ + struct rsp_open rsp_open; + struct cmd_close cmd_close; /** Close command */ + struct rsp_close rsp_close; + struct cmd_map cmd_map; /** Map WSM to service */ + struct rsp_map rsp_map; + struct cmd_unmap cmd_unmap; /** Unmap WSM from service */ + struct rsp_unmap rsp_unmap; + struct cmd_suspend cmd_suspend; /** Suspend MobiCore */ + struct rsp_suspend rsp_suspend; + struct cmd_resume cmd_resume; /** Resume MobiCore */ + struct rsp_resume rsp_resume; + struct cmd_get_version cmd_get_version; /** Get MobiCore Version */ + struct rsp_get_version rsp_get_version; + struct cmd_load_token cmd_load_token; /** Load token */ + struct rsp_load_token rsp_load_token; + struct cmd_check_load cmd_check_load; /** TA load check */ + struct rsp_check_load rsp_check_load; +}; + +/** Minimum MCP buffer length (in bytes) */ +#define MIN_MCP_LEN sizeof(mcp_message_t) + +#define MC_FLAG_NO_SLEEP_REQ 0 +#define MC_FLAG_REQ_TO_SLEEP 1 + +#define MC_STATE_NORMAL_EXECUTION 0 +#define MC_STATE_READY_TO_SLEEP 1 + +struct sleep_mode { + u16 sleep_req; /** Ask SWd to get ready to sleep */ + u16 ready_to_sleep; /** SWd is now ready to sleep */ +}; + +/** MobiCore status flags */ +struct mcp_flags { + /** If not MC_FLAG_SCHEDULE_IDLE, MobiCore needsscheduling */ + u32 schedule; + struct sleep_mode sleep_mode; + /** Secure-world sleep timeout in milliseconds */ + s32 timeout_ms; + /** Reserved for future use: Must not be interpreted */ + u32 RFU3; +}; + +/** MobiCore is idle. No scheduling required */ +#define MC_FLAG_SCHEDULE_IDLE 0 +/** MobiCore is non idle, scheduling is required */ +#define MC_FLAG_SCHEDULE_NON_IDLE 1 + +/** MCP buffer structure */ +struct mcp_buffer { + struct mcp_flags flags; /** MobiCore Flags */ + union mcp_message message; /** MCP message buffer */ +}; + +#endif /* MCP_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mci/mcinq.h b/drivers/gud/MobiCoreDriver/mci/mcinq.h new file mode 100644 index 000000000000..98a51681839b --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mci/mcinq.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef NQ_H_ +#define NQ_H_ + +/** \name NQ Size Defines + * Minimum and maximum count of elements in the notification queue. + */ +#define MIN_NQ_ELEM 1 /** Minimum notification queue elements */ +#define MAX_NQ_ELEM 64 /** Maximum notification queue elements */ + +/* Compute notification queue size in bytes from its number of elements */ +#define NQ_SIZE(n) (2 * (sizeof(struct notification_queue_header)\ + + (n) * sizeof(struct notification))) + +/** \name NQ Length Defines + * Note that there is one queue for NWd->SWd and one queue for SWd->NWd + */ +/** Minimum size for the notification queue data structure */ +#define MIN_NQ_LEN NQ_SIZE(MIN_NQ_ELEM) +/** Maximum size for the notification queue data structure */ +#define MAX_NQ_LEN NQ_SIZE(MAX_NQ_ELEM) + +/** \name Session ID Defines + * Standard Session IDs. + */ +/** MCP session ID, used to communicate with MobiCore (e.g. to start/stop TA) */ +#define SID_MCP 0 +/** Invalid session id, returned in case of error */ +#define SID_INVALID 0xffffffff + +/** Notification data structure */ +struct notification { + u32 session_id; /** Session ID */ + s32 payload; /** Additional notification info */ +}; + +/** Notification payload codes. + * 0 indicated a plain simple notification, + * a positive value is a termination reason from the task, + * a negative value is a termination reason from MobiCore. + * Possible negative values are given below. + */ +enum notification_payload { + /** task terminated, but exit code is invalid */ + ERR_INVALID_EXIT_CODE = -1, + /** task terminated due to session end, no exit code available */ + ERR_SESSION_CLOSE = -2, + /** task terminated due to invalid operation */ + ERR_INVALID_OPERATION = -3, + /** session ID is unknown */ + ERR_INVALID_SID = -4, + /** session is not active */ + ERR_SID_NOT_ACTIVE = -5, + /** session was force-killed (due to an administrative command). */ + ERR_SESSION_KILLED = -6, +}; + +/** Declaration of the notification queue header. + * layout as specified in the data structure specification. + */ +struct notification_queue_header { + u32 write_cnt; /** Write counter */ + u32 read_cnt; /** Read counter */ + u32 queue_size; /** Queue size */ +}; + +/** Queue struct which defines a queue object. + * The queue struct is accessed by the queue type of + * function. elementCnt must be a power of two and the power needs + * to be smaller than power of u32 (obviously 32). + */ +struct notification_queue { + struct notification_queue_header hdr; /** Queue header */ + struct notification notification[MIN_NQ_ELEM]; /** Elements */ +}; + +#endif /** NQ_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mci/mcitime.h b/drivers/gud/MobiCoreDriver/mci/mcitime.h new file mode 100644 index 000000000000..c666c1a680a6 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mci/mcitime.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 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. + */ + +#ifndef MCITIME_H_ +#define MCITIME_H_ + +/* + * Trustonic TEE RICH OS Time: + * -seconds and nanoseconds since Jan 1, 1970, UTC + * -monotonic counter + */ +struct mcp_time { + u64 wall_clock_seconds; + u64 wall_clock_nsec; + u64 monotonic_seconds; + u64 monotonic_nsec; +}; + +#endif /* MCITIME_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mci/mcloadformat.h b/drivers/gud/MobiCoreDriver/mci/mcloadformat.h new file mode 100644 index 000000000000..1ebea5f17ef9 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mci/mcloadformat.h @@ -0,0 +1,134 @@ +/* + * 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. + */ +#ifndef MCLOADFORMAT_H_ +#define MCLOADFORMAT_H_ + +/** Trustlet Blob length info */ +#define MC_TLBLOBLEN_MAGIC 0x7672746C /* Magic for SWd: vrtl */ +#define MAX_SO_CONT_SIZE 512 /* Max size for a container */ + +/** MCLF flags */ +/**< Loaded service cannot be unloaded from MobiCore. */ +#define MC_SERVICE_HEADER_FLAGS_PERMANENT BIT(0) +/**< Service has no WSM control interface. */ +#define MC_SERVICE_HEADER_FLAGS_NO_CONTROL_INTERFACE BIT(1) +/**< Service can be debugged. */ +#define MC_SERVICE_HEADER_FLAGS_DEBUGGABLE BIT(2) +/**< New-layout trusted application or trusted driver. */ +#define MC_SERVICE_HEADER_FLAGS_EXTENDED_LAYOUT BIT(3) + +/** Service type. + * The service type defines the type of executable. + */ +enum service_type { + SERVICE_TYPE_ILLEGAL = 0, + SERVICE_TYPE_DRIVER = 1, + SERVICE_TYPE_SP_TRUSTLET = 2, + SERVICE_TYPE_SYSTEM_TRUSTLET = 3, + SERVICE_TYPE_MIDDLEWARE = 4, + SERVICE_TYPE_LAST_ENTRY = 5, +}; + +/** + * Descriptor for a memory segment. + */ +struct segment_descriptor { + u32 start; /**< Virtual start address */ + u32 len; /**< Segment length in bytes */ +}; + +/** + * MCLF intro for data structure identification. + * Must be the first element of a valid MCLF file. + */ +struct mclf_intro { + u32 magic; /**< Header magic value ASCII "MCLF" */ + u32 version; /**< Version the MCLF header struct */ +}; + +/** + * @defgroup MCLF_VER_V2 MCLF Version 32 + * @ingroup MCLF_VER + * + * @addtogroup MCLF_VER_V2 + */ + +/* + * GP TA identity. + */ +struct identity { + /**< GP TA login type */ + u32 login_type; + /**< GP TA login data */ + u8 login_data[16]; +}; + +/** + * Version 2.1/2.2 MCLF header. + */ +struct mclf_header_v2 { + /**< MCLF header start with the mandatory intro */ + struct mclf_intro intro; + /**< Service flags */ + u32 flags; + /**< Type of memory the service must be executed from */ + u32 mem_type; + /**< Type of service */ + enum service_type service_type; + /**< Number of instances which can be run simultaneously */ + u32 num_instances; + /**< Loadable service unique identifier (UUID) */ + struct mc_uuid_t uuid; + /**< If the service_type is SERVICE_TYPE_DRIVER the Driver ID is used */ + u32 driver_id; + /**< + * Number of threads (N) in a service: + * SERVICE_TYPE_SP_TRUSTLET: N = 1 + * SERVICE_TYPE_SYSTEM_TRUSTLET: N = 1 + * SERVICE_TYPE_DRIVER: N >= 1 + */ + u32 num_threads; + /**< Virtual text segment */ + struct segment_descriptor text; + /**< Virtual data segment */ + struct segment_descriptor data; + /**< Length of the BSS segment in bytes. MUST be at least 8 byte */ + u32 bss_len; + /**< Virtual start address of service code */ + u32 entry; + /**< Version of the interface the driver exports */ + u32 service_version; +}; + +/** + * @addtogroup MCLF + */ + +/** MCLF header */ +union mclf_header { + /**< Intro for data identification */ + struct mclf_intro intro; + /**< Version 2 header */ + struct mclf_header_v2 mclf_header_v2; +}; + +struct mc_blob_len_info { + u32 magic; /**< New blob format magic number */ + u32 root_size; /**< Root container size */ + u32 sp_size; /**< SP container size */ + u32 ta_size; /**< TA container size */ + u32 reserved[4]; /**< Reserved for further Use */ +}; + +#endif /* MCLOADFORMAT_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mcp.c b/drivers/gud/MobiCoreDriver/mcp.c new file mode 100644 index 000000000000..ca541acf9488 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mcp.c @@ -0,0 +1,872 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE +#include /* local_clock */ +#endif + +#include "public/mc_user.h" +#include "public/mc_admin.h" + +#include "mci/mcimcp.h" +#include "mci/mcifc.h" +#include "mci/mcinq.h" /* SID_MCP */ +#include "mci/mcitime.h" /* struct mcp_time */ +#include "mci/mciiwp.h" + +#include "platform.h" /* IRQ number */ +#include "main.h" +#include "fastcall.h" +#include "logging.h" +#include "mcp.h" + +/* respond timeout for MCP notification, in secs */ +#define MCP_TIMEOUT 10 +#define MCP_RETRIES 5 +#define MCP_NF_QUEUE_SZ 8 + +static struct { + struct mutex queue_lock; /* Lock for MCP messages */ + struct completion complete; + bool mcp_dead; + struct mcp_session mcp_session; /* Pseudo session for MCP */ + /* Unexpected notification (during MCP open) */ + struct mutex unexp_notif_mutex; + struct notification unexp_notif; + /* Sessions */ + struct mutex sessions_lock; + struct list_head sessions; + /* Wait timeout */ + u32 timeout; + /* Log of last MCP commands */ +#define MCP_LOG_SIZE 256 + struct mutex last_mcp_cmds_mutex; /* Log protection */ + struct mcp_command_info { + u64 cpu_clk; /* Kernel time */ + pid_t pid; /* Caller PID */ + enum cmd_id id; /* MCP command ID */ + u32 session_id; + char uuid_str[34]; + enum state { + UNUSED, /* Unused slot */ + PENDING, /* Previous command in progress */ + SENT, /* Waiting for response */ + COMPLETE, /* Got result */ + FAILED, /* Something went wrong */ + } state; /* Command processing state */ + enum mcp_result result; /* Command result */ + int errno; /* Return code */ + } last_mcp_cmds[MCP_LOG_SIZE]; + int last_mcp_cmds_index; +} l_ctx; + +static const char *mcp_cmd_to_string(enum cmd_id id) +{ + switch (id) { + case MC_MCP_CMD_ID_INVALID: + return "invalid"; + case MC_MCP_CMD_OPEN_SESSION: + return "open session"; + case MC_MCP_CMD_CLOSE_SESSION: + return "close session"; + case MC_MCP_CMD_MAP: + return "map"; + case MC_MCP_CMD_UNMAP: + return "unmap"; + case MC_MCP_CMD_SUSPEND: + return "suspend"; + case MC_MCP_CMD_RESUME: + return "resume"; + case MC_MCP_CMD_GET_MOBICORE_VERSION: + return "get version"; + case MC_MCP_CMD_CLOSE_MCP: + return "close mcp"; + case MC_MCP_CMD_LOAD_TOKEN: + return "load token"; + case MC_MCP_CMD_CHECK_LOAD_TA: + return "check load TA"; + } + return "unknown"; +} + +static inline void mark_mcp_dead(void) +{ + struct mcp_session *session; + + l_ctx.mcp_dead = true; + complete(&l_ctx.complete); + /* Signal all potential waiters that SWd is going away */ + list_for_each_entry(session, &l_ctx.sessions, list) + complete(&session->completion); +} + +void mcp_session_init(struct mcp_session *session) +{ + nq_session_init(&session->nq_session, false); + session->sid = SID_INVALID; + INIT_LIST_HEAD(&session->list); + mutex_init(&session->notif_wait_lock); + init_completion(&session->completion); + mutex_init(&session->exit_code_lock); + session->exit_code = 0; + session->state = MCP_SESSION_RUNNING; +} + +static inline bool mcp_session_isrunning(struct mcp_session *session) +{ + bool ret; + + mutex_lock(&l_ctx.sessions_lock); + ret = session->state == MCP_SESSION_RUNNING; + mutex_unlock(&l_ctx.sessions_lock); + return ret; +} + +/* + * session remains valid thanks to the upper layers reference counters, but the + * SWd session may have died, in which case we are informed. + */ +int mcp_session_waitnotif(struct mcp_session *session, s32 timeout, + bool silent_expiry) +{ + int ret = 0; + + mutex_lock(&session->notif_wait_lock); + if (l_ctx.mcp_dead) { + ret = -ENOTCONN; + goto end; + } + + if (!mcp_session_isrunning(session)) { + ret = -ENXIO; + goto end; + } + + if (mcp_session_exitcode(session)) { + ret = -ECOMM; + goto end; + } + + if (timeout < 0) { + ret = wait_for_completion_interruptible(&session->completion); + if (ret) + goto end; + } else { + ret = wait_for_completion_interruptible_timeout( + &session->completion, timeout * HZ / 1000); + if (ret < 0) + /* Interrupted */ + goto end; + + if (!ret) { + /* Timed out */ + ret = -ETIME; + goto end; + } + + ret = 0; + } + + if (l_ctx.mcp_dead) { + ret = -ENOTCONN; + goto end; + } + + if (mcp_session_exitcode(session)) { + ret = -ECOMM; + goto end; + } + + if (!mcp_session_isrunning(session)) { + ret = -ENXIO; + goto end; + } + +end: + if (!ret) + nq_session_state_update(&session->nq_session, + NQ_NOTIF_CONSUMED); + else if (ret != -ERESTARTSYS) + nq_session_state_update(&session->nq_session, NQ_NOTIF_DEAD); + + mutex_unlock(&session->notif_wait_lock); + if (ret && ((ret != -ETIME) || !silent_expiry)) { +#ifdef CONFIG_FREEZER + if (ret == -ERESTARTSYS && system_freezing_cnt.counter == 1) + mc_dev_devel("freezing session %x", session->sid); + else +#endif + mc_dev_devel("session %x ec %d ret %d", + session->sid, session->exit_code, ret); + } + + return ret; +} + +s32 mcp_session_exitcode(struct mcp_session *session) +{ + s32 exit_code; + + mutex_lock(&session->exit_code_lock); + exit_code = session->exit_code; + mutex_unlock(&session->exit_code_lock); + if (exit_code) + mc_dev_info("session %x ec %d", session->sid, exit_code); + + return exit_code; +} + +static inline int wait_mcp_notification(void) +{ + unsigned long timeout = msecs_to_jiffies(l_ctx.timeout * 1000); + int try; + + /* + * Total timeout is l_ctx.timeout * MCP_RETRIES, but we check for + * a crash to try and terminate before then if things go wrong. + */ + for (try = 1; try <= MCP_RETRIES; try++) { + u32 status; + int ret; + + /* + * Wait non-interruptible to keep MCP synchronised even if + * caller is interrupted by signal. + */ + ret = wait_for_completion_timeout(&l_ctx.complete, timeout); + if (ret > 0) + return 0; + + mc_dev_err("No answer after %ds", l_ctx.timeout * try); + + /* If SWd halted, exit now */ + if (!mc_fc_info(MC_EXT_INFO_ID_MCI_VERSION, &status, NULL) && + status == MC_STATUS_HALT) + break; + } + + /* TEE halted or dead: dump status and SMC log */ + mark_mcp_dead(); + nq_dump_status(); + + return -ETIME; +} + +static int mcp_cmd(union mcp_message *cmd, + /* The fields below are for debug purpose only */ + u32 in_session_id, + u32 *out_session_id, + struct mc_uuid_t *uuid) +{ + int err = 0, ret = -ENOTCONN; + union mcp_message *msg = nq_get_mcp_message(); + enum cmd_id cmd_id = cmd->cmd_header.cmd_id; + struct mcp_command_info *cmd_info; + + /* Initialize MCP log */ + mutex_lock(&l_ctx.last_mcp_cmds_mutex); + cmd_info = &l_ctx.last_mcp_cmds[l_ctx.last_mcp_cmds_index]; + cmd_info->cpu_clk = local_clock(); + cmd_info->pid = current->pid; + cmd_info->cpu_clk = local_clock(); + cmd_info->id = cmd_id; + cmd_info->session_id = in_session_id; + if (uuid) { + /* display UUID because it's an openSession cmd */ + size_t i; + + cmd_info->uuid_str[0] = ' '; + for (i = 0; i < sizeof(uuid->value); i++) { + snprintf(&cmd_info->uuid_str[1 + i * 2], 3, "%02x", + uuid->value[i]); + } + } else { + cmd_info->uuid_str[0] = '\0'; + } + + cmd_info->state = PENDING; + cmd_info->result = MC_MCP_RET_OK; + cmd_info->errno = 0; + if (++l_ctx.last_mcp_cmds_index >= MCP_LOG_SIZE) + l_ctx.last_mcp_cmds_index = 0; + mutex_unlock(&l_ctx.last_mcp_cmds_mutex); + + mutex_lock(&l_ctx.queue_lock); + if (l_ctx.mcp_dead) + goto out; + + /* Copy message to MCP buffer */ + memcpy(msg, cmd, sizeof(*msg)); + + /* Poke TEE */ + ret = mcp_notify(&l_ctx.mcp_session); + if (ret) + goto out; + + /* Update MCP log */ + mutex_lock(&l_ctx.last_mcp_cmds_mutex); + cmd_info->state = SENT; + mutex_unlock(&l_ctx.last_mcp_cmds_mutex); + ret = wait_mcp_notification(); + if (ret) + goto out; + + /* Check response ID */ + if (msg->rsp_header.rsp_id != (cmd_id | FLAG_RESPONSE)) { + mc_dev_err("MCP command got invalid response (0x%X)", + msg->rsp_header.rsp_id); + ret = -EBADE; + goto out; + } + + /* Convert result */ + switch (msg->rsp_header.result) { + case MC_MCP_RET_OK: + err = 0; + break; + case MC_MCP_RET_ERR_CLOSE_TASK_FAILED: + err = -EAGAIN; + break; + case MC_MCP_RET_ERR_NO_MORE_SESSIONS: + err = -EBUSY; + break; + case MC_MCP_RET_ERR_OUT_OF_RESOURCES: + err = -ENOSPC; + break; + case MC_MCP_RET_ERR_UNKNOWN_UUID: + err = -ENOENT; + break; + case MC_MCP_RET_ERR_WRONG_PUBLIC_KEY: + err = -EKEYREJECTED; + break; + case MC_MCP_RET_ERR_SERVICE_BLOCKED: + err = -ECONNREFUSED; + break; + case MC_MCP_RET_ERR_SERVICE_LOCKED: + err = -ECONNABORTED; + break; + case MC_MCP_RET_ERR_SERVICE_KILLED: + err = -ECONNRESET; + break; + case MC_MCP_RET_ERR_SYSTEM_NOT_READY: + err = -EAGAIN; + break; + case MC_MCP_RET_ERR_DOWNGRADE_NOT_AUTHORIZED: + err = -EPERM; + break; + default: + err = -EPERM; + } + + /* Copy response back to caller struct */ + memcpy(cmd, msg, sizeof(*cmd)); + +out: + /* Update MCP log */ + mutex_lock(&l_ctx.last_mcp_cmds_mutex); + if (ret) { + cmd_info->state = FAILED; + cmd_info->errno = -ret; + } else { + cmd_info->state = COMPLETE; + cmd_info->result = msg->rsp_header.result; + cmd_info->errno = -err; + /* For open session: get SID */ + if (!err && out_session_id) + cmd_info->session_id = *out_session_id; + } + mutex_unlock(&l_ctx.last_mcp_cmds_mutex); + mutex_unlock(&l_ctx.queue_lock); + if (ret) { + mc_dev_err("%s: sending failed, ret = %d", + mcp_cmd_to_string(cmd_id), ret); + return ret; + } + + if (err) { + if (cmd_id == MC_MCP_CMD_CLOSE_SESSION && err == -EAGAIN) + mc_dev_devel("%s: try again", + mcp_cmd_to_string(cmd_id)); + else + mc_dev_err("%s: res %d/ret %d", + mcp_cmd_to_string(cmd_id), + msg->rsp_header.result, err); + return err; + } + + return 0; +} + +int mcp_get_version(struct mc_version_info *version_info) +{ + static struct mc_version_info static_version_info; + + /* If cache empty, get version from the SWd and cache it */ + if (!static_version_info.version_nwd) { + u32 version = MC_VERSION(MCDRVMODULEAPI_VERSION_MAJOR, + MCDRVMODULEAPI_VERSION_MINOR); + union mcp_message cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_GET_MOBICORE_VERSION; + ret = mcp_cmd(&cmd, 0, NULL, NULL); + if (ret) + return ret; + + memcpy(&static_version_info, &cmd.rsp_get_version.version_info, + sizeof(static_version_info)); + /* + * The CMP version is meaningless in this case, and is replaced + * by the driver's own version. + */ + static_version_info.version_nwd = version; + } + + /* Copy cached version */ + memcpy(version_info, &static_version_info, sizeof(*version_info)); + return 0; +} + +int mcp_load_token(uintptr_t data, const struct mcp_buffer_map *map) +{ + union mcp_message cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_LOAD_TOKEN; + cmd.cmd_load_token.wsm_data_type = map->type; + cmd.cmd_load_token.adr_load_data = map->phys_addr; + cmd.cmd_load_token.ofs_load_data = map->offset; + cmd.cmd_load_token.len_load_data = map->length; + return mcp_cmd(&cmd, 0, NULL, NULL); +} + +int mcp_load_check(const struct tee_object *obj, + const struct mcp_buffer_map *map) +{ + const union mclf_header *header; + union mcp_message cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_CHECK_LOAD_TA; + /* Data */ + cmd.cmd_check_load.wsm_data_type = map->type; + cmd.cmd_check_load.adr_load_data = map->phys_addr; + cmd.cmd_check_load.ofs_load_data = map->offset; + cmd.cmd_check_load.len_load_data = map->length; + /* Header */ + header = (union mclf_header *)(obj->data + obj->header_length); + cmd.cmd_check_load.uuid = header->mclf_header_v2.uuid; + return mcp_cmd(&cmd, 0, NULL, &cmd.cmd_check_load.uuid); +} + +int mcp_open_session(struct mcp_session *session, + const struct tee_object *obj, + const struct mcp_buffer_map *map, + const struct mcp_buffer_map *tci_map) +{ + static DEFINE_MUTEX(local_mutex); + const union mclf_header *header; + union mcp_message cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_OPEN_SESSION; + /* Data */ + cmd.cmd_open.wsm_data_type = map->type; + cmd.cmd_open.adr_load_data = map->phys_addr; + cmd.cmd_open.ofs_load_data = map->offset; + cmd.cmd_open.len_load_data = map->length; + /* Buffer */ + if (tci_map) { + cmd.cmd_open.wsmtype_tci = tci_map->type; + cmd.cmd_open.adr_tci_buffer = tci_map->phys_addr; + cmd.cmd_open.ofs_tci_buffer = tci_map->offset; + cmd.cmd_open.len_tci_buffer = tci_map->length; + } else { + cmd.cmd_open.wsmtype_tci = WSM_INVALID; + } + /* Header */ + header = (union mclf_header *)(obj->data + obj->header_length); + cmd.cmd_open.uuid = header->mclf_header_v2.uuid; + cmd.cmd_open.is_gpta = nq_session_is_gp(&session->nq_session); + /* Reset unexpected notification */ + mutex_lock(&local_mutex); + l_ctx.unexp_notif.session_id = SID_MCP; /* Cannot be */ + if (!g_ctx.f_client_login) + memcpy(&cmd.cmd_open.tl_header, header, + sizeof(cmd.cmd_open.tl_header)); + else + cmd.cmd_open.cmd_open_data.mclf_magic = MC_GP_CLIENT_AUTH_MAGIC; + + /* Send MCP open command */ + ret = mcp_cmd(&cmd, 0, &cmd.rsp_open.session_id, &cmd.cmd_open.uuid); + /* Make sure we have a valid session ID */ + if (!ret && !cmd.rsp_open.session_id) + ret = -EBADE; + + if (!ret) { + session->sid = cmd.rsp_open.session_id; + /* Add to list of sessions */ + mutex_lock(&l_ctx.sessions_lock); + list_add_tail(&session->list, &l_ctx.sessions); + mutex_unlock(&l_ctx.sessions_lock); + /* Check for spurious notification */ + mutex_lock(&l_ctx.unexp_notif_mutex); + if (l_ctx.unexp_notif.session_id == session->sid) { + mutex_lock(&session->exit_code_lock); + session->exit_code = l_ctx.unexp_notif.payload; + mutex_unlock(&session->exit_code_lock); + nq_session_state_update(&session->nq_session, + NQ_NOTIF_RECEIVED); + complete(&session->completion); + } + + mutex_unlock(&l_ctx.unexp_notif_mutex); + } + + mutex_unlock(&local_mutex); + return ret; +} + +/* + * Legacy and GP TAs close differently: + * - GP TAs always send a notification with payload, whether on close or crash + * - Legacy TAs only send a notification with payload on crash + * - GP TAs may take time to close, and we get -EAGAIN back from mcp_cmd + * - Legacy TAs always close when asked, unless they are driver in which case + * they just don't close at all + */ +int mcp_close_session(struct mcp_session *session) +{ + union mcp_message cmd; + int ret; + + /* Signal a potential waiter that SWd session is going away */ + complete(&session->completion); + /* Send MCP command */ + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_CLOSE_SESSION; + cmd.cmd_close.session_id = session->sid; + ret = mcp_cmd(&cmd, cmd.cmd_close.session_id, NULL, NULL); + mutex_lock(&l_ctx.sessions_lock); + if (!ret) { + session->state = MCP_SESSION_CLOSED; + list_del(&session->list); + nq_session_exit(&session->nq_session); + } else { + /* Something is not right, assume session is still running */ + session->state = MCP_SESSION_CLOSE_FAILED; + } + mutex_unlock(&l_ctx.sessions_lock); + mc_dev_devel("close session %x ret %d state %d", + session->sid, ret, session->state); + return ret; +} + +/* + * Session is to be removed from NWd records as SWd is dead + */ +void mcp_kill_session(struct mcp_session *session) +{ + mutex_lock(&l_ctx.sessions_lock); + list_del(&session->list); + nq_session_exit(&session->nq_session); + mutex_unlock(&l_ctx.sessions_lock); +} + +int mcp_map(u32 session_id, struct mcp_buffer_map *map) +{ + union mcp_message cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_MAP; + cmd.cmd_map.session_id = session_id; + cmd.cmd_map.wsm_type = map->type; + cmd.cmd_map.adr_buffer = map->phys_addr; + cmd.cmd_map.ofs_buffer = map->offset; + cmd.cmd_map.len_buffer = map->length; + cmd.cmd_map.flags = map->flags; + ret = mcp_cmd(&cmd, session_id, NULL, NULL); + if (!ret) { + map->secure_va = cmd.rsp_map.secure_va; + atomic_inc(&g_ctx.c_maps); + } + + return ret; +} + +int mcp_unmap(u32 session_id, const struct mcp_buffer_map *map) +{ + union mcp_message cmd; + int ret; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_UNMAP; + cmd.cmd_unmap.session_id = session_id; + cmd.cmd_unmap.wsm_type = map->type; + cmd.cmd_unmap.virtual_buffer_len = map->length; + cmd.cmd_unmap.secure_va = map->secure_va; + ret = mcp_cmd(&cmd, session_id, NULL, NULL); + if (!ret) + atomic_dec(&g_ctx.c_maps); + + return ret; +} + +static int mcp_close(void) +{ + union mcp_message cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd_header.cmd_id = MC_MCP_CMD_CLOSE_MCP; + return mcp_cmd(&cmd, 0, NULL, NULL); +} + +int mcp_notify(struct mcp_session *session) +{ + if (session->sid == SID_MCP) + mc_dev_devel("notify MCP"); + else + mc_dev_devel("notify session %x", session->sid); + + return nq_session_notify(&session->nq_session, session->sid, 0); +} + +static inline void session_notif_handler(struct mcp_session *session, u32 id, + u32 payload) +{ + mutex_lock(&l_ctx.sessions_lock); + mc_dev_devel("MCP notif from session %x exit code %d state %d", + id, payload, session ? session->state : -1); + if (session) { + /* TA has terminated */ + if (payload) { + /* Update exit code, or not */ + mutex_lock(&session->exit_code_lock); + /* + * In GP, the only way to recover the sessions exit code + * is to call TEEC_InvokeCommand which will notify. But + * notifying a dead session would change the exit code + * to ERR_SID_NOT_ACTIVE, hence the check below. + */ + if (!nq_session_is_gp(&session->nq_session) || + !session->exit_code || + payload != ERR_SID_NOT_ACTIVE) + session->exit_code = payload; + + mutex_unlock(&session->exit_code_lock); + } + + nq_session_state_update(&session->nq_session, + NQ_NOTIF_RECEIVED); + + /* Unblock waiter */ + complete(&session->completion); + } + mutex_unlock(&l_ctx.sessions_lock); + + /* Unknown session, probably being started */ + if (!session) { + mutex_lock(&l_ctx.unexp_notif_mutex); + l_ctx.unexp_notif.session_id = id; + l_ctx.unexp_notif.payload = payload; + mutex_unlock(&l_ctx.unexp_notif_mutex); + } +} + +static void mcp_notif_handler(u32 id, u32 payload) +{ + if (id == SID_MCP) { + /* MCP notification */ + mc_dev_devel("notification from MCP"); + complete(&l_ctx.complete); + } else { + /* Session notification */ + struct mcp_session *session = NULL, *candidate; + + mutex_lock(&l_ctx.sessions_lock); + list_for_each_entry(candidate, &l_ctx.sessions, list) { + if (candidate->sid == id) { + session = candidate; + break; + } + } + mutex_unlock(&l_ctx.sessions_lock); + + /* session is NULL if id not found */ + session_notif_handler(session, id, payload); + } +} + +int mcp_start(void) +{ + return 0; +} + +void mcp_stop(void) +{ + mcp_close(); +} + +int mcp_init(void) +{ + mutex_init(&l_ctx.queue_lock); + init_completion(&l_ctx.complete); + /* Setup notification queue mutex */ + mcp_session_init(&l_ctx.mcp_session); + l_ctx.mcp_session.sid = SID_MCP; + mutex_init(&l_ctx.unexp_notif_mutex); + INIT_LIST_HEAD(&l_ctx.sessions); + mutex_init(&l_ctx.sessions_lock); + mutex_init(&l_ctx.last_mcp_cmds_mutex); + + l_ctx.timeout = MCP_TIMEOUT; + debugfs_create_u32("mcp_timeout", 0600, g_ctx.debug_dir, + &l_ctx.timeout); + + nq_register_notif_handler(mcp_notif_handler, false); + + return 0; +} + +void mcp_exit(void) +{ + mark_mcp_dead(); +} + +static const char *state_to_string(enum mcp_session_state state) +{ + switch (state) { + case MCP_SESSION_RUNNING: + return "running"; + case MCP_SESSION_CLOSE_FAILED: + return "close failed"; + case MCP_SESSION_CLOSED: + return "closed"; + } + return "error"; +} + +int mcp_debug_sessions(struct kasnprintf_buf *buf) +{ + struct mcp_session *session; + int ret; + + /* Header */ + ret = kasnprintf(buf, "%20s %4s %4s %4s %-15s %-11s\n", + "CPU clock", "ID", "type", "ec", "state", + "notif state"); + if (ret < 0) + return ret; + + mutex_lock(&l_ctx.sessions_lock); + list_for_each_entry(session, &l_ctx.sessions, list) { + s32 exit_code = mcp_session_exitcode(session); + struct nq_session *nq_session = &session->nq_session; + + ret = kasnprintf(buf, "%20llu %4x %-4s %4d %-15s %-11s\n", + nq_session_notif_cpu_clk(nq_session), + session->sid, + nq_session_is_gp(nq_session) ? "GP" : "MC", + exit_code, state_to_string(session->state), + nq_session_state_string(nq_session)); + if (ret < 0) + break; + } + mutex_unlock(&l_ctx.sessions_lock); + return ret; +} + +static inline int show_mcp_log_entry(struct kasnprintf_buf *buf, + struct mcp_command_info *cmd_info) +{ + const char *state_str = "unknown"; + + switch (cmd_info->state) { + case UNUSED: + state_str = "unused"; + break; + case PENDING: + state_str = "pending"; + break; + case SENT: + state_str = "sent"; + break; + case COMPLETE: + state_str = "complete"; + break; + case FAILED: + state_str = "failed"; + break; + } + + return kasnprintf(buf, "%20llu %5d %-13s %5x %-8s %6d %5d%s\n", + cmd_info->cpu_clk, cmd_info->pid, + mcp_cmd_to_string(cmd_info->id), cmd_info->session_id, + state_str, cmd_info->result, cmd_info->errno, + cmd_info->uuid_str); +} + +int mcp_debug_mcpcmds(struct kasnprintf_buf *buf) +{ + struct mcp_command_info *cmd_info; + int i, ret = 0; + + /* Initialize MCP log */ + mutex_lock(&l_ctx.last_mcp_cmds_mutex); + ret = kasnprintf(buf, "%20s %5s %-13s %5s %-8s %6s %5s %s\n", + "CPU clock", "PID", "command", "S-ID", + "state", "result", "errno", "UUID"); + if (ret < 0) + goto out; + + cmd_info = &l_ctx.last_mcp_cmds[l_ctx.last_mcp_cmds_index]; + if (cmd_info->state != UNUSED) + /* Buffer has wrapped around, dump end (oldest records) */ + for (i = l_ctx.last_mcp_cmds_index; i < MCP_LOG_SIZE; i++) { + ret = show_mcp_log_entry(buf, cmd_info++); + if (ret < 0) + goto out; + } + + /* Dump first records */ + cmd_info = &l_ctx.last_mcp_cmds[0]; + for (i = 0; i < l_ctx.last_mcp_cmds_index; i++) { + ret = show_mcp_log_entry(buf, cmd_info++); + if (ret < 0) + goto out; + } + +out: + mutex_unlock(&l_ctx.last_mcp_cmds_mutex); + return ret; +} diff --git a/drivers/gud/MobiCoreDriver/mcp.h b/drivers/gud/MobiCoreDriver/mcp.h new file mode 100644 index 000000000000..42053430541a --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mcp.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef _MC_MCP_H_ +#define _MC_MCP_H_ + +#include "mci/mcloadformat.h" /* struct identity */ +#include "nq.h" + +/* Structure to hold the TA/driver descriptor to pass to MCP */ +struct tee_object { + u32 length; /* Total length */ + u32 header_length; /* Length of header before payload */ + u8 data[]; /* Header followed by payload */ +}; + +/* Structure to hold all mapped buffer data to pass to MCP */ +struct mcp_buffer_map { + u64 phys_addr; /** Page-aligned physical address */ + u32 secure_va; /** SWd virtual address */ + u32 offset; /** Data offset inside the first page */ + u32 length; /** Length of the data */ + u32 type; /** Type of MMU */ + u32 flags; /** Flags (typically read/write) */ +}; + +struct mcp_session { + /* Notification queue session (MUST BE FIRST) */ + struct nq_session nq_session; + /* Session ID */ + u32 sid; + /* Sessions list (protected by mcp sessions_lock) */ + struct list_head list; + /* Notification waiter lock */ + struct mutex notif_wait_lock; /* Only one at a time */ + /* Notification received */ + struct completion completion; + /* Notification lock */ + struct mutex exit_code_lock; + /* Last notification */ + s32 exit_code; + /* Session state (protected by mcp sessions_lock) */ + enum mcp_session_state { + MCP_SESSION_RUNNING, + MCP_SESSION_CLOSE_FAILED, + MCP_SESSION_CLOSED, + } state; +}; + +/* Init for the mcp_session structure */ +void mcp_session_init(struct mcp_session *session); +int mcp_session_waitnotif(struct mcp_session *session, s32 timeout, + bool silent_expiry); +s32 mcp_session_exitcode(struct mcp_session *mcp_session); + +/* Commands */ +int mcp_get_version(struct mc_version_info *version_info); +int mcp_load_token(uintptr_t data, const struct mcp_buffer_map *buffer_map); +int mcp_load_check(const struct tee_object *obj, + const struct mcp_buffer_map *buffer_map); +int mcp_open_session(struct mcp_session *session, + const struct tee_object *obj, + const struct mcp_buffer_map *map, + const struct mcp_buffer_map *tci_map); +int mcp_close_session(struct mcp_session *session); +void mcp_kill_session(struct mcp_session *session); +int mcp_map(u32 session_id, struct mcp_buffer_map *map); +int mcp_unmap(u32 session_id, const struct mcp_buffer_map *map); +int mcp_notify(struct mcp_session *mcp_session); + +/* Initialisation/cleanup */ +int mcp_init(void); +void mcp_exit(void); +int mcp_start(void); +void mcp_stop(void); + +/* Debug */ +int mcp_debug_sessions(struct kasnprintf_buf *buf); +int mcp_debug_mcpcmds(struct kasnprintf_buf *buf); + +#endif /* _MC_MCP_H_ */ diff --git a/drivers/gud/MobiCoreDriver/mmu.c b/drivers/gud/MobiCoreDriver/mmu.c new file mode 100644 index 000000000000..303cfded1f52 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mmu.c @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "public/mc_user.h" + +#include "mci/mcimcp.h" + +#include "platform.h" /* CONFIG_TRUSTONIC_TEE_LPAE */ +#include "main.h" +#include "mcp.h" /* mcp_buffer_map */ +#include "mmu.h" + +/* Common */ +#define MMU_BUFFERABLE BIT(2) /* AttrIndx[0] */ +#define MMU_CACHEABLE BIT(3) /* AttrIndx[1] */ +#define MMU_EXT_NG BIT(11) /* ARMv6 and higher */ + +/* LPAE */ +#define MMU_TYPE_PAGE (3 << 0) +#define MMU_NS BIT(5) +#define MMU_AP_RW_ALL BIT(6) /* AP[2:1], RW, at any privilege level */ +#define MMU_AP2_RO BIT(7) +#define MMU_EXT_SHARED_64 (3 << 8) /* SH[1:0], inner shareable */ +#define MMU_EXT_AF BIT(10) /* Access Flag */ +#define MMU_EXT_XN (((u64)1) << 54) /* XN */ + +/* Non-LPAE */ +#define MMU_TYPE_EXT (3 << 0) /* v5 */ +#define MMU_TYPE_SMALL (2 << 0) +#define MMU_EXT_AP0 BIT(4) +#define MMU_EXT_AP1 (2 << 4) +#define MMU_EXT_AP2 BIT(9) +#define MMU_EXT_TEX(x) ((x) << 6) /* v5 */ +#define MMU_EXT_SHARED_32 BIT(10) /* ARMv6 and higher */ + +/* ION */ +#define MMU_ION_BUF BIT(24) /* Trustonic Specific flag to detect ION mem */ + +/* + * MobiCore specific page tables for world shared memory. + * Linux uses shadow page tables, see arch/arm/include/asm/pgtable-2level. + * MobiCore uses the default ARM format. + * + * Number of page table entries in one L2 MMU table. This is ARM specific, an + * MMU table covers 1 MiB by using 256 entries referring to 4KiB pages each. + */ +#define L2_ENTRIES_MAX 256 + +/* + * Small buffers (below 1MiB) are mapped using the legacy L2 table, but bigger + * buffers now use a fake L1 table that holds 64-bit pointers to L2 tables. As + * this must be exactly one page, we can hold up to 512 entries. + */ +#define L1_ENTRIES_MAX 512 + +#if KERNEL_VERSION(4, 6, 0) > LINUX_VERSION_CODE +static inline long gup_local(struct mm_struct *mm, uintptr_t start, + unsigned long nr_pages, int write, + struct page **pages) +{ + return get_user_pages(NULL, mm, start, nr_pages, write, 0, pages, NULL); +} +#elif KERNEL_VERSION(4, 9, 0) > LINUX_VERSION_CODE +static inline long gup_local(struct mm_struct *mm, uintptr_t start, + unsigned long nr_pages, int write, + struct page **pages) +{ + unsigned int flags = 0; + + if (write) + flags |= FOLL_WRITE; + + return get_user_pages_remote(NULL, mm, start, nr_pages, write, 0, pages, + NULL); +} +#elif KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE +static inline long gup_local(struct mm_struct *mm, uintptr_t start, + unsigned long nr_pages, int write, + struct page **pages) +{ + unsigned int flags = 0; + + if (write) + flags |= FOLL_WRITE; + + return get_user_pages_remote(NULL, mm, start, nr_pages, flags, pages, + NULL); +} +#else +static inline long gup_local(struct mm_struct *mm, uintptr_t start, + unsigned long nr_pages, int write, + struct page **pages) +{ + unsigned int flags = 0; + + if (write) + flags |= FOLL_WRITE; + + return get_user_pages_remote(NULL, mm, start, nr_pages, flags, pages, + NULL, NULL); +} +#endif + +static inline long gup_local_repeat(struct mm_struct *mm, uintptr_t start, + unsigned long nr_pages, int write, + struct page **pages) +{ + int retries = 10; + long ret = 0; + + while (retries--) { + ret = gup_local(mm, start, nr_pages, write, pages); + + if (-EBUSY != ret) + break; + } + + return ret; +} + +/* + * Fake L1 MMU table. + */ +union l1_table { + u64 *pages_phys; /* Array of physical page addresses */ + unsigned long page; +}; + +/* + * L2 MMU table, which is more a L3 table in the LPAE case. + */ +union l2_table { + union { /* Array of PTEs */ + u32 *ptes_32; + u64 *ptes_64; + }; + unsigned long page; +}; + +/* + * MMU table allocated to the Daemon or a TLC describing a world shared + * buffer. + * When users map a malloc()ed area into SWd, a MMU table is allocated. + * In addition, the area of maximum 1MB virtual address space is mapped into + * the MMU table and a handle for this table is returned to the user. + */ +struct tee_mmu { + union l2_table l2_tables[L1_ENTRIES_MAX]; /* L2 tables */ + size_t l2_tables_nr; /* Actual number of L2 tables */ + union l1_table l1_table; /* Fake L1 table */ + union l2_table l1_l2_table; /* L2 table for the L1 table */ + u32 offset; + u32 length; + bool user; /* Pages are from user space */ + int pages_created; /* Leak check */ + int pages_locked; /* Leak check */ + /* ION case only */ + struct dma_buf *dma_buf; + struct dma_buf_attachment *attach; + struct sg_table *sgt; +}; + +/* + * Linux uses different mappings for SMP systems(the sharing flag is set for the + * pte. In order not to confuse things too much in Mobicore make sure the shared + * buffers have the same flags. This should also be done in SWD side. + */ + +static u64 pte_flags_64 = MMU_BUFFERABLE | MMU_CACHEABLE | MMU_EXT_NG | +#ifdef CONFIG_SMP + MMU_EXT_SHARED_64 | +#endif /* CONFIG_SMP */ + MMU_EXT_XN | MMU_EXT_AF | MMU_AP_RW_ALL | + MMU_NS | MMU_TYPE_PAGE; + +static u64 pte_flags_64_dma = MMU_EXT_NG | +#ifdef CONFIG_SMP + MMU_EXT_SHARED_64 | +#endif /* CONFIG_SMP */ + MMU_EXT_XN | MMU_EXT_AF | MMU_AP_RW_ALL | + MMU_NS | MMU_TYPE_PAGE; + +static u32 pte_flags_32 = MMU_BUFFERABLE | MMU_CACHEABLE | MMU_EXT_NG | +#ifdef CONFIG_SMP + MMU_EXT_SHARED_32 | MMU_EXT_TEX(1) | +#endif /* CONFIG_SMP */ + MMU_EXT_AP1 | MMU_EXT_AP0 | + MMU_TYPE_SMALL | MMU_TYPE_EXT; + +static u32 pte_flags_32_dma = MMU_EXT_NG | +#ifdef CONFIG_SMP + MMU_EXT_SHARED_32 | MMU_EXT_TEX(1) | +#endif /* CONFIG_SMP */ + MMU_EXT_AP1 | MMU_EXT_AP0 | + MMU_TYPE_SMALL | MMU_TYPE_EXT; + +static inline u32 get_pte_flags_32(bool is_writable) +{ + return is_writable ? pte_flags_32 : pte_flags_32 | MMU_EXT_AP2; +} + +static inline u32 get_pte_flags_32_dma(bool is_writable) +{ + return is_writable ? pte_flags_32_dma : pte_flags_32_dma | MMU_EXT_AP2; +} + +static inline u64 get_pte_flags_64(bool is_writable) +{ + return is_writable ? pte_flags_64 : pte_flags_64 | MMU_AP2_RO; +} + +static inline u64 get_pte_flags_64_dma(bool is_writable) +{ + return is_writable ? pte_flags_64_dma : pte_flags_64_dma | MMU_AP2_RO; +} + +static uintptr_t mmu_table_pointer(const struct tee_mmu *mmu) +{ + if (mmu->l1_table.page) { + return g_ctx.f_lpae ? + (uintptr_t)mmu->l1_l2_table.ptes_64 : + (uintptr_t)mmu->l1_l2_table.ptes_32; + } else { + return g_ctx.f_lpae ? + (uintptr_t)mmu->l2_tables[0].ptes_64 : + (uintptr_t)mmu->l2_tables[0].ptes_32; + } +} + +static void mmu_release(struct tee_mmu *mmu) +{ + size_t t; + +#ifdef CONFIG_DMA_SHARED_BUFFER + if (mmu->dma_buf) { + dma_buf_unmap_attachment(mmu->attach, mmu->sgt, + DMA_BIDIRECTIONAL); + dma_buf_detach(mmu->dma_buf, mmu->attach); + dma_buf_put(mmu->dma_buf); + } +#endif + + /* Release all locked user space pages */ + for (t = 0; t < mmu->l2_tables_nr; t++) { + union l2_table *l2_table = &mmu->l2_tables[t]; + + if (!l2_table->page) + break; + + if (mmu->user) { + u64 *pte64 = l2_table->ptes_64; + u32 *pte32 = l2_table->ptes_32; + pte_t pte; + int i; + + for (i = 0; i < L2_ENTRIES_MAX; i++) { +#if (KERNEL_VERSION(4, 7, 0) > LINUX_VERSION_CODE) || defined(CONFIG_ARM) + { + if (g_ctx.f_lpae) + pte = *pte64++; + else + pte = *pte32++; + } + + /* Unused entries are 0 */ + if (!pte) + break; +#else + { + if (g_ctx.f_lpae) + pte.pte = *pte64++; + else + pte.pte = *pte32++; + } + + /* Unused entries are 0 */ + if (!pte.pte) + break; +#endif + + /* pte_page() cannot return NULL */ + put_page(pte_page(pte)); + mmu->pages_locked--; + } + } + + free_page(l2_table->page); + mmu->pages_created--; + } + + if (mmu->l1_l2_table.page) { + free_page(mmu->l1_l2_table.page); + mmu->pages_created--; + } + + if (mmu->l1_table.page) { + free_page(mmu->l1_table.page); + mmu->pages_created--; + } + + if (mmu->pages_created || mmu->pages_locked) + mc_dev_err("leak detected: still in use %d, still locked %d", + mmu->pages_created, mmu->pages_locked); + + kfree(mmu); + + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_mmus); +} + +static bool mmu_get_dma_buffer(struct tee_mmu *mmu, int va) +{ +#ifdef CONFIG_DMA_SHARED_BUFFER + struct dma_buf *buf; + + buf = dma_buf_get(va); + if (IS_ERR(buf)) + return false; + + mmu->dma_buf = buf; + mmu->attach = dma_buf_attach(mmu->dma_buf, g_ctx.mcd); + if (IS_ERR(mmu->attach)) + goto err_attach; + + mmu->sgt = dma_buf_map_attachment(mmu->attach, DMA_BIDIRECTIONAL); + if (IS_ERR(mmu->sgt)) + goto err_map; + + return true; + +err_map: + dma_buf_detach(mmu->dma_buf, mmu->attach); + +err_attach: + dma_buf_put(mmu->dma_buf); +#endif + return false; +} + +/* + * Allocate MMU table and map buffer into it. + * That is, create respective table entries. + */ +struct tee_mmu *tee_mmu_create(struct mm_struct *mm, + const struct mc_ioctl_buffer *buf) +{ + struct tee_mmu *mmu; + const void *data = (const void *)(uintptr_t)buf->va; + const void *reader = (const void *)((uintptr_t)data & PAGE_MASK); + struct page **pages; /* Same as above, conveniently typed */ + unsigned long pages_page = 0; /* Page to contain the page pointers */ + size_t chunk; + unsigned long total_pages_nr; + int l1_entries_max; + int ret = 0; + int write = (buf->flags & MC_IO_MAP_OUTPUT) != 0; + +#ifndef CONFIG_DMA_SHARED_BUFFER + if (buf->flags & MMU_ION_BUF) { + mc_dev_err("ION buffers not supported by kernel"); + return ERR_PTR(-EINVAL); + } +#endif + + /* Check input arguments */ + if (!(buf->flags & MMU_ION_BUF) && !buf->va) + return ERR_PTR(-EINVAL); + + /* Allocate the struct */ + mmu = kzalloc(sizeof(*mmu), GFP_KERNEL); + if (!mmu) + return ERR_PTR(-ENOMEM); + + /* Increment debug counter */ + atomic_inc(&g_ctx.c_mmus); + + /* Check that we have enough space to map data */ + mmu->length = buf->len; + if (buf->flags & MMU_ION_BUF) { + /* Buffer is ION - + * va is the client's dma_buf fd, which should be converted + * to a struct sg_table * directly. + */ + if (!mmu_get_dma_buffer(mmu, buf->va)) { + mc_dev_err("mmu_get_dma_buffer failed"); + ret = -EINVAL; + goto end; + } + + mmu->offset = (u32)(mmu->sgt->sgl->offset & ~PAGE_MASK); + } else { + mmu->offset = (u32)((uintptr_t)data & ~PAGE_MASK); + } + + total_pages_nr = PAGE_ALIGN(mmu->offset + mmu->length) / PAGE_SIZE; + if (g_ctx.f_mem_ext) + l1_entries_max = L1_ENTRIES_MAX; + else + l1_entries_max = 1; + + if (total_pages_nr > (l1_entries_max * L2_ENTRIES_MAX)) { + mc_dev_err("data mapping exceeds %d pages", + l1_entries_max * L2_ENTRIES_MAX); + ret = -EINVAL; + goto end; + } + + /* Get number of L2 tables needed */ + mmu->l2_tables_nr = (total_pages_nr + L2_ENTRIES_MAX - 1) / + L2_ENTRIES_MAX; + mc_dev_devel("total_pages_nr %lu l2_tables_nr %zu", + total_pages_nr, mmu->l2_tables_nr); + + /* Get a page to store page pointers */ + pages_page = get_zeroed_page(GFP_KERNEL); + if (!pages_page) { + ret = -ENOMEM; + goto end; + } + mmu->pages_created++; + + pages = (struct page **)pages_page; + + /* Allocate a page for the L1 table */ + if (mmu->l2_tables_nr > 1) { + mmu->l1_table.page = get_zeroed_page(GFP_KERNEL); + if (!mmu->l1_table.page) { + ret = -ENOMEM; + goto end; + } + mmu->pages_created++; + + mmu->l1_l2_table.page = get_zeroed_page(GFP_KERNEL); + if (!mmu->l1_l2_table.page) { + ret = -ENOMEM; + goto end; + } + mmu->pages_created++; + + /* Map it */ + if (g_ctx.f_lpae) { + u64 *pte; + + pte = &mmu->l1_l2_table.ptes_64[0]; + *pte = virt_to_phys(mmu->l1_table.pages_phys); + *pte |= get_pte_flags_64(write); + } else { + u32 *pte; + + pte = &mmu->l1_l2_table.ptes_32[0]; + *pte = virt_to_phys(mmu->l1_table.pages_phys); + *pte |= get_pte_flags_32(write); + } + } + + for (chunk = 0; chunk < mmu->l2_tables_nr; chunk++) { + unsigned long pages_nr, i; + struct page **page_ptr; + + /* Size to map for this chunk */ + if (chunk == (mmu->l2_tables_nr - 1)) + pages_nr = ((total_pages_nr - 1) % L2_ENTRIES_MAX) + 1; + else + pages_nr = L2_ENTRIES_MAX; + + /* Allocate a page for the MMU descriptor */ + mmu->l2_tables[chunk].page = get_zeroed_page(GFP_KERNEL); + if (!mmu->l2_tables[chunk].page) { + ret = -ENOMEM; + goto end; + } + mmu->pages_created++; + + /* Add page address to L1 table if needed */ + if (mmu->l1_table.page) { + void *table; + + if (g_ctx.f_lpae) + table = mmu->l2_tables[chunk].ptes_64; + else + table = mmu->l2_tables[chunk].ptes_32; + + mmu->l1_table.pages_phys[chunk] = virt_to_phys(table); + } + + /* Get pages */ + if (mmu->dma_buf) { + /* Buffer is ION */ + struct sg_mapping_iter miter; + + page_ptr = &pages[0]; + sg_miter_start(&miter, mmu->sgt->sgl, + mmu->sgt->nents, + SG_MITER_FROM_SG); + while (sg_miter_next(&miter)) + *page_ptr++ = miter.page; + + sg_miter_stop(&miter); + } else if (mm) { + long gup_ret; + + /* Buffer was allocated in user space */ + down_read(&mm->mmap_sem); + gup_ret = gup_local_repeat(mm, (uintptr_t)reader, + pages_nr, 1, pages); + if ((gup_ret == -EFAULT) && !write) { + gup_ret = gup_local_repeat(mm, + (uintptr_t)reader, + pages_nr, 0, pages); + } + up_read(&mm->mmap_sem); + if (gup_ret < 0) { + ret = gup_ret; + mc_dev_err("failed to get user pages @%p: %d", + reader, ret); + goto end; + } + + /* check if we could lock all pages. */ + if (gup_ret != pages_nr) { + mc_dev_err("failed to get user pages: %ld", + gup_ret); + release_pages(pages, gup_ret, 0); + ret = -EINVAL; + goto end; + } + + reader += pages_nr * PAGE_SIZE; + mmu->user = true; + mmu->pages_locked += pages_nr; + } else if (is_vmalloc_addr(data)) { + /* Buffer vmalloc'ed in kernel space */ + page_ptr = &pages[0]; + for (i = 0; i < pages_nr; i++) { + struct page *page = vmalloc_to_page(reader); + + if (!page) { + mc_dev_err("failed to map address"); + ret = -EINVAL; + goto end; + } + + *page_ptr++ = page; + reader += PAGE_SIZE; + } + } else { + /* Buffer kmalloc'ed in kernel space */ + struct page *page = virt_to_page(reader); + + reader += pages_nr * PAGE_SIZE; + page_ptr = &pages[0]; + for (i = 0; i < pages_nr; i++) + *page_ptr++ = page++; + } + + /* Create MMU Table entries */ + page_ptr = &pages[0]; + + /* + * Create MMU table entry, see ARM MMU docu for details about + * flags stored in the lowest 12 bits. As a side reference, the + * Article "ARM's multiply-mapped memory mess" found in the + * collection at http://lwn.net/Articles/409032/ is also worth + * reading. + */ + if (g_ctx.f_lpae) { + u64 *pte = &mmu->l2_tables[chunk].ptes_64[0]; + + for (i = 0; i < pages_nr; i++, page_ptr++, pte++) { + *pte = page_to_phys(*page_ptr); + if (mmu->dma_buf) + *pte |= get_pte_flags_64_dma(write); + else + *pte |= get_pte_flags_64(write); + } + } else { + u32 *pte = &mmu->l2_tables[chunk].ptes_32[0]; + + for (i = 0; i < pages_nr; i++, page_ptr++, pte++) { + unsigned long phys = page_to_phys(*page_ptr); +#if defined CONFIG_ARM64 + if (phys & 0xffffffff00000000UL) { + mc_dev_err("64-bit pointer: 0x%16lx", + phys); + ret = -EFAULT; + goto end; + } +#endif + *pte = (u32)phys; + if (mmu->dma_buf) + *pte |= get_pte_flags_32_dma(write); + else + *pte |= get_pte_flags_32(write); + } + } + } + +end: + if (pages_page) { + free_page(pages_page); + mmu->pages_created--; + } + + if (ret) { + mmu_release(mmu); + return ERR_PTR(ret); + } + + mc_dev_devel("created mmu %p: %s va %llx len %u off %u L%d table %lx", + mmu, mmu->user ? "user" : "kernel", buf->va, mmu->length, + mmu->offset, mmu->l1_table.page ? 1 : 2, + mmu_table_pointer(mmu)); + return mmu; +} + +void tee_mmu_delete(struct tee_mmu *mmu) +{ + if (!mmu) + return; + + mc_dev_devel("free mmu %p: %s len %u off %u L%d table %lx", + mmu, mmu->user ? "user" : "kernel", mmu->length, + mmu->offset, mmu->l1_table.page ? 1 : 2, + mmu_table_pointer(mmu)); + mmu_release(mmu); +} + +bool client_mmu_matches(const struct tee_mmu *left, + const struct tee_mmu *right) +{ + const void *left_page = left->l2_tables[0].ptes_32; + const void *right_page = right->l2_tables[0].ptes_32; + bool ret; + + /* L1 not supported */ + if (left->l1_table.page || right->l1_table.page) + return false; + + /* Only need to compare contents of L2 page */ + ret = !memcmp(left_page, right_page, PAGE_SIZE); + mc_dev_devel("MMU tables virt %p and %p %smatch", left, right, + ret ? "" : "do not "); + return ret; +} + +void tee_mmu_buffer(const struct tee_mmu *mmu, struct mcp_buffer_map *map) +{ + uintptr_t table = mmu_table_pointer(mmu); + + map->phys_addr = virt_to_phys((void *)table); + map->secure_va = 0; + map->offset = mmu->offset; + map->length = mmu->length; + map->flags = MC_IO_MAP_INPUT | MC_IO_MAP_OUTPUT; + if (mmu->l1_table.page) + map->type = WSM_L1; + else + map->type = WSM_L2; +} + +int tee_mmu_debug_structs(struct kasnprintf_buf *buf, const struct tee_mmu *mmu) +{ + return kasnprintf(buf, + "\t\t\tmmu %pK: %s len %u off %u table %pK type L%d\n" + , mmu, mmu->user ? "user" : "kernel", mmu->length, + mmu->offset, (void *)mmu_table_pointer(mmu), + mmu->l1_table.page ? 1 : 2); +} diff --git a/drivers/gud/MobiCoreDriver/mmu.h b/drivers/gud/MobiCoreDriver/mmu.h new file mode 100644 index 000000000000..62cef294d0b3 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/mmu.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef _TBASE_MEM_H_ +#define _TBASE_MEM_H_ + +struct tee_mmu; +struct mcp_buffer_map; + +/* + * Allocate MMU table and map buffer into it. + * That is, create respective table entries. + */ +struct tee_mmu *tee_mmu_create(struct mm_struct *mm, + const struct mc_ioctl_buffer *buf); + +/* + * Delete a used MMU table. + */ +void tee_mmu_delete(struct tee_mmu *mmu); + +/* + * Compare physical addresses from two MMU tables. + */ +bool client_mmu_matches(const struct tee_mmu *left, + const struct tee_mmu *right); + +/* + * Fill in buffer info for MMU table. + */ +void tee_mmu_buffer(const struct tee_mmu *mmu, struct mcp_buffer_map *map); + +/* + * Add info to debug buffer. + */ +int tee_mmu_debug_structs(struct kasnprintf_buf *buf, + const struct tee_mmu *mmu); + +#endif /* _TBASE_MEM_H_ */ diff --git a/drivers/gud/MobiCoreDriver/nq.c b/drivers/gud/MobiCoreDriver/nq.c new file mode 100644 index 000000000000..122f678a05ea --- /dev/null +++ b/drivers/gud/MobiCoreDriver/nq.c @@ -0,0 +1,722 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include +#include +#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE +#include /* local_clock */ +#endif + +#include "public/mc_user.h" + +#include "mci/mcifc.h" +#include "mci/mciiwp.h" +#include "mci/mcimcp.h" +#include "mci/mcinq.h" +#include "mci/mcitime.h" /* struct mcp_time */ +#include "main.h" +#include "fastcall.h" +#include "nq.h" + +#define NQ_NUM_ELEMS 64 + +static struct { + struct mutex buffer_mutex; /* Lock on SWd communication buffer */ + struct mcp_buffer *mcp_buffer; + struct interworld_session *iwp_buffer; + struct task_struct *irq_bh_thread; + struct completion irq_bh_complete; + bool irq_bh_active; + int irq; + int (*scheduler_cb)(enum nq_scheduler_commands); + void (*crash_handler_cb)(void); + void (*mcp_notif_handler)(u32 id, u32 payload); + void (*iwp_notif_handler)(u32 id, u32 payload); + /* MobiCore MCI information */ + unsigned int order; + union { + void *base; + struct { + struct notification_queue *tx; + struct notification_queue *rx; + } nq; + }; + /* + * This notifications list is to be used to queue notifications when the + * notification queue overflows, so no session gets its notification + * lost, especially MCP. + */ + struct mutex notifications_mutex; + struct list_head notifications; + /* Dump buffer */ + struct kasnprintf_buf dump; + /* Time */ + struct mcp_time *time; +} l_ctx; + +static inline bool is_iwp_id(u32 id) +{ + return (id & SID_IWP_NOTIFICATION) != 0; +} + +/* + * Notification Queue overflow management: + * - once the SWd NQ is full, sessions get added to the overflow queue: + * 'l_ctx.notifications' + * - as long as this queue is not empty, new notifications get added there + * first, if not already present, then the queue is flushed + * - the queue is also flushed by the scheduler once the SWd has run + */ +static inline bool notif_queue_full(void) +{ + struct notification_queue *tx = l_ctx.nq.tx; + + return (tx->hdr.write_cnt - tx->hdr.read_cnt) == tx->hdr.queue_size; +} + +static inline void notif_queue_push(u32 session_id, u32 payload) +{ + struct notification_queue_header *hdr = &l_ctx.nq.tx->hdr; + u32 i = hdr->write_cnt % hdr->queue_size; + + l_ctx.nq.tx->notification[i].session_id = session_id; + l_ctx.nq.tx->notification[i].payload = payload; + /* + * Ensure notification[] is written before we update the counter + * We want a ARM dmb() / ARM64 dmb(sy) here + */ + smp_mb(); + + hdr->write_cnt++; + /* + * Ensure write_cnt is written before new notification + * We want a ARM dsb() / ARM64 dsb(sy) here + */ + rmb(); +} + +static inline bool nq_notifications_flush_nolock(void) +{ + bool flushed = false; + + while (!list_empty(&l_ctx.notifications) && !notif_queue_full()) { + struct nq_session *session; + + session = list_first_entry(&l_ctx.notifications, + struct nq_session, list); + mc_dev_devel("pop %x", session->id); + notif_queue_push(session->id, session->payload); + nq_session_state_update(session, NQ_NOTIF_SENT); + list_del_init(&session->list); + flushed = true; + } + + return flushed; +} + +bool nq_notifications_flush(void) +{ + bool flushed = false; + + mutex_lock(&l_ctx.notifications_mutex); + flushed = nq_notifications_flush_nolock(); + mutex_unlock(&l_ctx.notifications_mutex); + return flushed; +} + +int nq_session_notify(struct nq_session *session, u32 id, u32 payload) +{ + int ret = 0; + + if (!l_ctx.scheduler_cb) + return -EAGAIN; + + mutex_lock(&l_ctx.notifications_mutex); + session->id = id; + session->payload = payload; + if (!list_empty(&l_ctx.notifications) || notif_queue_full()) { + if (!list_empty(&session->list)) { + if (payload != session->payload) { + mc_dev_err("skip %x payload change %x -> %x", + session->id, session->payload, + payload); + } else { + mc_dev_devel("skip %x payload %x", + session->id, payload); + } + ret = -EAGAIN; + } else { + mc_dev_devel("push %x payload %x", session->id, + payload); + /* session->payload = payload; */ + list_add_tail(&session->list, &l_ctx.notifications); + nq_session_state_update(session, NQ_NOTIF_QUEUED); + } + + nq_notifications_flush_nolock(); + + if (l_ctx.scheduler_cb(MC_NQ_YIELD)) + ret = -EPROTO; + } else { + mc_dev_devel("send %x payload %x", session->id, payload); + notif_queue_push(session->id, payload); + nq_session_state_update(session, NQ_NOTIF_SENT); + if (l_ctx.scheduler_cb(MC_NQ_NSIQ)) + ret = -EPROTO; + } + + mutex_unlock(&l_ctx.notifications_mutex); + return ret; +} + +void nq_update_time(void) +{ + struct timespec tm; + + getnstimeofday(&tm); + l_ctx.time->wall_clock_seconds = tm.tv_sec; + l_ctx.time->wall_clock_nsec = tm.tv_nsec; + if (g_ctx.f_monotonic_time) { + getrawmonotonic(&tm); + l_ctx.time->monotonic_seconds = tm.tv_sec; + l_ctx.time->monotonic_nsec = tm.tv_nsec; + } +} + +union mcp_message *nq_get_mcp_message(void) +{ + return &l_ctx.mcp_buffer->message; +} + +struct interworld_session *nq_get_iwp_buffer(void) +{ + return l_ctx.iwp_buffer; +} + +void nq_register_notif_handler(void (*handler)(u32 id, u32 payload), bool iwp) +{ + if (iwp) + l_ctx.iwp_notif_handler = handler; + else + l_ctx.mcp_notif_handler = handler; +} + +static inline void nq_notif_handler(u32 id, u32 payload) +{ + mc_dev_devel("NQ notif for id %x payload %x", id, payload); + if (is_iwp_id(id)) + l_ctx.iwp_notif_handler(id, payload); + else + l_ctx.mcp_notif_handler(id, payload); +} + +static int irq_bh_worker(void *arg) +{ + struct notification_queue *rx = l_ctx.nq.rx; + + while (l_ctx.irq_bh_active) { + wait_for_completion_killable(&l_ctx.irq_bh_complete); + + /* Deal with all pending notifications in one go */ + while ((rx->hdr.write_cnt - rx->hdr.read_cnt) > 0) { + struct notification nf; + + nf = rx->notification[ + rx->hdr.read_cnt % rx->hdr.queue_size]; + + /* + * Ensure read_cnt writing happens after buffer read + * We want a ARM dmb() / ARM64 dmb(sy) here + */ + smp_mb(); + rx->hdr.read_cnt++; + /* + * Ensure read_cnt writing finishes before reader + * We want a ARM dsb() / ARM64 dsb(sy) here + */ + rmb(); + nq_notif_handler(nf.session_id, nf.payload); + } + + /* + * Finished processing notifications. It does not matter whether + * there actually were any notification or not. S-SIQs can also + * be triggered by an SWd driver which was waiting for a FIQ. + * In this case the S-SIQ tells NWd that SWd is no longer idle + * an will need scheduling again. + */ + if (l_ctx.scheduler_cb) + l_ctx.scheduler_cb(MC_NQ_NSIQ); + } + return 0; +} + +static irqreturn_t irq_handler(int intr, void *arg) +{ + /* wake up thread to continue handling this interrupt */ + complete(&l_ctx.irq_bh_complete); + return IRQ_HANDLED; +} + +void nq_session_init(struct nq_session *session, bool is_gp) +{ + session->id = SID_INVALID; + session->payload = 0; + INIT_LIST_HEAD(&session->list); + mutex_init(&session->mutex); + session->state = NQ_NOTIF_IDLE; + session->cpu_clk = 0; + session->is_gp = is_gp; +} + +bool nq_session_is_gp(const struct nq_session *session) +{ + return session->is_gp; +} + +u64 nq_session_notif_cpu_clk(const struct nq_session *session) +{ + return session->cpu_clk; +} + +void nq_session_exit(struct nq_session *session) +{ + mutex_lock(&l_ctx.notifications_mutex); + list_del(&session->list); + mutex_unlock(&l_ctx.notifications_mutex); +} + +void nq_session_state_update(struct nq_session *session, + enum nq_notif_state state) +{ + mutex_lock(&session->mutex); + session->state = state; + session->cpu_clk = local_clock(); + mutex_unlock(&session->mutex); +} + +const char *nq_session_state_string(struct nq_session *session) +{ + switch (session->state) { + case NQ_NOTIF_IDLE: + return "idle"; + case NQ_NOTIF_QUEUED: + return "queued"; + case NQ_NOTIF_SENT: + return "sent"; + case NQ_NOTIF_RECEIVED: + return "received"; + case NQ_NOTIF_CONSUMED: + return "consumed"; + case NQ_NOTIF_DEAD: + return "dead"; + } + return "error"; +} + +static ssize_t debug_crashdump_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + if (l_ctx.dump.off) + return simple_read_from_buffer(user_buf, count, ppos, + l_ctx.dump.buf, l_ctx.dump.off); + + return 0; +} + +static const struct file_operations mc_debug_crashdump_ops = { + .read = debug_crashdump_read, + .llseek = default_llseek, +}; + +static ssize_t debug_smclog_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + return debug_generic_read(file, user_buf, count, ppos, + mc_fastcall_debug_smclog); +} + +static const struct file_operations mc_debug_smclog_ops = { + .read = debug_smclog_read, + .llseek = default_llseek, + .open = debug_generic_open, + .release = debug_generic_release, +}; + +void nq_dump_status(void) +{ + static const struct { + unsigned int index; + const char *msg; + } status_map[] = { + /**< MobiCore control flags */ + { MC_EXT_INFO_ID_FLAGS, "flags"}, + /**< MobiCore halt condition code */ + { MC_EXT_INFO_ID_HALT_CODE, "haltCode"}, + /**< MobiCore halt condition instruction pointer */ + { MC_EXT_INFO_ID_HALT_IP, "haltIp"}, + /**< MobiCore fault counter */ + { MC_EXT_INFO_ID_FAULT_CNT, "faultRec.cnt"}, + /**< MobiCore last fault cause */ + { MC_EXT_INFO_ID_FAULT_CAUSE, "faultRec.cause"}, + /**< MobiCore last fault meta */ + { MC_EXT_INFO_ID_FAULT_META, "faultRec.meta"}, + /**< MobiCore last fault threadid */ + { MC_EXT_INFO_ID_FAULT_THREAD, "faultRec.thread"}, + /**< MobiCore last fault instruction pointer */ + { MC_EXT_INFO_ID_FAULT_IP, "faultRec.ip"}, + /**< MobiCore last fault stack pointer */ + { MC_EXT_INFO_ID_FAULT_SP, "faultRec.sp"}, + /**< MobiCore last fault ARM arch information */ + { MC_EXT_INFO_ID_FAULT_ARCH_DFSR, "faultRec.arch.dfsr"}, + /**< MobiCore last fault ARM arch information */ + { MC_EXT_INFO_ID_FAULT_ARCH_ADFSR, "faultRec.arch.adfsr"}, + /**< MobiCore last fault ARM arch information */ + { MC_EXT_INFO_ID_FAULT_ARCH_DFAR, "faultRec.arch.dfar"}, + /**< MobiCore last fault ARM arch information */ + { MC_EXT_INFO_ID_FAULT_ARCH_IFSR, "faultRec.arch.ifsr"}, + /**< MobiCore last fault ARM arch information */ + { MC_EXT_INFO_ID_FAULT_ARCH_AIFSR, "faultRec.arch.aifsr"}, + /**< MobiCore last fault ARM arch information */ + { MC_EXT_INFO_ID_FAULT_ARCH_IFAR, "faultRec.arch.ifar"}, + /**< MobiCore configured by Daemon via fc_init flag */ + { MC_EXT_INFO_ID_MC_CONFIGURED, "mcData.flags"}, + /**< MobiCore exception handler last partner */ + { MC_EXT_INFO_ID_MC_EXC_PARTNER, "mcExcep.partner"}, + /**< MobiCore exception handler last peer */ + { MC_EXT_INFO_ID_MC_EXC_IPCPEER, "mcExcep.peer"}, + /**< MobiCore exception handler last IPC message */ + { MC_EXT_INFO_ID_MC_EXC_IPCMSG, "mcExcep.cause"}, + /**< MobiCore exception handler last IPC data */ + {MC_EXT_INFO_ID_MC_EXC_IPCDATA, "mcExcep.meta"}, + }; + + char uuid_str[33]; + int ret = 0; + size_t i; + + if (l_ctx.dump.off) + ret = -EBUSY; + + mc_dev_err("TEE halted. Status dump:"); + for (i = 0; i < (size_t)ARRAY_SIZE(status_map); i++) { + u32 info; + + if (!mc_fc_info(status_map[i].index, NULL, &info)) { + mc_dev_err(" %-20s= 0x%08x", status_map[i].msg, info); + if (ret >= 0) + ret = kasnprintf(&l_ctx.dump, "%-20s= 0x%08x\n", + status_map[i].msg, info); + } + } + + /* construct UUID string */ + for (i = 0; i < 4; i++) { + u32 info; + size_t j; + + if (mc_fc_info(MC_EXT_INFO_ID_MC_EXC_UUID + i, NULL, &info)) + return; + + for (j = 0; j < sizeof(info); j++) { + snprintf(&uuid_str[(i * sizeof(info) + j) * 2], 3, + "%02x", (info >> (j * 8)) & 0xff); + } + } + + mc_dev_err(" %-20s= 0x%s", "mcExcep.uuid", uuid_str); + if (ret >= 0) + ret = kasnprintf(&l_ctx.dump, "%-20s= 0x%s\n", "mcExcep.uuid", + uuid_str); + + if (ret < 0) { + kfree(l_ctx.dump.buf); + l_ctx.dump.off = 0; + return; + } + + debugfs_create_file("crashdump", 0400, g_ctx.debug_dir, NULL, + &mc_debug_crashdump_ops); + debugfs_create_file("last_smc_commands", 0400, g_ctx.debug_dir, NULL, + &mc_debug_smclog_ops); + if (l_ctx.crash_handler_cb) + l_ctx.crash_handler_cb(); +} + +void nq_register_scheduler(int (*scheduler_cb)(enum nq_scheduler_commands)) +{ + l_ctx.scheduler_cb = scheduler_cb; +} + +void nq_register_crash_handler(void (*crash_handler_cb)(void)) +{ + l_ctx.crash_handler_cb = crash_handler_cb; +} + +static inline int set_sleep_mode_rq(u16 sleep_req) +{ + mutex_lock(&l_ctx.buffer_mutex); + l_ctx.mcp_buffer->flags.sleep_mode.sleep_req = sleep_req; + mutex_unlock(&l_ctx.buffer_mutex); + return 0; +} + +int nq_suspend(void) +{ + return set_sleep_mode_rq(MC_FLAG_REQ_TO_SLEEP); +} + +int nq_resume(void) +{ + return set_sleep_mode_rq(MC_FLAG_NO_SLEEP_REQ); +} + +bool nq_suspended(void) +{ + struct mcp_flags *flags = &l_ctx.mcp_buffer->flags; + bool ret; + + mutex_lock(&l_ctx.buffer_mutex); + ret = flags->sleep_mode.ready_to_sleep & MC_STATE_READY_TO_SLEEP; + if (!ret) { + mc_dev_devel("IDLE=%d", flags->schedule); + mc_dev_devel("Request Sleep=%d", flags->sleep_mode.sleep_req); + mc_dev_devel("Sleep Ready=%d", + flags->sleep_mode.ready_to_sleep); + } + + mutex_unlock(&l_ctx.buffer_mutex); + return ret; +} + +bool nq_get_idle_timeout(s32 *timeout) +{ + u32 schedule; + bool ret; + + mutex_lock(&l_ctx.buffer_mutex); + schedule = l_ctx.mcp_buffer->flags.schedule; + if (schedule == MC_FLAG_SCHEDULE_IDLE) { + if (g_ctx.f_timeout) + *timeout = l_ctx.mcp_buffer->flags.timeout_ms; + else + *timeout = -1; + + ret = true; + } else { + ret = false; + } + + mutex_unlock(&l_ctx.buffer_mutex); + return ret; +} + +void nq_reset_idle_timeout(void) +{ + mutex_lock(&l_ctx.buffer_mutex); + l_ctx.mcp_buffer->flags.timeout_ms = -1; + mutex_unlock(&l_ctx.buffer_mutex); +} + +int nq_start(void) +{ + size_t q_len = ALIGN(2 * (sizeof(struct notification_queue_header) + + NQ_NUM_ELEMS * sizeof(struct notification)), 4); + int ret; + struct irq_data *irq_d; + + /* Make sure we have an interrupt number before going on */ +#if defined(CONFIG_OF) + l_ctx.irq = irq_of_parse_and_map(g_ctx.mcd->of_node, 0); +#endif +#if defined(MC_INTR_SSIQ) + if (l_ctx.irq <= 0) + l_ctx.irq = MC_INTR_SSIQ; +#endif + + if (l_ctx.irq <= 0) { + mc_dev_err("No IRQ number, aborting"); + return -EINVAL; + } + + /* + * Initialize the time structure for SWd + * At this stage, we don't know if the SWd needs to get the REE time and + * we set it anyway. + */ + nq_update_time(); + + /* Call the INIT fastcall to setup shared buffers */ + ret = mc_fc_init( + virt_to_phys(l_ctx.base), + (uintptr_t)l_ctx.mcp_buffer - (uintptr_t)l_ctx.base, + q_len, + sizeof(*l_ctx.mcp_buffer)); + + if (ret) + return ret; + + /* Set initialization values */ +#if defined(MC_INTR_SSIQ_SWD) + l_ctx.mcp_buffer->message.init_values.flags |= MC_IV_FLAG_IRQ; + l_ctx.mcp_buffer->message.init_values.irq = MC_INTR_SSIQ_SWD; +#endif + l_ctx.mcp_buffer->message.init_values.flags |= MC_IV_FLAG_TIME; + irq_d = irq_get_irq_data(l_ctx.irq); + if (irq_d) + l_ctx.mcp_buffer->message.init_values.irq = irq_d->hwirq; + l_ctx.mcp_buffer->message.init_values.time_ofs = + (u32)((uintptr_t)l_ctx.time - (uintptr_t)l_ctx.base); + l_ctx.mcp_buffer->message.init_values.time_len = + sizeof(*l_ctx.time); + + l_ctx.mcp_buffer->message.init_values.flags |= MC_IV_FLAG_IWP; + l_ctx.mcp_buffer->message.init_values.iws_buf_ofs = + (u64)((uintptr_t)l_ctx.iwp_buffer - (uintptr_t)l_ctx.base); + l_ctx.mcp_buffer->message.init_values.iws_buf_size = + MAX_IW_SESSION * sizeof(struct interworld_session); + + /* First empty N-SIQ to setup of the MCI structure */ + ret = mc_fc_nsiq(); + if (ret) + return ret; + + /* + * Wait until the TEE state switches to MC_STATUS_INITIALIZED + * It is assumed that it always switches state at some point + */ + do { + u32 status = 0; + u32 timeslot; + + ret = mc_fc_info(MC_EXT_INFO_ID_MCI_VERSION, &status, NULL); + if (ret) + return ret; + + switch (status) { + case MC_STATUS_NOT_INITIALIZED: + /* Switch to the TEE to give it more CPU time. */ + ret = EAGAIN; + for (timeslot = 0; timeslot < 10; timeslot++) { + int tmp_ret = mc_fc_yield(); + + if (tmp_ret) + return tmp_ret; + } + + /* No need to loop like mad */ + if (ret == EAGAIN) + usleep_range(100, 500); + + break; + case MC_STATUS_HALT: + nq_dump_status(); + mc_dev_err("halt during init, state 0x%x", status); + return -ENODEV; + case MC_STATUS_INITIALIZED: + mc_dev_devel("ready"); + break; + default: + /* MC_STATUS_BAD_INIT or anything else */ + mc_dev_err("MCI init failed, state 0x%x", status); + return -EIO; + } + } while (ret == EAGAIN); + + /* Set up S-SIQ interrupt handler and its bottom-half */ + l_ctx.irq_bh_active = true; + l_ctx.irq_bh_thread = kthread_run(irq_bh_worker, NULL, "tee_irq_bh"); + if (IS_ERR(l_ctx.irq_bh_thread)) { + mc_dev_err("irq_bh_worker thread creation failed"); + return PTR_ERR(l_ctx.irq_bh_thread); + } + return request_irq(l_ctx.irq, irq_handler, IRQF_TRIGGER_RISING, + "trustonic", NULL); +} + +void nq_stop(void) +{ + free_irq(l_ctx.irq, NULL); + l_ctx.irq_bh_active = false; + kthread_stop(l_ctx.irq_bh_thread); +} + +int nq_init(void) +{ + size_t q_len, buf_len; + unsigned long mci; + + mutex_init(&l_ctx.buffer_mutex); + init_completion(&l_ctx.irq_bh_complete); + /* Setup notification queue mutex */ + mutex_init(&l_ctx.notifications_mutex); + INIT_LIST_HEAD(&l_ctx.notifications); + + /* NQ_NUM_ELEMS must be power of 2 */ + q_len = ALIGN(2 * (sizeof(struct notification_queue_header) + + NQ_NUM_ELEMS * sizeof(struct notification)), 4); + + buf_len = q_len + + sizeof(*l_ctx.time) + + sizeof(*l_ctx.mcp_buffer) + + MAX_IW_SESSION * sizeof(struct interworld_session); + + l_ctx.order = get_order(buf_len); + + mci = __get_free_pages(GFP_USER | __GFP_ZERO, l_ctx.order); + if (!mci) + return -ENOMEM; + + l_ctx.nq.tx = (struct notification_queue *)mci; + l_ctx.nq.tx->hdr.queue_size = NQ_NUM_ELEMS; + mci += sizeof(struct notification_queue_header) + + l_ctx.nq.tx->hdr.queue_size * sizeof(struct notification); + + l_ctx.nq.rx = (struct notification_queue *)mci; + l_ctx.nq.rx->hdr.queue_size = NQ_NUM_ELEMS; + mci += sizeof(struct notification_queue_header) + + l_ctx.nq.rx->hdr.queue_size * sizeof(struct notification); + + l_ctx.mcp_buffer = (void *)ALIGN(mci, 8); + mci += sizeof(struct mcp_buffer); + + /* interworld_buffer contains: + * MAX_IW_SESSION session, and for each session S(i), we could have + * D(i) extra data, NB: D(i) could be different from D(j) + * + * v0: D(i) = 0 + */ + /* mci should be already 8 bytes aligned */ + l_ctx.iwp_buffer = (void *)ALIGN(mci, 8); + if ((unsigned long)l_ctx.iwp_buffer != mci) + mc_dev_err("ERROR: iws buffer is not aligned"); + + mci += MAX_IW_SESSION * sizeof(struct interworld_session); + + l_ctx.time = (void *)ALIGN(mci, 8); + return 0; +} + +void nq_exit(void) +{ + if (l_ctx.dump.off) + kfree(l_ctx.dump.buf); + free_pages((unsigned long)l_ctx.base, l_ctx.order); +} diff --git a/drivers/gud/MobiCoreDriver/nq.h b/drivers/gud/MobiCoreDriver/nq.h new file mode 100644 index 000000000000..115c044bd6c1 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/nq.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef _MC_NQ_H_ +#define _MC_NQ_H_ + +#include +#include + +/* FIXME to be in .c file, renamed */ +struct nq_session { + /* Notification id */ + u32 id; + /* Notification payload */ + u32 payload; + /* Notifications list */ + struct list_head list; + /* Notification debug mutex */ + struct mutex mutex; + /* Current notification/session state */ + enum nq_notif_state { + NQ_NOTIF_IDLE, /* Nothing happened yet */ + NQ_NOTIF_QUEUED, /* Notification in overflow queue */ + NQ_NOTIF_SENT, /* Notification in send queue */ + NQ_NOTIF_RECEIVED, /* Notification received */ + NQ_NOTIF_CONSUMED, /* Notification reported to CA */ + NQ_NOTIF_DEAD, /* Error reported to CA */ + } state; + /* Time at notification state change */ + u64 cpu_clk; + /* This TA is of Global Platform type, set by upper layer */ + bool is_gp; +}; + +/* Initialisation/cleanup */ +int nq_init(void); +void nq_exit(void); + +/* Start/stop TEE */ +int nq_start(void); +void nq_stop(void); + +/* SWd suspend/resume */ +int nq_suspend(void); +int nq_resume(void); +bool nq_suspended(void); + +/* + * Get the requested SWd sleep timeout value (ms) + * - if the timeout is -1, wait indefinitely + * - if the timeout is 0, re-schedule immediately (timeouts in µs in the SWd) + * - otherwise sleep for the required time + * returns true if sleep is required, false otherwise + */ +bool nq_get_idle_timeout(s32 *timeout); +void nq_reset_idle_timeout(void); + +/* Services */ +/* Callback to scheduler registration */ +enum nq_scheduler_commands { + MC_NQ_YIELD, + MC_NQ_NSIQ, +}; + +void nq_register_scheduler(int (*scheduler_cb)(enum nq_scheduler_commands)); +void nq_register_crash_handler(void (*crashhandler_cb)(void)); +void nq_dump_status(void); +void nq_update_time(void); +/* Accessors for MCP/IWP contexts */ +union mcp_message *nq_get_mcp_message(void); +struct interworld_session *nq_get_iwp_buffer(void); +void nq_register_notif_handler(void (*handler)(u32 id, u32 payload), bool iwp); + +/* Notifications */ +void nq_session_init(struct nq_session *session, bool is_gp); +bool nq_session_is_gp(const struct nq_session *session); +u64 nq_session_notif_cpu_clk(const struct nq_session *session); +void nq_session_exit(struct nq_session *session); +void nq_session_state_update(struct nq_session *session, + enum nq_notif_state state); +const char *nq_session_state_string(struct nq_session *session); +int nq_session_notify(struct nq_session *session, u32 id, u32 payload); +bool nq_notifications_flush(void); + +#endif /* _MC_NQ_H_ */ diff --git a/drivers/gud/MobiCoreDriver/platform.h b/drivers/gud/MobiCoreDriver/platform.h new file mode 100644 index 000000000000..e2981b321569 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/platform.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013-2018 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. + */ +#ifndef _MC_PLATFORM_H_ +#define _MC_PLATFORM_H_ + +/* MobiCore Interrupt for Qualcomm (DT IRQ has priority if present) */ +#define MC_INTR_SSIQ 280 + +/* Use SMC for fastcalls */ +#define MC_SMC_FASTCALL + +#include + +/*--------------- Implementation -------------- */ +#include +#include + +#include +#include +#include +#include +#include + +#define SCM_MOBIOS_FNID(s, c) (((((s) & 0xFF) << 8) | ((c) & 0xFF)) \ + | 0x33000000) + +#define TZ_EXECUTIVE_EXT_ID_PARAM_ID \ + TZ_SYSCALL_CREATE_PARAM_ID_4( \ + TZ_SYSCALL_PARAM_TYPE_BUF_RW, \ + TZ_SYSCALL_PARAM_TYPE_VAL, \ + TZ_SYSCALL_PARAM_TYPE_BUF_RW, \ + TZ_SYSCALL_PARAM_TYPE_VAL) + +/* from following file */ +#define SCM_SVC_MOBICORE 250 +#define SCM_CMD_MOBICORE 1 + +static inline int smc_fastcall(void *fc_generic, size_t size) +{ + if (is_scm_armv8()) { + struct scm_desc desc = {0}; + int ret; + void *scm_buf = NULL; + + scm_buf = kzalloc(PAGE_ALIGN(size), GFP_KERNEL); + if (!scm_buf) + return -ENOMEM; + memcpy(scm_buf, fc_generic, size); + dmac_flush_range(scm_buf, scm_buf + size); + + desc.arginfo = TZ_EXECUTIVE_EXT_ID_PARAM_ID; + desc.args[0] = virt_to_phys(scm_buf); + desc.args[1] = (u32)size; + desc.args[2] = virt_to_phys(scm_buf); + desc.args[3] = (u32)size; + + ret = scm_call2( + SCM_MOBIOS_FNID(SCM_SVC_MOBICORE, SCM_CMD_MOBICORE), + &desc); + + dmac_flush_range(scm_buf, scm_buf + size); + + memcpy(fc_generic, scm_buf, size); + kfree(scm_buf); + return ret; + } + return scm_call(SCM_SVC_MOBICORE, SCM_CMD_MOBICORE, + fc_generic, size, + fc_generic, size); +} + +/* Should be defined for all TZBSPv4 platform common flag */ +#ifndef CONFIG_TRUSTONIC_TEE_LPAE +#define CONFIG_TRUSTONIC_TEE_LPAE +#endif + +/* + * Do not start the TEE at driver init + */ +#define MC_DELAYED_TEE_START + +/* + * Perform crypto clock enable/disable + * of clocks + * "bus_clk" + * "core_clk" + * "iface_clk" + */ +#define MC_CRYPTO_CLOCK_MANAGEMENT +#define MC_CRYPTO_CLOCK_CORESRC_PROPNAME "qcom,ce-opp-freq" +#define MC_CLOCK_CORESRC_DEFAULTRATE 100000000 + +/* + * Perform clock enable/disable for clock "core_clk_src" + */ +#define MC_DEVICE_PROPNAME "qcom,mcd" + +/* All TZBSPv4 targets are using AARCH32_FC flag */ +#define MC_AARCH32_FC + +#endif /* _MC_PLATFORM_H_ */ diff --git a/drivers/gud/MobiCoreDriver/pm.c b/drivers/gud/MobiCoreDriver/pm.c new file mode 100644 index 000000000000..9552d4f536fd --- /dev/null +++ b/drivers/gud/MobiCoreDriver/pm.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013-2016 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 "platform.h" /* MC_BL_NOTIFIER */ + +#ifdef MC_BL_NOTIFIER +#include "main.h" +#include "scheduler.h" /* SWd suspend/resume commands */ +#include "pm.h" +#include + +static struct pm_context { + struct notifier_block bl_swicher_notifier; +} pm_ctx; + +static int bl_switcher_notifier_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + unsigned int mpidr, cpu; + int ret = 0; + + asm volatile ("mrc\tp15, 0, %0, c0, c0, 5" : "=r" (mpidr)); + cpu = mpidr & 0x3; + mc_dev_devel("%s switching!!, cpu: %u, Out=%u", + event == SWITCH_ENTER ? "Before" : "After", cpu, + (mpidr >> 8) & 0xf); + + if (cpu != 0) + return 0; + + switch (event) { + case SWITCH_ENTER: + ret = mc_scheduler_suspend(); + break; + case SWITCH_EXIT: + ret = mc_scheduler_resume(); + break; + default: + mc_dev_devel("MobiCore: Unknown switch event!"); + } + + return ret; +} + +int mc_pm_start(void) +{ + pm_ctx.bl_swicher_notifier.notifier_call = bl_switcher_notifier_handler; + register_bL_swicher_notifier(&pm_ctx.bl_swicher_notifier); + return 0; +} + +void mc_pm_stop(void) +{ + unregister_bL_swicher_notifier(&pm_ctx.bl_swicher_notifier); +} + +#endif /* MC_BL_NOTIFIER */ diff --git a/drivers/gud/MobiCoreDriver/pm.h b/drivers/gud/MobiCoreDriver/pm.h new file mode 100644 index 000000000000..c6fbde581974 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/pm.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#ifndef _MC_PM_H_ +#define _MC_PM_H_ + +#include "platform.h" /* MC_BL_NOTIFIER */ + +#ifdef MC_BL_NOTIFIER +/* Initialize Power Management */ +int mc_pm_start(void); +/* Free all Power Management resources*/ +void mc_pm_stop(void); +#else +static inline int mc_pm_start(void) +{ + return 0; +} + +static inline void mc_pm_stop(void) +{ +} +#endif + +#endif /* _MC_PM_H_ */ diff --git a/drivers/gud/MobiCoreDriver/public/GP/tee_client_api.h b/drivers/gud/MobiCoreDriver/public/GP/tee_client_api.h new file mode 100644 index 000000000000..14253c4ad91c --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/GP/tee_client_api.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +/* + * This header file corresponds to V1.0 of the GlobalPlatform + * TEE Client API Specification + */ +#ifndef __TEE_CLIENT_API_H__ +#define __TEE_CLIENT_API_H__ + +#include "tee_client_types.h" +#include "tee_client_error.h" + +#include "tee_client_api_imp.h" + +#define TEEC_EXPORT + +/* + * The header tee_client_api_imp.h must define implementation-dependent types, + * constants and macros. + * + * The implementation-dependent types are: + * - teec_context_imp + * - teec_session_imp + * - teec_shared_memory_imp + * - teec_operation_imp + * + * The implementation-dependent constants are: + * - TEEC_CONFIG_SHAREDMEM_MAX_SIZE + * The implementation-dependent macros are: + * - TEEC_PARAM_TYPES + */ + +struct teec_value { + u32 a; + u32 b; +}; + +/* Type definitions */ +struct teec_context { + struct teec_context_imp imp; +}; + +struct teec_session { + struct teec_session_imp imp; +}; + +struct teec_shared_memory { + void *buffer; + size_t size; + u32 flags; + struct teec_shared_memory_imp imp; +}; + +struct teec_temp_memory_reference { + void *buffer; + size_t size; +}; + +struct teec_registered_memory_reference { + struct teec_shared_memory *parent; + size_t size; + size_t offset; +}; + +union teec_parameter { + struct teec_temp_memory_reference tmpref; + struct teec_registered_memory_reference memref; + struct teec_value value; +}; + +struct teec_operation { + u32 started; + u32 param_types; + union teec_parameter params[4]; + struct teec_operation_imp imp; +}; + +#define TEEC_ORIGIN_API 0x00000001 +#define TEEC_ORIGIN_COMMS 0x00000002 +#define TEEC_ORIGIN_TEE 0x00000003 +#define TEEC_ORIGIN_TRUSTED_APP 0x00000004 + +#define TEEC_MEM_INPUT 0x00000001 +#define TEEC_MEM_OUTPUT 0x00000002 + +#define TEEC_NONE 0x0 +#define TEEC_VALUE_INPUT 0x1 +#define TEEC_VALUE_OUTPUT 0x2 +#define TEEC_VALUE_INOUT 0x3 +#define TEEC_MEMREF_TEMP_INPUT 0x5 +#define TEEC_MEMREF_TEMP_OUTPUT 0x6 +#define TEEC_MEMREF_TEMP_INOUT 0x7 +#define TEEC_MEMREF_WHOLE 0xC +#define TEEC_MEMREF_PARTIAL_INPUT 0xD +#define TEEC_MEMREF_PARTIAL_OUTPUT 0xE +#define TEEC_MEMREF_PARTIAL_INOUT 0xF + +#define TEEC_LOGIN_PUBLIC 0x00000000 +#define TEEC_LOGIN_USER 0x00000001 +#define TEEC_LOGIN_GROUP 0x00000002 +#define TEEC_LOGIN_APPLICATION 0x00000004 +#define TEEC_LOGIN_USER_APPLICATION 0x00000005 +#define TEEC_LOGIN_GROUP_APPLICATION 0x00000006 + +#define TEEC_TIMEOUT_INFINITE 0xFFFFFFFF + +#pragma GCC visibility push(default) + +TEEC_EXPORT u32 +teec_initialize_context(const char *name, struct teec_context *context); + +TEEC_EXPORT void +teec_finalize_context(struct teec_context *context); + +TEEC_EXPORT u32 +teec_register_shared_memory(struct teec_context *context, + struct teec_shared_memory *shared_mem); + +TEEC_EXPORT u32 +teec_allocate_shared_memory(struct teec_context *context, + struct teec_shared_memory *shared_mem); + +TEEC_EXPORT void +teec_release_shared_memory(struct teec_shared_memory *shared_mem); + +TEEC_EXPORT u32 +teec_open_session(struct teec_context *context, + struct teec_session *session, + const struct teec_uuid *destination, + u32 connection_method, /* Should be 0 */ + const void *connection_data, + struct teec_operation *operation, + u32 *return_origin); + +TEEC_EXPORT void +teec_close_session(struct teec_session *session); + +TEEC_EXPORT u32 +teec_invoke_command(struct teec_session *session, + u32 command_id, + struct teec_operation *operation, + u32 *return_origin); + +TEEC_EXPORT void +teec_request_cancellation(struct teec_operation *operation); + +#pragma GCC visibility pop + +#endif /* __TEE_CLIENT_API_H__ */ diff --git a/drivers/gud/MobiCoreDriver/public/GP/tee_client_api_imp.h b/drivers/gud/MobiCoreDriver/public/GP/tee_client_api_imp.h new file mode 100644 index 000000000000..4b3497f4568b --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/GP/tee_client_api_imp.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +/* + * This header file defines the implementation-dependent types, + * constants and macros for all the Trusted Foundations implementations + * of the TEE Client API + */ +#ifndef __TEE_CLIENT_API_IMP_H__ +#define __TEE_CLIENT_API_IMP_H__ + +#define TEEC_MEM_INOUT (TEEC_MEM_INPUT | TEEC_MEM_OUTPUT) + +struct tee_client; + +struct teec_context_imp { + struct tee_client *client; +}; + +struct teec_session_imp { + u32 session_id; + struct teec_context_imp context; + bool active; +}; + +struct teec_shared_memory_imp { + struct tee_client *client; + bool implementation_allocated; +}; + +struct teec_operation_imp { + struct teec_session_imp *session; +}; + +/* + * There is no natural, compile-time limit on the shared memory, but a specific + * implementation may introduce a limit (in particular on TrustZone) + */ +#define TEEC_CONFIG_SHAREDMEM_MAX_SIZE ((size_t)0xFFFFFFFF) + +#define TEEC_PARAM_TYPES(entry0_type, entry1_type, entry2_type, entry3_type) \ + ((entry0_type) | ((entry1_type) << 4) | \ + ((entry2_type) << 8) | ((entry3_type) << 12)) + +#endif /* __TEE_CLIENT_API_IMP_H__ */ diff --git a/drivers/gud/MobiCoreDriver/public/GP/tee_client_error.h b/drivers/gud/MobiCoreDriver/public/GP/tee_client_error.h new file mode 100644 index 000000000000..85be1d4b8128 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/GP/tee_client_error.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef __TEE_CLIENT_ERROR_H__ +#define __TEE_CLIENT_ERROR_H__ + +#define TEEC_SUCCESS ((uint32_t)0x00000000) + +/** + * Generic error code : Generic error + **/ +#define TEEC_ERROR_GENERIC ((uint32_t)0xFFFF0000) + +/** + * Generic error code : The underlying security system denies the access to the + * object + **/ +#define TEEC_ERROR_ACCESS_DENIED ((uint32_t)0xFFFF0001) + +/** + * Generic error code : The pending operation is cancelled. + **/ +#define TEEC_ERROR_CANCEL ((uint32_t)0xFFFF0002) + +/** + * Generic error code : The underlying system detects a conflict + **/ +#define TEEC_ERROR_ACCESS_CONFLICT ((uint32_t)0xFFFF0003) + +/** + * Generic error code : Too much data for the operation or some data remain + * unprocessed by the operation. + **/ +#define TEEC_ERROR_EXCESS_DATA ((uint32_t)0xFFFF0004) + +/** + * Generic error code : Error of data format + **/ +#define TEEC_ERROR_BAD_FORMAT ((uint32_t)0xFFFF0005) + +/** + * Generic error code : The specified parameters are invalid + **/ +#define TEEC_ERROR_BAD_PARAMETERS ((uint32_t)0xFFFF0006) + +/** + * Generic error code : Illegal state for the operation. + **/ +#define TEEC_ERROR_BAD_STATE ((uint32_t)0xFFFF0007) + +/** + * Generic error code : The item is not found + **/ +#define TEEC_ERROR_ITEM_NOT_FOUND ((uint32_t)0xFFFF0008) + +/** + * Generic error code : The specified operation is not implemented + **/ +#define TEEC_ERROR_NOT_IMPLEMENTED ((uint32_t)0xFFFF0009) + +/** + * Generic error code : The specified operation is not supported + **/ +#define TEEC_ERROR_NOT_SUPPORTED ((uint32_t)0xFFFF000A) + +/** + * Generic error code : Insufficient data is available for the operation. + **/ +#define TEEC_ERROR_NO_DATA ((uint32_t)0xFFFF000B) + +/** + * Generic error code : Not enough memory to perform the operation + **/ +#define TEEC_ERROR_OUT_OF_MEMORY ((uint32_t)0xFFFF000C) + +/** + * Generic error code : The service is currently unable to handle the request; + * try later + **/ +#define TEEC_ERROR_BUSY ((uint32_t)0xFFFF000D) + +/** + * Generic communication error + **/ +#define TEEC_ERROR_COMMUNICATION ((uint32_t)0xFFFF000E) + +/** + * Generic error code : security violation + **/ +#define TEEC_ERROR_SECURITY ((uint32_t)0xFFFF000F) + +/** + * Generic error code : the buffer is too short + **/ +#define TEEC_ERROR_SHORT_BUFFER ((uint32_t)0xFFFF0010) + +/** + * Error of communication: The target of the connection is dead + **/ +#define TEEC_ERROR_TARGET_DEAD ((uint32_t)0xFFFF3024) + +/** + * File system error code: not enough space to complete the operation. + **/ +#define TEEC_ERROR_STORAGE_NO_SPACE ((uint32_t)0xFFFF3041) + +#endif /* __TEE_CLIENT_ERROR_H__ */ + diff --git a/drivers/gud/MobiCoreDriver/public/GP/tee_client_types.h b/drivers/gud/MobiCoreDriver/public/GP/tee_client_types.h new file mode 100644 index 000000000000..e870ee6aef48 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/GP/tee_client_types.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef __TEE_CLIENT_TYPES_H__ +#define __TEE_CLIENT_TYPES_H__ + +/* Definition of an UUID (from RFC 4122 http://www.ietf.org/rfc/rfc4122.txt) */ +struct teec_uuid { + u32 time_low; + u16 time_mid; + u16 time_hi_and_version; + u8 clock_seq_and_node[8]; +}; + +/* Type definition for a TEE Identity */ +struct tee_identity { + u32 login; + struct teec_uuid uuid; +}; + +#endif /* __TEE_CLIENT_TYPES_H__ */ diff --git a/drivers/gud/MobiCoreDriver/public/mc_admin.h b/drivers/gud/MobiCoreDriver/public/mc_admin.h new file mode 100644 index 000000000000..b0e3abb35e0a --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/mc_admin.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef __MC_ADMIN_IOCTL_H__ +#define __MC_ADMIN_IOCTL_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MC_ADMIN_DEVNODE "mobicore" + +/* Driver/daemon commands */ +enum { + /* Command 0 is reserved */ + MC_DRV_GET_ROOT_CONTAINER = 1, + MC_DRV_GET_SP_CONTAINER = 2, + MC_DRV_GET_TRUSTLET_CONTAINER = 3, + MC_DRV_GET_TRUSTLET = 4, + MC_DRV_SIGNAL_CRASH = 5, +}; + +/* MobiCore IOCTL magic number */ +#define MC_IOC_MAGIC 'M' + +struct mc_admin_request { + __u32 request_id; /* Unique request identifier */ + __u32 command; /* Command to daemon */ + struct mc_uuid_t uuid; /* UUID of trustlet, if relevant */ + __u32 is_gp; /* Whether trustlet is GP */ + __u32 spid; /* SPID of trustlet, if relevant */ +}; + +struct mc_admin_response { + __u32 request_id; /* Unique request identifier */ + __u32 error_no; /* Errno from daemon */ + __u32 spid; /* SPID of trustlet, if relevant */ + __u32 service_type; /* Type of trustlet being returned */ + __u32 length; /* Length of data to get */ + /* Any data follows */ +}; + +struct mc_admin_driver_info { + /* Version, and something else..*/ + __u32 drv_version; + __u32 initial_cmd_id; +}; + +struct mc_admin_load_info { + __u32 spid; /* SPID of trustlet, if relevant */ + __u64 address; /* Address of the data */ + __u32 length; /* Length of data to get */ +}; + +#define MC_ADMIN_IO_GET_DRIVER_REQUEST \ + _IOR(MC_IOC_MAGIC, 0, struct mc_admin_request) +#define MC_ADMIN_IO_GET_INFO \ + _IOR(MC_IOC_MAGIC, 1, struct mc_admin_driver_info) +#define MC_ADMIN_IO_LOAD_DRIVER \ + _IOW(MC_IOC_MAGIC, 2, struct mc_admin_load_info) +#define MC_ADMIN_IO_LOAD_TOKEN \ + _IOW(MC_IOC_MAGIC, 3, struct mc_admin_load_info) +#define MC_ADMIN_IO_LOAD_CHECK \ + _IOW(MC_IOC_MAGIC, 4, struct mc_admin_load_info) + +#ifdef __cplusplus +} +#endif +#endif /* __MC_ADMIN_IOCTL_H__ */ diff --git a/drivers/gud/MobiCoreDriver/public/mc_linux_api.h b/drivers/gud/MobiCoreDriver/public/mc_linux_api.h new file mode 100644 index 000000000000..151e661d8d88 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/mc_linux_api.h @@ -0,0 +1,30 @@ +/* + * 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. + */ +#ifndef _MC_LINUX_API_H_ +#define _MC_LINUX_API_H_ + +#include + +/* + * Switch TEE active core to core_num, defined as linux + * core id + */ +int mc_switch_core(int core_num); + +/* + * Return TEE active core as Linux core id + */ +int mc_active_core(void); + +#endif /* _MC_LINUX_API_H_ */ diff --git a/drivers/gud/MobiCoreDriver/public/mc_user.h b/drivers/gud/MobiCoreDriver/public/mc_user.h new file mode 100644 index 000000000000..812335e1a084 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/mc_user.h @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2013-2018 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. + */ + +#ifndef _MC_USER_H_ +#define _MC_USER_H_ + +#define MCDRVMODULEAPI_VERSION_MAJOR 6 +#define MCDRVMODULEAPI_VERSION_MINOR 3 + +#include + +#ifndef __KERNEL__ +#define BIT(n) (1 << (n)) +#endif /* __KERNEL__ */ + +#define MC_USER_DEVNODE "mobicore-user" + +/** Maximum length of MobiCore product ID string. */ +#define MC_PRODUCT_ID_LEN 64 + +/** Flags for session */ +#define MC_IO_SESSION_REMOTE_BUFFERS BIT(0) +#define MC_IO_SESSION_NO_MULTIMAP BIT(1) + +/** Number of buffers that can be mapped at once */ +#define MC_MAP_MAX 4 + +/* Max length for buffers */ +#define BUFFER_LENGTH_MAX 0x100000 + +/* Flags for buffers to map (aligned on GP) */ +#define MC_IO_MAP_INPUT BIT(0) +#define MC_IO_MAP_OUTPUT BIT(1) +#define MC_IO_MAP_INPUT_OUTPUT (MC_IO_MAP_INPUT | MC_IO_MAP_OUTPUT) +/** Extra flags for buffers to map (proprietary) */ +#define MC_IO_MAP_PERSISTENT BIT(20) + +/* + * Universally Unique Identifier (UUID) according to ISO/IEC 11578. + */ +struct mc_uuid_t { + __u8 value[16]; /* Value of the UUID */ +}; + +/* + * GP TA login types. + */ +enum mc_login_type { + LOGIN_PUBLIC = 0, + LOGIN_USER, + LOGIN_GROUP, + LOGIN_APPLICATION = 4, + LOGIN_USER_APPLICATION, + LOGIN_GROUP_APPLICATION, +}; + +/* + * GP TA identity structure. + */ +struct mc_identity { + enum mc_login_type login_type; + union { + __u8 login_data[16]; + gid_t gid; /* Requested group id */ + struct { + uid_t euid; + uid_t ruid; + } uid; + }; +}; + +/* + * Data exchange structure of the MC_IO_OPEN_SESSION ioctl command. + */ +struct mc_ioctl_open_session { + struct mc_uuid_t uuid; /* trustlet uuid */ + __u32 is_gp_uuid; /* uuid is for GP TA */ + __u32 sid; /* session id (out) */ + __u64 tci; /* tci buffer pointer */ + __u32 tcilen; /* tci length */ + struct mc_identity identity; /* GP TA identity */ + __s32 client_fd; /* client, when using proxy */ +}; + +/* + * Data exchange structure of the MC_IO_OPEN_TRUSTLET ioctl command. + */ +struct mc_ioctl_open_trustlet { + __u32 sid; /* session id (out) */ + __u32 spid; /* trustlet spid */ + __u64 buffer; /* trustlet binary pointer */ + __u32 tlen; /* binary length */ + __u64 tci; /* tci buffer pointer */ + __u32 tcilen; /* tci length */ + __s32 client_fd; /* client, when using proxy */ +}; + +/* + * Data exchange structure of the MC_IO_WAIT ioctl command. + */ +struct mc_ioctl_wait { + __u32 sid; /* session id (in) */ + __s32 timeout; /* notification timeout */ + __u32 partial; /* for proxy server to retry silently */ +}; + +/* + * Data exchange structure of the MC_IO_ALLOC ioctl command. + */ +struct mc_ioctl_alloc { + __u32 len; /* buffer length */ + __u32 handle; /* user handle for the buffer (out) */ +}; + +/* + * Buffer mapping incoming and outgoing information. + */ +struct mc_ioctl_buffer { + __u64 va; /* user space address of buffer */ + __u32 len; /* buffer length */ + __u64 sva; /* SWd virt address of buffer (out) */ + __u32 flags; /* buffer flags */ +}; + +/* + * Data exchange structure of the MC_IO_MAP and MC_IO_UNMAP ioctl commands. + */ +struct mc_ioctl_map { + __u32 sid; /* session id */ + __s32 client_fd; /* client, when using proxy */ + struct mc_ioctl_buffer buf; /* buffers info */ +}; + +/* + * Data exchange structure of the MC_IO_ERR ioctl command. + */ +struct mc_ioctl_geterr { + __u32 sid; /* session id */ + __s32 value; /* error value (out) */ +}; + +/* + * Global MobiCore Version Information. + */ +struct mc_version_info { + char product_id[MC_PRODUCT_ID_LEN]; /* Product ID string */ + __u32 version_mci; /* Mobicore Control Interface */ + __u32 version_so; /* Secure Objects */ + __u32 version_mclf; /* MobiCore Load Format */ + __u32 version_container; /* MobiCore Container Format */ + __u32 version_mc_config; /* MobiCore Config. Block Format */ + __u32 version_tl_api; /* MobiCore Trustlet API */ + __u32 version_dr_api; /* MobiCore Driver API */ + __u32 version_nwd; /* This Driver */ +}; + +/* + * GP TA operation structure. + */ +struct gp_value { + __u32 a; + __u32 b; +}; + +struct gp_temp_memref { + __u64 buffer; + __u64 size; +}; + +struct gp_shared_memory { + __u64 buffer; + __u64 size; + __u32 flags; +}; + +struct gp_regd_memref { + struct gp_shared_memory parent; + __u64 size; + __u64 offset; +}; + +union gp_param { + struct gp_temp_memref tmpref; + struct gp_regd_memref memref; + struct gp_value value; +}; + +struct gp_operation { + __u32 started; + __u32 param_types; + union gp_param params[4]; +}; + +struct gp_return { + __u32 origin; + __u32 value; +}; + +/* + * Data exchange structure of the MC_IO_GP_INITIALIZE_CONTEXT ioctl command. + */ +struct mc_ioctl_gp_initialize_context { + struct gp_return ret; /* return origin/value (out) */ +}; + +/* + * Data exchange structure of the MC_IO_GP_REGISTER_SHARED_MEM ioctl command. + */ +struct mc_ioctl_gp_register_shared_mem { + struct gp_shared_memory memref; + struct gp_return ret; /* return origin/value (out) */ + __s32 client_fd; /* client, when using proxy */ +}; + +/* + * Data exchange structure of the MC_IO_GP_RELEASE_SHARED_MEM ioctl command. + */ +struct mc_ioctl_gp_release_shared_mem { + struct gp_shared_memory memref; +}; + +/* + * Data exchange structure of the MC_IO_GP_OPEN_SESSION ioctl command. + */ +struct mc_ioctl_gp_open_session { + struct mc_uuid_t uuid; /* trustlet uuid */ + struct mc_identity identity; /* GP TA identity */ + struct gp_operation operation; /* set of parameters */ + struct gp_return ret; /* return origin/value (out) */ + __u32 session_id; /* session id (out) */ + __s32 client_fd; /* client, when using proxy */ +}; + +/* + * Data exchange structure of the MC_IO_GP_CLOSE_SESSION ioctl command. + */ +struct mc_ioctl_gp_close_session { + __u32 session_id; /* session id */ +}; + +/* + * Data exchange structure of the MC_IO_GP_INVOKE_COMMAND ioctl command. + */ +struct mc_ioctl_gp_invoke_command { + struct gp_operation operation; /* set of parameters */ + __u32 session_id; /* session id */ + __u32 command_id; /* ID of the command */ + struct gp_return ret; /* return origin/value (out) */ + __s32 client_fd; /* client, when using proxy */ +}; + +/* + * Data exchange structure of the MC_IO_GP_CANCEL ioctl command. + */ +struct mc_ioctl_gp_request_cancellation { + struct gp_operation operation; /* set of parameters */ +}; + +/* + * defines for the ioctl mobicore driver module function call from user space. + */ +/* MobiCore IOCTL magic number */ +#define MC_IOC_MAGIC 'M' + +/* + * Implement corresponding functions from user api + */ +#define MC_IO_OPEN_SESSION \ + _IOWR(MC_IOC_MAGIC, 0, struct mc_ioctl_open_session) +#define MC_IO_OPEN_TRUSTLET \ + _IOWR(MC_IOC_MAGIC, 1, struct mc_ioctl_open_trustlet) +#define MC_IO_CLOSE_SESSION \ + _IO(MC_IOC_MAGIC, 2) +#define MC_IO_NOTIFY \ + _IO(MC_IOC_MAGIC, 3) +#define MC_IO_WAIT \ + _IOW(MC_IOC_MAGIC, 4, struct mc_ioctl_wait) +#define MC_IO_MAP \ + _IOWR(MC_IOC_MAGIC, 5, struct mc_ioctl_map) +#define MC_IO_UNMAP \ + _IOW(MC_IOC_MAGIC, 6, struct mc_ioctl_map) +#define MC_IO_ERR \ + _IOWR(MC_IOC_MAGIC, 7, struct mc_ioctl_geterr) +#define MC_IO_HAS_SESSIONS \ + _IO(MC_IOC_MAGIC, 8) +#define MC_IO_VERSION \ + _IOR(MC_IOC_MAGIC, 9, struct mc_version_info) +#define MC_IO_GP_INITIALIZE_CONTEXT \ + _IOW(MC_IOC_MAGIC, 20, struct mc_ioctl_gp_initialize_context) +#define MC_IO_GP_REGISTER_SHARED_MEM \ + _IOWR(MC_IOC_MAGIC, 21, struct mc_ioctl_gp_register_shared_mem) +#define MC_IO_GP_RELEASE_SHARED_MEM \ + _IOW(MC_IOC_MAGIC, 23, struct mc_ioctl_gp_release_shared_mem) +#define MC_IO_GP_OPEN_SESSION \ + _IOWR(MC_IOC_MAGIC, 24, struct mc_ioctl_gp_open_session) +#define MC_IO_GP_CLOSE_SESSION \ + _IOW(MC_IOC_MAGIC, 25, struct mc_ioctl_gp_close_session) +#define MC_IO_GP_INVOKE_COMMAND \ + _IOWR(MC_IOC_MAGIC, 26, struct mc_ioctl_gp_invoke_command) +#define MC_IO_GP_REQUEST_CANCELLATION \ + _IOW(MC_IOC_MAGIC, 27, struct mc_ioctl_gp_request_cancellation) + +#endif /* _MC_USER_H_ */ diff --git a/drivers/gud/MobiCoreDriver/public/mobicore_driver_api.h b/drivers/gud/MobiCoreDriver/public/mobicore_driver_api.h new file mode 100644 index 000000000000..801b19bd19db --- /dev/null +++ b/drivers/gud/MobiCoreDriver/public/mobicore_driver_api.h @@ -0,0 +1,466 @@ +/* + * 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. + */ +#ifndef _MOBICORE_DRIVER_API_H_ +#define _MOBICORE_DRIVER_API_H_ + +#include "mc_user.h" + +#define __MC_CLIENT_LIB_API + +/* + * Return values of MobiCore driver functions. + */ +enum mc_result { + /* Function call succeeded. */ + MC_DRV_OK = 0, + /* No notification available. */ + MC_DRV_NO_NOTIFICATION = 1, + /* Error during notification on communication level. */ + MC_DRV_ERR_NOTIFICATION = 2, + /* Function not implemented. */ + MC_DRV_ERR_NOT_IMPLEMENTED = 3, + /* No more resources available. */ + MC_DRV_ERR_OUT_OF_RESOURCES = 4, + /* Driver initialization failed. */ + MC_DRV_ERR_INIT = 5, + /* Unknown error. */ + MC_DRV_ERR_UNKNOWN = 6, + /* The specified device is unknown. */ + MC_DRV_ERR_UNKNOWN_DEVICE = 7, + /* The specified session is unknown.*/ + MC_DRV_ERR_UNKNOWN_SESSION = 8, + /* The specified operation is not allowed. */ + MC_DRV_ERR_INVALID_OPERATION = 9, + /* The response header from the MC is invalid. */ + MC_DRV_ERR_INVALID_RESPONSE = 10, + /* Function call timed out. */ + MC_DRV_ERR_TIMEOUT = 11, + /* Can not allocate additional memory. */ + MC_DRV_ERR_NO_FREE_MEMORY = 12, + /* Free memory failed. */ + MC_DRV_ERR_FREE_MEMORY_FAILED = 13, + /* Still some open sessions pending. */ + MC_DRV_ERR_SESSION_PENDING = 14, + /* MC daemon not reachable */ + MC_DRV_ERR_DAEMON_UNREACHABLE = 15, + /* The device file of the kernel module could not be opened. */ + MC_DRV_ERR_INVALID_DEVICE_FILE = 16, + /* Invalid parameter. */ + MC_DRV_ERR_INVALID_PARAMETER = 17, + /* Unspecified error from Kernel Module*/ + MC_DRV_ERR_KERNEL_MODULE = 18, + /* Error during mapping of additional bulk memory to session. */ + MC_DRV_ERR_BULK_MAPPING = 19, + /* Error during unmapping of additional bulk memory to session. */ + MC_DRV_ERR_BULK_UNMAPPING = 20, + /* Notification received, exit code available. */ + MC_DRV_INFO_NOTIFICATION = 21, + /* Set up of NWd connection failed. */ + MC_DRV_ERR_NQ_FAILED = 22, + /* Wrong daemon version. */ + MC_DRV_ERR_DAEMON_VERSION = 23, + /* Wrong container version. */ + MC_DRV_ERR_CONTAINER_VERSION = 24, + /* System Trustlet public key is wrong. */ + MC_DRV_ERR_WRONG_PUBLIC_KEY = 25, + /* Wrong container type(s). */ + MC_DRV_ERR_CONTAINER_TYPE_MISMATCH = 26, + /* Container is locked (or not activated). */ + MC_DRV_ERR_CONTAINER_LOCKED = 27, + /* SPID is not registered with root container. */ + MC_DRV_ERR_SP_NO_CHILD = 28, + /* UUID is not registered with sp container. */ + MC_DRV_ERR_TL_NO_CHILD = 29, + /* Unwrapping of root container failed. */ + MC_DRV_ERR_UNWRAP_ROOT_FAILED = 30, + /* Unwrapping of service provider container failed. */ + MC_DRV_ERR_UNWRAP_SP_FAILED = 31, + /* Unwrapping of Trustlet container failed. */ + MC_DRV_ERR_UNWRAP_TRUSTLET_FAILED = 32, + /* No device associated with connection. */ + MC_DRV_ERR_DAEMON_DEVICE_NOT_OPEN = 33, + /* TA blob attestation is incorrect. */ + MC_DRV_ERR_TA_ATTESTATION_ERROR = 34, + /* Interrupted system call. */ + MC_DRV_ERR_INTERRUPTED_BY_SIGNAL = 35, + /* Service is blocked and opensession is thus not allowed. */ + MC_DRV_ERR_SERVICE_BLOCKED = 36, + /* Service is locked and opensession is thus not allowed. */ + MC_DRV_ERR_SERVICE_LOCKED = 37, + /* Service was killed by the TEE (due to an administrative command). */ + MC_DRV_ERR_SERVICE_KILLED = 38, + /* All permitted instances to the service are used */ + MC_DRV_ERR_NO_FREE_INSTANCES = 39, + /* TA blob header is incorrect. */ + MC_DRV_ERR_TA_HEADER_ERROR = 40, +}; + +/* + * Structure of Session Handle, includes the Session ID and the Device ID the + * Session belongs to. + * The session handle will be used for session-based MobiCore communication. + * It will be passed to calls which address a communication end point in the + * MobiCore environment. + */ +struct mc_session_handle { + u32 session_id; /* MobiCore session ID */ + u32 device_id; /* Device ID the session belongs to */ +}; + +/* + * Information structure about additional mapped Bulk buffer between the + * Trustlet Connector (NWd) and the Trustlet (SWd). This structure is + * initialized from a Trustlet Connector by calling mc_map(). + * In order to use the memory within a Trustlet the Trustlet Connector has to + * inform the Trustlet with the content of this structure via the TCI. + */ +struct mc_bulk_map { + /* + * The virtual address of the Bulk buffer regarding the address space + * of the Trustlet, already includes a possible offset! + */ + u32 secure_virt_addr; + u32 secure_virt_len; /* Length of the mapped Bulk buffer */ +}; + +/* The default device ID */ +#define MC_DEVICE_ID_DEFAULT 0 +/* Wait infinite for a response of the MC. */ +#define MC_INFINITE_TIMEOUT ((s32)(-1)) +/* Do not wait for a response of the MC. */ +#define MC_NO_TIMEOUT 0 +/* TCI/DCI must not exceed 1MiB */ +#define MC_MAX_TCI_LEN 0x100000 + +/** + * mc_open_device() - Open a new connection to a MobiCore device. + * @device_id: Identifier for the MobiCore device to be used. + * MC_DEVICE_ID_DEFAULT refers to the default device. + * + * Initializes all device specific resources required to communicate with a + * MobiCore instance located on the specified device in the system. If the + * device does not exist the function will return MC_DRV_ERR_UNKNOWN_DEVICE. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_ERR_INVALID_OPERATION: device already opened + * MC_DRV_ERR_DAEMON_UNREACHABLE: problems with daemon + * MC_DRV_ERR_UNKNOWN_DEVICE: device_id unknown + * MC_DRV_ERR_INVALID_DEVICE_FILE: kernel module under /dev/mobicore + * cannot be opened + */ +__MC_CLIENT_LIB_API enum mc_result mc_open_device( + u32 device_id); + +/** + * mc_close_device() - Close the connection to a MobiCore device. + * @device_id: Identifier for the MobiCore device. + * + * When closing a device, active sessions have to be closed beforehand. + * Resources associated with the device will be released. + * The device may be opened again after it has been closed. + * + * MC_DEVICE_ID_DEFAULT refers to the default device. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_ERR_UNKNOWN_DEVICE: device id is invalid + * MC_DRV_ERR_SESSION_PENDING: a session is still open + * MC_DRV_ERR_DAEMON_UNREACHABLE: problems with daemon occur + */ +__MC_CLIENT_LIB_API enum mc_result mc_close_device( + u32 device_id); + +/** + * mc_open_session() - Open a new session to a Trustlet. + * @session: On success, the session data will be returned + * @uuid: UUID of the Trustlet to be opened + * @tci: TCI buffer for communicating with the Trustlet + * @tci_len: Length of the TCI buffer. Maximum allowed value + * is MC_MAX_TCI_LEN + * + * The Trustlet with the given UUID has to be available in the flash filesystem. + * + * Write MCP open message to buffer and notify MobiCore about the availability + * of a new command. + * + * Waits till the MobiCore responses with the new session ID (stored in the MCP + * buffer). + * + * Note that session.device_id has to be the device id of an opened device. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: session parameter is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id is invalid + * MC_DRV_ERR_DAEMON_UNREACHABLE: problems with daemon socket occur + * MC_DRV_ERR_NQ_FAILED: daemon returns an error + */ +__MC_CLIENT_LIB_API enum mc_result mc_open_session( + struct mc_session_handle *session, + const struct mc_uuid_t *uuid, + u8 *tci, + u32 tci_len); + +/** + * mc_open_trustlet() - Open a new session to the provided Trustlet. + * @session: On success, the session data will be returned + * @spid: Service Provider ID (for SP trustlets otherwise ignored) + * @trustlet Memory buffer containing the Trusted Application binary + * @trustlet_len Trusted Application length + * @tci: TCI buffer for communicating with the Trustlet + * @tci_len: Length of the TCI buffer. Maximum allowed value + * is MC_MAX_TCI_LEN + * + * Write MCP open message to buffer and notify MobiCore about the availability + * of a new command. + * + * Waits till the MobiCore responses with the new session ID (stored in the MCP + * buffer). + * + * Note that session.device_id has to be the device id of an opened device. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: session parameter is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id is invalid + * MC_DRV_ERR_DAEMON_UNREACHABLE: problems with daemon socket occur + * MC_DRV_ERR_NQ_FAILED: daemon returns an error + */ +__MC_CLIENT_LIB_API enum mc_result mc_open_trustlet( + struct mc_session_handle *session, + u32 spid, + u8 *trustlet, + u32 trustlet_len, + u8 *tci, + u32 len); + +/** + * mc_close_session() - Close a Trustlet session. + * @session: Session to be closed. + * + * Closes the specified MobiCore session. The call will block until the + * session has been closed. + * + * Device device_id has to be opened in advance. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: session parameter is invalid + * MC_DRV_ERR_UNKNOWN_SESSION: session id is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id of session is invalid + * MC_DRV_ERR_DAEMON_UNREACHABLE: problems with daemon occur + * MC_DRV_ERR_INVALID_DEVICE_FILE: daemon cannot open Trustlet file + */ +__MC_CLIENT_LIB_API enum mc_result mc_close_session( + struct mc_session_handle *session); + +/** + * mc_notify() - Notify a session. + * @session: The session to be notified. + * + * Notifies the session end point about available message data. + * If the session parameter is correct, notify will always succeed. + * Corresponding errors can only be received by mc_wait_notification(). + * + * A session has to be opened in advance. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: session parameter is invalid + * MC_DRV_ERR_UNKNOWN_SESSION: session id is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id of session is invalid + */ +__MC_CLIENT_LIB_API enum mc_result mc_notify( + struct mc_session_handle *session); + +/** + * mc_wait_notification() - Wait for a notification. + * @session: The session the notification should correspond to. + * @timeout: Time in milliseconds to wait + * (MC_NO_TIMEOUT : direct return, > 0 : milliseconds, + * MC_INFINITE_TIMEOUT : wait infinitely) + * + * Wait for a notification issued by the MobiCore for a specific session. + * The timeout parameter specifies the number of milliseconds the call will wait + * for a notification. + * + * If the caller passes 0 as timeout value the call will immediately return. + * If timeout value is below 0 the call will block until a notification for the + * session has been received. + * + * If timeout is below 0, call will block. + * + * Caller has to trust the other side to send a notification to wake him up + * again. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_ERR_TIMEOUT: no notification arrived in time + * MC_DRV_INFO_NOTIFICATION: a problem with the session was + * encountered. Get more details with + * mc_get_session_error_code() + * MC_DRV_ERR_NOTIFICATION: a problem with the socket occurred + * MC_DRV_INVALID_PARAMETER: a parameter is invalid + * MC_DRV_ERR_UNKNOWN_SESSION: session id is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id of session is invalid + */ +__MC_CLIENT_LIB_API enum mc_result mc_wait_notification( + struct mc_session_handle *session, + s32 timeout); + +/** + * mc_malloc_wsm() - Allocate a block of world shared memory (WSM). + * @device_id: The ID of an opened device to retrieve the WSM from. + * @align: The alignment (number of pages) of the memory block + * (e.g. 0x00000001 for 4kb). + * @len: Length of the block in bytes. + * @wsm: Virtual address of the world shared memory block. + * @wsm_flags: Platform specific flags describing the memory to + * be allocated. + * + * The MC driver allocates a contiguous block of memory which can be used as + * WSM. + * This implicates that the allocated memory is aligned according to the + * alignment parameter. + * + * Always returns a buffer of size WSM_SIZE aligned to 4K. + * + * Align and wsm_flags are currently ignored + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: a parameter is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id is invalid + * MC_DRV_ERR_NO_FREE_MEMORY: no more contiguous memory is + * available in this size or for this + * process + */ +__MC_CLIENT_LIB_API enum mc_result mc_malloc_wsm( + u32 device_id, + u32 align, + u32 len, + u8 **wsm, + u32 wsm_flags); + +/** + * mc_free_wsm() - Free a block of world shared memory (WSM). + * @device_id: The ID to which the given address belongs + * @wsm: Address of WSM block to be freed + * + * The MC driver will free a block of world shared memory (WSM) previously + * allocated with mc_malloc_wsm(). The caller has to assure that the address + * handed over to the driver is a valid WSM address. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: a parameter is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: when device id is invalid + * MC_DRV_ERR_FREE_MEMORY_FAILED: on failure + */ +__MC_CLIENT_LIB_API enum mc_result mc_free_wsm( + u32 device_id, + u8 *wsm); + +/** + *mc_map() - Map additional bulk buffer between a Trustlet Connector (TLC) + * and the Trustlet (TL) for a session + * @session: Session handle with information of the device_id and + * the session_id. The given buffer is mapped to the + * session specified in the sessionHandle + * @buf: Virtual address of a memory portion (relative to TLC) + * to be shared with the Trustlet, already includes a + * possible offset! + * @len: length of buffer block in bytes. + * @map_info: Information structure about the mapped Bulk buffer + * between the TLC (NWd) and the TL (SWd). + * + * Memory allocated in user space of the TLC can be mapped as additional + * communication channel (besides TCI) to the Trustlet. Limitation of the + * Trustlet memory structure apply: only 6 chunks can be mapped with a maximum + * chunk size of 1 MiB each. + * + * It is up to the application layer (TLC) to inform the Trustlet + * about the additional mapped bulk memory. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: a parameter is invalid + * MC_DRV_ERR_UNKNOWN_SESSION: session id is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id of session is invalid + * MC_DRV_ERR_DAEMON_UNREACHABLE: problems with daemon occur + * MC_DRV_ERR_BULK_MAPPING: buf is already uses as bulk buffer or + * when registering the buffer failed + */ +__MC_CLIENT_LIB_API enum mc_result mc_map( + struct mc_session_handle *session, + void *buf, + u32 len, + struct mc_bulk_map *map_info); + +/** + * mc_unmap() - Remove additional mapped bulk buffer between Trustlet Connector + * (TLC) and the Trustlet (TL) for a session + * @session: Session handle with information of the device_id and + * the session_id. The given buffer is unmapped from the + * session specified in the sessionHandle. + * @buf: Virtual address of a memory portion (relative to TLC) + * shared with the TL, already includes a possible offset! + * @map_info: Information structure about the mapped Bulk buffer + * between the TLC (NWd) and the TL (SWd) + * + * The bulk buffer will immediately be unmapped from the session context. + * + * The application layer (TLC) must inform the TL about unmapping of the + * additional bulk memory before calling mc_unmap! + * + * The clientlib currently ignores the len field in map_info. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: a parameter is invalid + * MC_DRV_ERR_UNKNOWN_SESSION: session id is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id of session is invalid + * MC_DRV_ERR_DAEMON_UNREACHABLE: problems with daemon occur + * MC_DRV_ERR_BULK_UNMAPPING: buf was not registered earlier + * or when unregistering failed + */ +__MC_CLIENT_LIB_API enum mc_result mc_unmap( + struct mc_session_handle *session, + void *buf, + struct mc_bulk_map *map_info); + +/* + * mc_get_session_error_code() - Get additional error information of the last + * error that occurred on a session. + * @session: Session handle with information of the device_id and + * the session_id + * @exit_code: >0 Trustlet has terminated itself with this value, + * <0 Trustlet is dead because of an error within the + * MobiCore (e.g. Kernel exception). See also MCI + * definition. + * + * After the request the stored error code will be deleted. + * + * Return codes: + * MC_DRV_OK: operation completed successfully + * MC_DRV_INVALID_PARAMETER: a parameter is invalid + * MC_DRV_ERR_UNKNOWN_SESSION: session id is invalid + * MC_DRV_ERR_UNKNOWN_DEVICE: device id of session is invalid + */ +__MC_CLIENT_LIB_API enum mc_result mc_get_session_error_code( + struct mc_session_handle *session, + s32 *exit_code); + +#endif /* _MOBICORE_DRIVER_API_H_ */ diff --git a/drivers/gud/MobiCoreDriver/scheduler.c b/drivers/gud/MobiCoreDriver/scheduler.c new file mode 100644 index 000000000000..b6e3f65c58f1 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/scheduler.c @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include + +#include "public/mc_user.h" + +#include "main.h" +#include "fastcall.h" +#include "logging.h" +#include "mcp.h" +#include "nq.h" +#include "scheduler.h" + +#define SCHEDULING_FREQ 5 /**< N-SIQ every n-th time */ +#define DEFAULT_TIMEOUT_MS 60000 + +static struct sched_ctx { + struct task_struct *thread; + bool thread_run; + struct completion idle_complete; /* Unblock scheduler thread */ + struct completion sleep_complete; /* Wait for sleep status */ + struct mutex sleep_mutex; /* Protect sleep request */ + struct mutex request_mutex; /* Protect all below */ + /* The order of this enum matters */ + enum sched_command { + NONE, /* No specific request */ + YIELD, /* Run the SWd */ + NSIQ, /* Schedule the SWd */ + SUSPEND, /* Suspend the SWd */ + RESUME, /* Resume the SWd */ + } request; + bool suspended; +} sched_ctx; + +static int mc_scheduler_command(enum sched_command command) +{ + if (IS_ERR_OR_NULL(sched_ctx.thread)) + return -EFAULT; + + mutex_lock(&sched_ctx.request_mutex); + if (sched_ctx.request < command) { + sched_ctx.request = command; + complete(&sched_ctx.idle_complete); + } + + mutex_unlock(&sched_ctx.request_mutex); + return 0; +} + +static int mc_scheduler_pm_command(enum sched_command command) +{ + int ret = -EPERM; + + if (IS_ERR_OR_NULL(sched_ctx.thread)) + return -EFAULT; + + mutex_lock(&sched_ctx.sleep_mutex); + + /* Send request */ + mc_scheduler_command(command); + + /* Wait for scheduler to reply */ + wait_for_completion(&sched_ctx.sleep_complete); + mutex_lock(&sched_ctx.request_mutex); + if (command == SUSPEND) { + if (sched_ctx.suspended) + ret = 0; + } else { + if (!sched_ctx.suspended) + ret = 0; + } + + mutex_unlock(&sched_ctx.request_mutex); + + mutex_unlock(&sched_ctx.sleep_mutex); + return ret; +} + +static int mc_dev_command(enum nq_scheduler_commands command) +{ + switch (command) { + case MC_NQ_YIELD: + return mc_scheduler_command(YIELD); + case MC_NQ_NSIQ: + return mc_scheduler_command(NSIQ); + } + + return -EINVAL; +} + +int mc_scheduler_suspend(void) +{ + return mc_scheduler_pm_command(SUSPEND); +} + +int mc_scheduler_resume(void) +{ + return mc_scheduler_pm_command(RESUME); +} + +/* + * This thread, and only this thread, schedules the SWd. Hence, reading the idle + * status and its associated timeout is safe from race conditions. + */ +static int tee_scheduler(void *arg) +{ + int timeslice = 0; /* Actually scheduling period */ + int ret = 0; + + while (1) { + s32 timeout_ms = -1; + bool pm_request = false; + + if (sched_ctx.suspended || nq_get_idle_timeout(&timeout_ms)) { + /* If timeout is 0 we keep scheduling the SWd */ + if (!timeout_ms) { + mc_scheduler_command(NSIQ); + } else { + bool infinite_timeout = timeout_ms < 0; + + if (timeout_ms < 0 || + timeout_ms > DEFAULT_TIMEOUT_MS) + timeout_ms = DEFAULT_TIMEOUT_MS; + + if (!wait_for_completion_timeout( + &sched_ctx.idle_complete, + msecs_to_jiffies(timeout_ms))) { + if (infinite_timeout) + continue; + + /* Timed out, force SWd schedule */ + mc_scheduler_command(NSIQ); + } + } + } + + if (kthread_should_stop() || !sched_ctx.thread_run) + break; + + /* Get requested command if any */ + mutex_lock(&sched_ctx.request_mutex); + if (sched_ctx.request == YIELD) + /* Yield forced: increment timeslice */ + timeslice++; + else if (sched_ctx.request >= NSIQ) { + /* Force N_SIQ, also to suspend/resume SWd */ + timeslice = 0; + if (sched_ctx.request == SUSPEND) { + nq_suspend(); + pm_request = true; + } else if (sched_ctx.request == RESUME) { + nq_resume(); + pm_request = true; + } + } + + if (g_ctx.f_time) + nq_update_time(); + + sched_ctx.request = NONE; + mutex_unlock(&sched_ctx.request_mutex); + + /* Reset timeout so we don't loop if SWd halted */ + nq_reset_idle_timeout(); + if (timeslice--) { + /* Resume SWd from where it was */ + ret = mc_fc_yield(); + } else { + timeslice = SCHEDULING_FREQ; + /* Call SWd scheduler */ + ret = mc_fc_nsiq(); + } + + /* Always flush log buffer after the SWd has run */ + mc_logging_run(); + if (ret) + break; + + /* Should have suspended by now if requested */ + mutex_lock(&sched_ctx.request_mutex); + if (pm_request) { + sched_ctx.suspended = nq_suspended(); + complete(&sched_ctx.sleep_complete); + } + + mutex_unlock(&sched_ctx.request_mutex); + + /* Flush pending notifications if possible */ + if (nq_notifications_flush()) + complete(&sched_ctx.idle_complete); + } + + mc_dev_devel("exit, ret is %d", ret); + return ret; +} + +int mc_scheduler_start(void) +{ + sched_ctx.thread_run = true; + sched_ctx.thread = kthread_run(tee_scheduler, NULL, "tee_scheduler"); + if (IS_ERR(sched_ctx.thread)) { + mc_dev_err("tee_scheduler thread creation failed"); + return PTR_ERR(sched_ctx.thread); + } + + nq_register_scheduler(mc_dev_command); + complete(&sched_ctx.idle_complete); + return 0; +} + +void mc_scheduler_stop(void) +{ + nq_register_scheduler(NULL); + sched_ctx.thread_run = false; + complete(&sched_ctx.idle_complete); + kthread_stop(sched_ctx.thread); +} + +int mc_scheduler_init(void) +{ + init_completion(&sched_ctx.idle_complete); + init_completion(&sched_ctx.sleep_complete); + mutex_init(&sched_ctx.sleep_mutex); + mutex_init(&sched_ctx.request_mutex); + return 0; +} diff --git a/drivers/gud/MobiCoreDriver/scheduler.h b/drivers/gud/MobiCoreDriver/scheduler.h new file mode 100644 index 000000000000..c3c17f1c9017 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/scheduler.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef __MC_SCHEDULER_H__ +#define __MC_SCHEDULER_H__ + +int mc_scheduler_init(void); +static inline void mc_scheduler_exit(void) {} +int mc_scheduler_start(void); +void mc_scheduler_stop(void); +int mc_scheduler_suspend(void); +int mc_scheduler_resume(void); + +#endif /* __MC_SCHEDULER_H__ */ diff --git a/drivers/gud/MobiCoreDriver/session.c b/drivers/gud/MobiCoreDriver/session.c new file mode 100644 index 000000000000..1251edfe8407 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/session.c @@ -0,0 +1,939 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* sockfd_lookup */ +#include +#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE +#include /* local_clock */ +#include /* put_task_struct */ +#endif + +#include "public/mc_user.h" +#include "public/mc_admin.h" + +#if KERNEL_VERSION(3, 5, 0) <= LINUX_VERSION_CODE +#include +#else +#define kuid_t uid_t +#define kgid_t gid_t +#define KGIDT_INIT(value) ((kgid_t)value) + +static inline uid_t __kuid_val(kuid_t uid) +{ + return uid; +} + +static inline gid_t __kgid_val(kgid_t gid) +{ + return gid; +} + +static inline bool gid_eq(kgid_t left, kgid_t right) +{ + return __kgid_val(left) == __kgid_val(right); +} + +static inline bool gid_gt(kgid_t left, kgid_t right) +{ + return __kgid_val(left) > __kgid_val(right); +} + +static inline bool gid_lt(kgid_t left, kgid_t right) +{ + return __kgid_val(left) < __kgid_val(right); +} +#endif +#include "main.h" +#include "mmu.h" +#include "mcp.h" +#include "client.h" /* *cbuf* */ +#include "session.h" +#include "mci/mcimcp.h" /* WSM_INVALID */ + +#define SHA1_HASH_SIZE 20 + +static int wsm_create(struct tee_session *session, struct tee_wsm *wsm, + const struct mc_ioctl_buffer *buf, int client_fd) +{ + if (wsm->in_use) { + mc_dev_err("wsm already in use"); + return -EINVAL; + } + + wsm->mmu = client_mmu_create(session->client, buf, &wsm->cbuf, + client_fd); + if (IS_ERR(wsm->mmu)) + return PTR_ERR(wsm->mmu); + + /* Increment debug counter */ + atomic_inc(&g_ctx.c_wsms); + wsm->va = buf->va; + wsm->len = buf->len; + wsm->flags = buf->flags; + wsm->in_use = true; + return 0; +} + +/* + * Free a WSM object, must be called under the session's wsms_lock + */ +static void wsm_free(struct tee_session *session, struct tee_wsm *wsm) +{ + if (!wsm->in_use) { + mc_dev_err("wsm not in use"); + return; + } + + /* Free MMU table */ + client_mmu_free(session->client, wsm->va, wsm->mmu, wsm->cbuf); + /* Delete wsm object */ + mc_dev_devel("freed wsm %p: mmu %p cbuf %p va %lx len %u sva %x", + wsm, wsm->mmu, wsm->cbuf, wsm->va, wsm->len, wsm->sva); + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_wsms); + wsm->in_use = false; +} + +#if KERNEL_VERSION(4, 6, 0) <= LINUX_VERSION_CODE +static int hash_path_and_data(struct task_struct *task, u8 *hash, + const void *data, unsigned int data_len) +{ + struct mm_struct *mm = task->mm; + struct crypto_shash *tfm; + char *buf; + char *path; + unsigned int path_len; + int ret = 0; + + buf = (char *)__get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + down_read(&mm->mmap_sem); + if (!mm->exe_file) { + ret = -ENOENT; + goto end; + } + + path = d_path(&mm->exe_file->f_path, buf, PAGE_SIZE); + if (IS_ERR(path)) { + ret = PTR_ERR(path); + goto end; + } + + mc_dev_devel("process path ="); + { + char *c; + + for (c = path; *c; c++) + mc_dev_devel("%c %d", *c, *c); + } + + path_len = (unsigned int)strnlen(path, PAGE_SIZE); + mc_dev_devel("path_len = %u", path_len); + /* Compute hash of path */ + tfm = crypto_alloc_shash("sha1", 0, 0); + if (IS_ERR(tfm)) { + ret = PTR_ERR(tfm); + mc_dev_err("cannot allocate shash: %d", ret); + goto end; + } + + { + SHASH_DESC_ON_STACK(desc, tfm); + + desc->tfm = tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + + crypto_shash_init(desc); + crypto_shash_update(desc, (u8 *)path, path_len); + if (data) { + mc_dev_devel("hashing additional data"); + crypto_shash_update(desc, data, data_len); + } + + crypto_shash_final(desc, hash); + shash_desc_zero(desc); + } + + crypto_free_shash(tfm); + +end: + up_read(&mm->mmap_sem); + free_page((unsigned long)buf); + + return ret; +} +#else +static int hash_path_and_data(struct task_struct *task, u8 *hash, + const void *data, unsigned int data_len) +{ + struct mm_struct *mm = task->mm; + struct hash_desc desc; + struct scatterlist sg; + char *buf; + char *path; + unsigned int path_len; + int ret = 0; + + buf = (char *)__get_free_page(GFP_KERNEL); + if (!buf) + return -ENOMEM; + + down_read(&mm->mmap_sem); + if (!mm->exe_file) { + ret = -ENOENT; + goto end; + } + + path = d_path(&mm->exe_file->f_path, buf, PAGE_SIZE); + if (IS_ERR(path)) { + ret = PTR_ERR(path); + goto end; + } + + mc_dev_devel("process path ="); + { + char *c; + + for (c = path; *c; c++) + mc_dev_devel("%c %d", *c, *c); + } + + path_len = (unsigned int)strnlen(path, PAGE_SIZE); + mc_dev_devel("path_len = %u", path_len); + /* Compute hash of path */ + desc.tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc.tfm)) { + ret = PTR_ERR(desc.tfm); + mc_dev_devel("could not alloc hash = %d", ret); + goto end; + } + + desc.flags = 0; + sg_init_one(&sg, path, path_len); + crypto_hash_init(&desc); + crypto_hash_update(&desc, &sg, path_len); + if (data) { + mc_dev_devel("current process path: hashing additional data"); + sg_init_one(&sg, data, data_len); + crypto_hash_update(&desc, &sg, data_len); + } + + crypto_hash_final(&desc, hash); + crypto_free_hash(desc.tfm); + +end: + up_read(&mm->mmap_sem); + free_page((unsigned long)buf); + + return ret; +} +#endif + +#if KERNEL_VERSION(4, 9, 0) <= LINUX_VERSION_CODE +#define GROUP_AT(gi, i) ((gi)->gid[i]) +#endif + +/* + * groups_search is not EXPORTed so copied from kernel/groups.c + * a simple bsearch + */ +static int has_group(const struct cred *cred, gid_t id_gid) +{ + const struct group_info *group_info = cred->group_info; + unsigned int left, right; + kgid_t gid = KGIDT_INIT(id_gid); + + if (gid_eq(gid, cred->fsgid) || gid_eq(gid, cred->egid)) + return 1; + + if (!group_info) + return 0; + + left = 0; + right = group_info->ngroups; + while (left < right) { + unsigned int mid = (left + right) / 2; + + if (gid_gt(gid, GROUP_AT(group_info, mid))) + left = mid + 1; + else if (gid_lt(gid, GROUP_AT(group_info, mid))) + right = mid; + else + return 1; + } + return 0; +} + +static int check_prepare_identity(const struct mc_identity *identity, + struct identity *mcp_identity, + struct task_struct *task) +{ + struct mc_identity *mcp_id = (struct mc_identity *)mcp_identity; + u8 hash[SHA1_HASH_SIZE]; + bool application = false; + const void *data; + unsigned int data_len; + + /* Copy login type */ + mcp_identity->login_type = identity->login_type; + + if (identity->login_type == LOGIN_PUBLIC || + identity->login_type == TEEC_TT_LOGIN_KERNEL) + return 0; + + /* Mobicore doesn't support GP client authentication. */ + if (!g_ctx.f_client_login) { + mc_dev_err("Unsupported login type %x", identity->login_type); + return -EINVAL; + } + + /* Fill in uid field */ + if (identity->login_type == LOGIN_USER || + identity->login_type == LOGIN_USER_APPLICATION) { + /* Set euid and ruid of the process. */ + mcp_id->uid.euid = __kuid_val(task_euid(task)); + mcp_id->uid.ruid = __kuid_val(task_uid(task)); + } + + /* Check gid field */ + if (identity->login_type == LOGIN_GROUP || + identity->login_type == LOGIN_GROUP_APPLICATION) { + const struct cred *cred = __task_cred(task); + + /* + * Check if gid is one of: egid of the process, its rgid or one + * of its supplementary groups + */ + if (!has_group(cred, identity->gid)) { + mc_dev_err("group %d not allowed", identity->gid); + return -EACCES; + } + + mc_dev_devel("group %d found", identity->gid); + mcp_id->gid = identity->gid; + } + + switch (identity->login_type) { + case LOGIN_PUBLIC: + case LOGIN_USER: + case LOGIN_GROUP: + break; + case LOGIN_APPLICATION: + application = true; + data = NULL; + data_len = 0; + break; + case LOGIN_USER_APPLICATION: + application = true; + data = &mcp_id->uid; + data_len = sizeof(mcp_id->uid); + break; + case LOGIN_GROUP_APPLICATION: + application = true; + data = &identity->gid; + data_len = sizeof(identity->gid); + break; + default: + /* Any other login_type value is invalid. */ + mc_dev_err("Invalid login type %d", identity->login_type); + return -EINVAL; + } + + if (application) { + int ret = hash_path_and_data(task, hash, data, data_len); + + if (ret) { + mc_dev_devel("hash calculation returned %d", ret); + return ret; + } + + memcpy(&mcp_id->login_data, hash, sizeof(mcp_id->login_data)); + } + + return 0; +} + +/* + * The proxy gives us the fd of its server-side socket, so we can find out the + * PID of its client. put_task_struct() must be called to free the resource. + */ +static struct task_struct *get_task_struct_from_client_fd(int client_fd) +{ + struct task_struct *task = NULL; + struct socket *sock; + int err; + + if (client_fd < 0) { + get_task_struct(current); + return current; + } + + sock = sockfd_lookup(client_fd, &err); + if (!sock) + return NULL; + + if (sock->sk && sock->sk->sk_peer_pid) { + rcu_read_lock(); + task = pid_task(sock->sk->sk_peer_pid, PIDTYPE_PID); + get_task_struct(task); + rcu_read_unlock(); + } + + sockfd_put(sock); + return task; +} + +/* + * Create a session object. + * Note: object is not attached to client yet. + */ +struct tee_session *session_create(struct tee_client *client, bool is_gp, + struct mc_identity *identity, int client_fd) +{ + struct tee_session *session; + struct identity mcp_identity; + int i; + + if (is_gp) { + /* Check identity method and data. */ + struct task_struct *task; + int ret; + + task = get_task_struct_from_client_fd(client_fd); + if (!task) { + mc_dev_err("can't get task from client fd %d", + client_fd); + return ERR_PTR(-EINVAL); + } + + ret = check_prepare_identity(identity, &mcp_identity, task); + put_task_struct(task); + if (ret) + return ERR_PTR(ret); + } + + /* Allocate session object */ + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) + return ERR_PTR(-ENOMEM); + + /* Increment debug counter */ + atomic_inc(&g_ctx.c_sessions); + /* Initialise object members */ + if (is_gp) + iwp_session_init(&session->iwp_session, &mcp_identity); + else + mcp_session_init(&session->mcp_session); + + client_get(client); + session->client = client; + kref_init(&session->kref); + INIT_LIST_HEAD(&session->list); + mutex_init(&session->wsms_lock); + for (i = 0; i < MC_MAP_MAX; i++) + session->wsms_lru[i] = &session->wsms[i]; + mc_dev_devel("created session %p: client %p", + session, session->client); + return session; +} + +int session_open(struct tee_session *session, const struct tee_object *obj, + const struct tee_mmu *obj_mmu, uintptr_t tci, size_t len, + int client_fd) +{ + struct mcp_buffer_map map; + + if (len > BUFFER_LENGTH_MAX) + return -EINVAL; + + tee_mmu_buffer(obj_mmu, &map); + /* Create wsm object for tci */ + if (tci && len) { + struct tee_wsm *wsm = &session->tci; + struct mcp_buffer_map tci_map; + struct mc_ioctl_buffer buf; + int ret = 0; + + buf.va = tci; + buf.len = len; + buf.flags = MC_IO_MAP_INPUT_OUTPUT; + buf.sva = 0; + ret = wsm_create(session, wsm, &buf, client_fd); + if (ret) + return ret; + + mc_dev_devel("created tci: mmu %p cbuf %p va %lx len %u", + wsm->mmu, wsm->cbuf, wsm->va, wsm->len); + + tee_mmu_buffer(wsm->mmu, &tci_map); + ret = mcp_open_session(&session->mcp_session, obj, &map, + &tci_map); + if (ret) { + wsm_free(session, wsm); + return ret; + } + + return 0; + } + + if (tci || len) { + mc_dev_devel("Tci pointer and length are incoherent"); + return -EINVAL; + } + + return mcp_open_session(&session->mcp_session, obj, &map, NULL); +} + +/* + * Close session and unreference session object. + * Session object is assumed to have been removed from main list, which means + * that session_close cannot be called anymore. + */ +int session_close(struct tee_session *session) +{ + int ret; + + if (nq_session_is_gp(&session->nq_session)) { + ret = iwp_close_session(&session->iwp_session); + if (!ret) + mc_dev_devel("closed GP session %x", + session->iwp_session.sid); + } else { + ret = mcp_close_session(&session->mcp_session); + if (!ret) + mc_dev_devel("closed MC session %x", + session->mcp_session.sid); + } + return ret; +} + +/* + * Free session object and all objects it contains (wsm). + */ +static void session_release(struct kref *kref) +{ + struct tee_session *session; + int i; + + /* Remove remaining shared buffers (unmapped in SWd by mcp_close) */ + session = container_of(kref, struct tee_session, kref); + for (i = 0; i < MC_MAP_MAX; i++) { + if (!session->wsms[i].in_use) + continue; + + mc_dev_devel("session %p: free wsm #%d", session, i); + wsm_free(session, &session->wsms[i]); + /* Buffer unmapped by SWd */ + atomic_dec(&g_ctx.c_maps); + } + + if (session->tci.in_use) { + mc_dev_devel("session %p: free tci", session); + wsm_free(session, &session->tci); + } + + if (nq_session_is_gp(&session->nq_session)) + mc_dev_devel("freed GP session %p: client %p id %x", session, + session->client, session->iwp_session.sid); + else + mc_dev_devel("freed MC session %p: client %p id %x", session, + session->client, session->mcp_session.sid); + + client_put(session->client); + kfree(session); + /* Decrement debug counter */ + atomic_dec(&g_ctx.c_sessions); +} + +/* + * Unreference session. + * Free session object if reference reaches 0. + */ +int session_put(struct tee_session *session) +{ + return kref_put(&session->kref, session_release); +} + +/* + * Session is to be removed from NWd records as SWd is dead + */ +int session_kill(struct tee_session *session) +{ + mcp_kill_session(&session->mcp_session); + return session_put(session); +} + +/* + * Send a notification to TA + */ +int session_notify_swd(struct tee_session *session) +{ + if (!session) { + mc_dev_devel("Session pointer is null"); + return -EINVAL; + } + + return mcp_notify(&session->mcp_session); +} + +/* + * Read and clear last notification received from TA + */ +s32 session_exitcode(struct tee_session *session) +{ + return mcp_session_exitcode(&session->mcp_session); +} + +static int wsm_debug_structs(struct kasnprintf_buf *buf, struct tee_wsm *wsm, + int no) +{ + ssize_t ret; + + if (!wsm->in_use) + return 0; + + ret = kasnprintf(buf, "\t\t"); + if (no < 0) + ret = kasnprintf(buf, "tci %pK: cbuf %pK va %pK len %u\n", + wsm, wsm->cbuf, (void *)wsm->va, wsm->len); + else if (wsm->in_use) + ret = kasnprintf(buf, + "wsm #%d: cbuf %pK va %pK len %u sva %x\n", + no, wsm->cbuf, (void *)wsm->va, wsm->len, + wsm->sva); + + if (ret < 0) + return ret; + + if (wsm->mmu) { + ret = tee_mmu_debug_structs(buf, wsm->mmu); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Share buffers with SWd and add corresponding WSM objects to session. + * This may involve some re-use or cleanup of inactive mappings. + */ +int session_map(struct tee_session *session, struct mc_ioctl_buffer *buf, + int client_fd) +{ + struct tee_wsm *wsm; + struct mcp_buffer_map map; + int i, ret; + + mutex_lock(&session->wsms_lock); + /* Look for an available slot in the session WSMs array */ + for (i = 0; i < MC_MAP_MAX; i++) + if (!session->wsms[i].in_use) + break; + + if (i == MC_MAP_MAX) { + ret = -EPERM; + mc_dev_devel("no available WSM slot in session %x", + session->mcp_session.sid); + goto out; + } + + wsm = &session->wsms[i]; + ret = wsm_create(session, wsm, buf, client_fd); + if (ret) { + mc_dev_devel("maps[%d] va=%llx create failed: %d", + i, buf->va, ret); + goto out; + } + + mc_dev_devel("created wsm #%d: mmu %p cbuf %p va %lx len %u", + i, wsm->mmu, wsm->cbuf, wsm->va, wsm->len); + tee_mmu_buffer(wsm->mmu, &map); + mc_dev_devel("maps[%d] va=%llx: t:%u a:%llx o:%u l:%u", i, buf->va, + map.type, map.phys_addr, map.offset, map.length); + + ret = mcp_map(session->mcp_session.sid, &map); + if (ret) { + wsm_free(session, wsm); + } else { + buf->sva = map.secure_va; + wsm->sva = map.secure_va; + } + +out: + mutex_unlock(&session->wsms_lock); + mc_dev_devel("ret=%d", ret); + return ret; +} + +/* + * In theory, stop sharing buffers with the SWd. In fact, mark them inactive. + */ +int session_unmap(struct tee_session *session, + const struct mc_ioctl_buffer *buf) +{ + struct tee_wsm *wsm; + struct mcp_buffer_map map; + int i, ret = -EINVAL; + + mutex_lock(&session->wsms_lock); + /* Look for buffer in the session WSMs array */ + for (i = 0; i < MC_MAP_MAX; i++) + if (session->wsms[i].in_use && + buf->va == session->wsms[i].va && + buf->len == session->wsms[i].len && + buf->sva == session->wsms[i].sva) + break; + + if (i == MC_MAP_MAX) { + ret = -EINVAL; + mc_dev_devel("maps[%d] va=%llx sva=%llx not found", + i, buf[i].va, buf[i].sva); + goto out; + } + + wsm = &session->wsms[i]; + tee_mmu_buffer(wsm->mmu, &map); + map.secure_va = wsm->sva; + ret = mcp_unmap(session->mcp_session.sid, &map); + if (!ret) + wsm_free(session, wsm); + +out: + mutex_unlock(&session->wsms_lock); + return ret; +} + +/* + * Sleep until next notification from SWd. + */ +int session_waitnotif(struct tee_session *session, s32 timeout, + bool silent_expiry) +{ + return mcp_session_waitnotif(&session->mcp_session, timeout, + silent_expiry); +} + +static void unmap_gp_bufs(struct tee_session *session, + struct iwp_buffer_map *maps) +{ + int i; + + /* Create WSMs from bufs */ + mutex_lock(&session->wsms_lock); + for (i = 0; i < MC_MAP_MAX; i++) { + if (session->wsms[i].in_use) + wsm_free(session, &session->wsms[i]); + + if (maps[i].sva) + client_put_cwsm_sva(session->client, maps[i].sva); + } + mutex_unlock(&session->wsms_lock); +} + +static int map_gp_bufs(struct tee_session *session, + const struct mc_ioctl_buffer *bufs, + struct gp_shared_memory **parents, + struct iwp_buffer_map *maps, int client_fd) +{ + int i, ret = 0; + + /* Create WSMs from bufs */ + mutex_lock(&session->wsms_lock); + for (i = 0; i < MC_MAP_MAX; i++) { + /* Reset reference for temporary memory */ + maps[i].map.phys_addr = 0; + /* Reset reference for registered memory */ + maps[i].sva = 0; + if (bufs[i].va) { + /* Temporary memory, needs mapping */ + ret = wsm_create(session, &session->wsms[i], &bufs[i], + client_fd); + if (ret) { + mc_dev_devel( + "maps[%d] va=%llx create failed: %d", + i, bufs[i].va, ret); + break; + } + + tee_mmu_buffer(session->wsms[i].mmu, &maps[i].map); + } else if (parents[i]) { + /* Registered memory, already mapped */ + maps[i].sva = client_get_cwsm_sva(session->client, + parents[i]); + if (!maps[i].sva) { + ret = -EINVAL; + mc_dev_devel("couldn't find shared mem"); + break; + } + } + } + mutex_unlock(&session->wsms_lock); + + /* Failed above */ + if (i < MC_MAP_MAX) + unmap_gp_bufs(session, maps); + + return ret; +} + +int session_gp_open_session(struct tee_session *session, + const struct tee_object *obj, + const struct tee_mmu *obj_mmu, + struct gp_operation *operation, + struct gp_return *gp_ret, + int client_fd) +{ + /* TEEC_MEMREF_TEMP_* buffers to map */ + struct mc_ioctl_buffer bufs[MC_MAP_MAX]; + struct iwp_buffer_map maps[MC_MAP_MAX]; + struct gp_shared_memory *parents[MC_MAP_MAX]; + struct mcp_buffer_map obj_map; + struct client_gp_operation client_operation; + int ret = 0; + + ret = iwp_open_session_prepare(&session->iwp_session, obj, operation, + bufs, parents, gp_ret); + if (ret) + return ret; + + /* Create WSMs from bufs */ + ret = map_gp_bufs(session, bufs, parents, maps, client_fd); + if (ret) { + iwp_open_session_abort(&session->iwp_session); + return iwp_set_ret(ret, gp_ret); + } + + /* Tell client about operation */ + client_operation.started = operation->started; + client_operation.slot = iwp_session_slot(&session->iwp_session); + client_operation.cancelled = false; + if (!client_gp_operation_add(session->client, &client_operation)) { + iwp_open_session_abort(&session->iwp_session); + return iwp_set_ret(-ECANCELED, gp_ret); + } + /* Open/call TA */ + tee_mmu_buffer(obj_mmu, &obj_map); + ret = iwp_open_session(&session->iwp_session, operation, &obj_map, maps, + gp_ret); + /* Cleanup */ + client_gp_operation_remove(session->client, &client_operation); + unmap_gp_bufs(session, maps); + return ret; +} + +int session_gp_invoke_command(struct tee_session *session, u32 command_id, + struct gp_operation *operation, + struct gp_return *gp_ret, int client_fd) +{ + /* TEEC_MEMREF_TEMP_* buffers to map */ + struct mc_ioctl_buffer bufs[4]; + struct iwp_buffer_map maps[MC_MAP_MAX]; + struct gp_shared_memory *parents[MC_MAP_MAX]; + struct client_gp_operation client_operation; + int ret = 0; + + ret = iwp_invoke_command_prepare(&session->iwp_session, command_id, + operation, bufs, parents, gp_ret); + if (ret) + return ret; + + /* Create WSMs from bufs */ + ret = map_gp_bufs(session, bufs, parents, maps, client_fd); + if (ret) { + iwp_invoke_command_abort(&session->iwp_session); + return iwp_set_ret(ret, gp_ret); + } + + /* Tell client about operation */ + client_operation.started = operation->started; + client_operation.slot = iwp_session_slot(&session->iwp_session); + client_operation.cancelled = false; + if (!client_gp_operation_add(session->client, &client_operation)) { + iwp_invoke_command_abort(&session->iwp_session); + return iwp_set_ret(-ECANCELED, gp_ret); + } + /* Call TA */ + ret = iwp_invoke_command(&session->iwp_session, operation, maps, + gp_ret); + /* Cleanup */ + client_gp_operation_remove(session->client, &client_operation); + unmap_gp_bufs(session, maps); + return ret; +} + +int session_gp_request_cancellation(u32 slot) +{ + return iwp_request_cancellation(slot); +} + +int session_debug_structs(struct kasnprintf_buf *buf, + struct tee_session *session, bool is_closing) +{ + const char *type; + u32 session_id; + s32 exit_code; + int i, ret; + + if (nq_session_is_gp(&session->nq_session)) { + session_id = session->iwp_session.sid; + exit_code = 0; + type = "GP"; + } else { + session_id = session->mcp_session.sid; + exit_code = mcp_session_exitcode(&session->mcp_session); + type = "MC"; + } + + ret = kasnprintf(buf, "\tsession %pK [%d]: %4x %s ec %d%s\n", + session, kref_read(&session->kref), session_id, type, + exit_code, is_closing ? " " : ""); + if (ret < 0) + return ret; + + /* TCI */ + if (session->tci.in_use) { + ret = wsm_debug_structs(buf, &session->tci, -1); + if (ret < 0) + return ret; + } + + /* WMSs */ + mutex_lock(&session->wsms_lock); + for (i = 0; i < MC_MAP_MAX; i++) { + ret = wsm_debug_structs(buf, &session->wsms[i], i); + if (ret < 0) + break; + } + mutex_unlock(&session->wsms_lock); + if (ret < 0) + return ret; + + return 0; +} diff --git a/drivers/gud/MobiCoreDriver/session.h b/drivers/gud/MobiCoreDriver/session.h new file mode 100644 index 000000000000..a272d7b7c665 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/session.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2013-2016 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. + */ + +#ifndef _SESSION_H_ +#define _SESSION_H_ + +#include + +#include "mcp.h" +#include "iwp.h" +#include "nq.h" + +struct tee_object; +struct tee_mmu; +struct mc_ioctl_buffer; + +struct tee_wsm { + /* Buffer NWd address (uva or kva, used only for lookup) */ + uintptr_t va; + /* Buffer length */ + u32 len; + /* Buffer flags */ + u32 flags; + /* Buffer SWd address */ + u32 sva; + union { + /* MMU table */ + struct tee_mmu *mmu; + /* Index of re-used buffer (temporary) */ + int index; + }; + /* Pointer to associated cbuf, if relevant */ + struct cbuf *cbuf; + /* State of this WSM */ + bool in_use; +}; + +struct tee_session { + /* Session descriptor */ + union { + struct nq_session nq_session; + struct mcp_session mcp_session; + struct iwp_session iwp_session; + }; + /* Owner */ + struct tee_client *client; + /* Number of references kept to this object */ + struct kref kref; + /* WSM for the TCI */ + struct tee_wsm tci; + /* The list entry to attach to session list of owner */ + struct list_head list; + /* Session WSMs lock */ + struct mutex wsms_lock; + /* WSMs for a session */ + struct tee_wsm wsms[MC_MAP_MAX]; + /* Pointers to WSMs in LRU order (0 is oldest) */ + struct tee_wsm *wsms_lru[MC_MAP_MAX]; +}; + +struct tee_session *session_create(struct tee_client *client, bool is_gp, + struct mc_identity *identity, int client_fd); +int session_open(struct tee_session *session, const struct tee_object *obj, + const struct tee_mmu *obj_mmu, uintptr_t tci, size_t len, + int client_fd); +int session_close(struct tee_session *session); +static inline void session_get(struct tee_session *session) +{ + kref_get(&session->kref); +} + +int session_put(struct tee_session *session); +int session_kill(struct tee_session *session); +int session_map(struct tee_session *session, struct mc_ioctl_buffer *bufs, + int client_fd); +int session_unmap(struct tee_session *session, + const struct mc_ioctl_buffer *bufs); +s32 session_exitcode(struct tee_session *session); +int session_notify_swd(struct tee_session *session); +int session_waitnotif(struct tee_session *session, s32 timeout, + bool silent_expiry); + +int session_gp_open_session(struct tee_session *session, + const struct tee_object *obj, + const struct tee_mmu *obj_mmu, + struct gp_operation *operation, + struct gp_return *gp_ret, + int client_fd); +int session_gp_invoke_command(struct tee_session *session, u32 command_id, + struct gp_operation *operation, + struct gp_return *gp_ret, int client_fd); +int session_gp_request_cancellation(u32 slot); + +int session_debug_structs(struct kasnprintf_buf *buf, + struct tee_session *session, bool is_closing); + +#endif /* _SESSION_H_ */ diff --git a/drivers/gud/MobiCoreDriver/teeclientapi.c b/drivers/gud/MobiCoreDriver/teeclientapi.c new file mode 100644 index 000000000000..fcb2e0a2f882 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/teeclientapi.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include + +#include "public/GP/tee_client_api.h" +#include "public/mc_user.h" + +#include "main.h" +#include "mci/gptci.h" /* Needs stuff from tee_client_api.h or its includes */ +#include "mci/mcinq.h" /* TA termination codes */ +#include "client.h" + +/* Macros */ +#define _TEEC_GET_PARAM_TYPE(t, i) (((t) >> (4 * (i))) & 0xF) + +/* Parameter number */ +#define _TEEC_PARAMETER_NUMBER 4 + +/**teec_shared_memory + * These error codes are still to be decided by GP and as we do not wish to + * expose any part of the GP TAF as of yet, for now they will have to live here + * until we decide what to do about them. + */ +#define TEEC_ERROR_TA_LOCKED 0xFFFF0257 +#define TEEC_ERROR_SD_BLOCKED 0xFFFF0258 +#define TEEC_ERROR_TARGET_KILLED 0xFFFF0259 + +static DECLARE_WAIT_QUEUE_HEAD(operations_wq); + +static void _lib_uuid_to_array(const struct teec_uuid *uuid, u8 *uuid_array) +{ + u8 *identifier_cursor = (u8 *)uuid; + /* offsets and syntax constants. See explanations above */ +#ifdef S_BIG_ENDIAN + u32 offsets = 0; +#else + u32 offsets = 0xF1F1DF13; +#endif + u32 i; + + for (i = 0; i < sizeof(struct teec_uuid); i++) { + /* Two-digit hex number */ + s32 offset = ((s32)((offsets & 0xF) << 28)) >> 28; + u8 number = identifier_cursor[offset]; + + offsets >>= 4; + identifier_cursor++; + + uuid_array[i] = number; + } +} + +static u32 _teec_to_gp_operation(struct teec_operation *teec_op, + struct gp_operation *gp_op) +{ + int i; + int ret = 0; + + for (i = 0; i < _TEEC_PARAMETER_NUMBER; i++) { + switch (_TEEC_GET_PARAM_TYPE(teec_op->param_types, i)) { + case TEEC_VALUE_INPUT: + case TEEC_VALUE_INOUT: + gp_op->params[i].value.a = teec_op->params[i].value.a; + gp_op->params[i].value.b = teec_op->params[i].value.b; + break; + case TEEC_MEMREF_TEMP_INPUT: + case TEEC_MEMREF_TEMP_OUTPUT: + case TEEC_MEMREF_TEMP_INOUT: + gp_op->params[i].tmpref.buffer = + (uintptr_t)teec_op->params[i].tmpref.buffer; + gp_op->params[i].tmpref.size = + teec_op->params[i].tmpref.size; + break; + case TEEC_MEMREF_WHOLE: + case TEEC_MEMREF_PARTIAL_INPUT: + case TEEC_MEMREF_PARTIAL_OUTPUT: + case TEEC_MEMREF_PARTIAL_INOUT: + gp_op->params[i].memref.offset = + teec_op->params[i].memref.offset; + gp_op->params[i].memref.size = + teec_op->params[i].memref.size; + gp_op->params[i].memref.parent.buffer = + (uintptr_t)teec_op->params[i].memref.parent->buffer; + gp_op->params[i].memref.parent.size = + teec_op->params[i].memref.parent->size; + gp_op->params[i].memref.parent.flags = + teec_op->params[i].memref.parent->flags; + break; + case TEEC_NONE: + case TEEC_VALUE_OUTPUT: + break; + default: + ret = -EINVAL; + } + } + gp_op->param_types = teec_op->param_types; + return ret; +} + +static void _teec_from_gp_operation(struct gp_operation *gp_op, + struct teec_operation *teec_op) +{ + int i; + + for (i = 0; i < _TEEC_PARAMETER_NUMBER; i++) { + switch (_TEEC_GET_PARAM_TYPE(gp_op->param_types, i)) { + case TEEC_VALUE_OUTPUT: + case TEEC_VALUE_INOUT: + teec_op->params[i].value.a = gp_op->params[i].value.a; + teec_op->params[i].value.b = gp_op->params[i].value.b; + break; + case TEEC_MEMREF_TEMP_INPUT: + case TEEC_MEMREF_TEMP_OUTPUT: + case TEEC_MEMREF_TEMP_INOUT: + teec_op->params[i].tmpref.size = + gp_op->params[i].tmpref.size; + break; + case TEEC_MEMREF_WHOLE: + break; + case TEEC_MEMREF_PARTIAL_INPUT: + case TEEC_MEMREF_PARTIAL_OUTPUT: + case TEEC_MEMREF_PARTIAL_INOUT: + teec_op->params[i].memref.size = + gp_op->params[i].memref.size; + break; + case TEEC_NONE: + case TEEC_VALUE_INPUT: + break; + default: + break; + } + } +} + +static u32 _teec_convert_error(int errno) +{ + switch (errno) { + case ENOENT: + return TEEC_ERROR_ITEM_NOT_FOUND; + case EACCES: + return TEEC_ERROR_ACCESS_DENIED; + case EINVAL: + return TEEC_ERROR_BAD_PARAMETERS; + case ENOSPC: + return TEEC_ERROR_OUT_OF_MEMORY; + case ECONNREFUSED: + return TEEC_ERROR_SD_BLOCKED; + case ECONNABORTED: + return TEEC_ERROR_TA_LOCKED; + case ECONNRESET: + return TEEC_ERROR_TARGET_KILLED; + case EBUSY: + return TEEC_ERROR_BUSY; + case EKEYREJECTED: + return TEEC_ERROR_SECURITY; + case ETIME: + return TEEC_ERROR_TARGET_DEAD; + default: + return TEEC_ERROR_GENERIC; + } +} + +/* teec_initialize_context: TEEC_SUCCESS, Another error code from Table 4-2 */ +u32 teec_initialize_context(const char *name, struct teec_context *context) +{ + struct tee_client *client; + int ret; + (void)name; + + mc_dev_devel("== %s() ==============", __func__); + + if (!context) { + mc_dev_devel("context is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + + /* Make sure TEE was started */ + ret = mc_wait_tee_start(); + if (ret) { + mc_dev_err("TEE failed to start, now or in the past"); + return TEEC_ERROR_BAD_STATE; + } + + /* Create client */ + client = client_create(true); + if (!client) + return TEEC_ERROR_OUT_OF_MEMORY; + + /* Store client in context */ + context->imp.client = client; + + return TEEC_SUCCESS; +} +EXPORT_SYMBOL(teec_initialize_context); + +/* + * The implementation of this function MUST NOT be able to fail: after this + * function returns the Client Application must be able to consider that the + * Context has been closed + */ +void teec_finalize_context(struct teec_context *context) +{ + mc_dev_devel("== %s() ==============", __func__); + + /* The parameter context MUST point to an initialized TEE Context */ + if (!context) { + mc_dev_devel("context is NULL"); + return; + } + + /* The implementation of this function MUST NOT be able to fail: after + * this function returns the Client Application must be able to + * consider that the Context has been closed + */ + client_close(context->imp.client); + context->imp.client = NULL; +} +EXPORT_SYMBOL(teec_finalize_context); + +/* + * If the return_origin is different from TEEC_ORIGIN_TRUSTED_APP, an error code + * from Table 4-2. If the return_origin is equal to TEEC_ORIGIN_TRUSTED_APP, a + * return code defined by the protocol between the Client Application and the + * Trusted Application + */ +u32 teec_open_session(struct teec_context *context, + struct teec_session *session, + const struct teec_uuid *destination, + u32 connection_method, + const void *connection_data, + struct teec_operation *operation, + u32 *return_origin) +{ + struct mc_uuid_t tauuid; + struct mc_identity identity; + struct tee_client *client = NULL; + struct gp_operation gp_op; + struct gp_return gp_ret; + int ret = 0, timeout; + + mc_dev_devel("== %s() ==============", __func__); + gp_ret.value = TEEC_SUCCESS; + if (return_origin) + *return_origin = TEEC_ORIGIN_API; + + /* The parameter context MUST point to an initialized TEE Context */ + if (!context) { + mc_dev_devel("context is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + + if (!context->imp.client) { + mc_dev_devel("context not initialized"); + return TEEC_ERROR_BAD_PARAMETERS; + } + client = context->imp.client; + + if (!session) { + mc_dev_devel("session is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + + connection_method = TEEC_TT_LOGIN_KERNEL; + session->imp.active = false; + + _lib_uuid_to_array(destination, tauuid.value); + + memset(&gp_op, 0, sizeof(gp_op)); + if (operation) { + operation->imp.session = &session->imp; + ret = _teec_to_gp_operation(operation, &gp_op); + if (ret) + return TEEC_ERROR_BAD_PARAMETERS; + } + + identity.login_type = (enum mc_login_type)connection_method; + + /* Wait for GP loading to be possible, maximum 30s */ + timeout = 30; + do { + ret = client_gp_open_session(client, &session->imp.session_id, + &tauuid, &gp_op, &identity, + &gp_ret, -1); + if (!ret || ret != EAGAIN) + break; + + msleep(1000); + } while (--timeout); + + if (ret || gp_ret.value != TEEC_SUCCESS) { + mc_dev_devel("client_gp_open_session failed(%08x) %08x", ret, + gp_ret.value); + if (ret) + gp_ret.value = _teec_convert_error(-ret); + else if (return_origin) + /* Update origin as it's not the API */ + *return_origin = gp_ret.origin; + } else { + mc_dev_devel(" created session ID %x", session->imp.session_id); + session->imp.context = context->imp; + session->imp.active = true; + if (operation) + _teec_from_gp_operation(&gp_op, operation); + } + + mc_dev_devel(" %s() = 0x%x", __func__, gp_ret.value); + return gp_ret.value; +} +EXPORT_SYMBOL(teec_open_session); + +u32 teec_invoke_command(struct teec_session *session, + u32 command_id, + struct teec_operation *operation, + u32 *return_origin) +{ + struct tee_client *client = NULL; + struct gp_operation gp_op = {0}; + struct gp_return gp_ret = {0}; + int ret = 0; + + mc_dev_devel("== %s() ==============", __func__); + + gp_ret.value = TEEC_SUCCESS; + if (return_origin) + *return_origin = TEEC_ORIGIN_API; + + if (!session) { + mc_dev_devel("session is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + + if (!session->imp.active) { + mc_dev_devel("session is inactive"); + return TEEC_ERROR_BAD_STATE; + } + client = session->imp.context.client; + + if (operation) { + operation->imp.session = &session->imp; + if (_teec_to_gp_operation(operation, &gp_op)) + return TEEC_ERROR_BAD_PARAMETERS; + } else { + gp_op.param_types = 0; + } + + ret = client_gp_invoke_command(client, session->imp.session_id, + command_id, &gp_op, &gp_ret, -1); + + if (ret || gp_ret.value != TEEC_SUCCESS) { + mc_dev_devel("client_gp_invoke_command failed(%08x) %08x", ret, + gp_ret.value); + if (ret) + gp_ret.value = _teec_convert_error(-ret); + else if (return_origin) + /* Update origin as it's not the API */ + *return_origin = gp_ret.origin; + } else if (operation) { + _teec_from_gp_operation(&gp_op, operation); + } + + mc_dev_devel(" %s() = 0x%x", __func__, gp_ret.value); + return gp_ret.value; +} +EXPORT_SYMBOL(teec_invoke_command); + +void teec_close_session(struct teec_session *session) +{ + int ret = 0; + struct tee_client *client = NULL; + + mc_dev_devel("== %s() ==============", __func__); + + /* The implementation MUST do nothing if session is NULL */ + if (!session) { + mc_dev_devel("session is NULL"); + return; + } + client = session->imp.context.client; + + if (session->imp.active) { + ret = client_gp_close_session(client, session->imp.session_id); + + if (ret) + /* continue even in case of error */ + mc_dev_devel("client_gp_close failed(%08x)", ret); + + session->imp.active = false; + } + + mc_dev_devel(" %s() = 0x%x", __func__, ret); +} +EXPORT_SYMBOL(teec_close_session); + +/* + * Implementation note. We handle internally 2 kind of pointers : kernel memory + * (kmalloc, get_pages, ...) and dynamic memory (vmalloc). A global pointer from + * a kernel module has the same format as a vmalloc buffer. However, our code + * cannot detect that, so it considers it a kmalloc buffer. The TA trying to use + * that shared buffer is likely to crash + */ +u32 teec_register_shared_memory(struct teec_context *context, + struct teec_shared_memory *shared_mem) +{ + struct gp_shared_memory memref; + struct gp_return gp_ret; + int ret = 0; + + mc_dev_devel("== %s() ==============", __func__); + + /* The parameter context MUST point to an initialized TEE Context */ + if (!context) { + mc_dev_devel("context is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + /* + * The parameter shared_mem MUST point to the Shared Memory structure + * defining the memory region to register + */ + if (!shared_mem) { + mc_dev_devel("shared_mem is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + /* + * The buffer field MUST point to the memory region to be shared, + * and MUST not be NULL + */ + if (!shared_mem->buffer) { + mc_dev_devel("shared_mem->buffer is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + if (shared_mem->flags & ~TEEC_MEM_INOUT) { + mc_dev_devel("shared_mem->flags is incorrect"); + return TEEC_ERROR_BAD_PARAMETERS; + } + if (!shared_mem->flags) { + mc_dev_devel("shared_mem->flags is incorrect"); + return TEEC_ERROR_BAD_PARAMETERS; + } + + memref.buffer = (uintptr_t)shared_mem->buffer; + memref.flags = shared_mem->flags; + memref.size = shared_mem->size; + ret = client_gp_register_shared_mem(context->imp.client, &memref, + &gp_ret, -1); + + if (ret) + return _teec_convert_error(-ret); + + shared_mem->imp.client = context->imp.client; + shared_mem->imp.implementation_allocated = false; + + return TEEC_SUCCESS; +} +EXPORT_SYMBOL(teec_register_shared_memory); + +u32 teec_allocate_shared_memory(struct teec_context *context, + struct teec_shared_memory *shared_mem) +{ + struct gp_shared_memory memref; + struct gp_return gp_ret; + int ret = 0; + + /* No connection to "context"? */ + mc_dev_devel("== %s() ==============", __func__); + + /* The parameter context MUST point to an initialized TEE Context */ + if (!context) { + mc_dev_devel("context is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + /* + * The parameter shared_mem MUST point to the Shared Memory structure + * defining the memory region to register + */ + if (!shared_mem) { + mc_dev_devel("shared_mem is NULL"); + return TEEC_ERROR_BAD_PARAMETERS; + } + if (shared_mem->flags & ~TEEC_MEM_INOUT) { + mc_dev_devel("shared_mem->flags is incorrect"); + return TEEC_ERROR_BAD_PARAMETERS; + } + if (!shared_mem->flags) { + mc_dev_devel("shared_mem->flags is incorrect"); + return TEEC_ERROR_BAD_PARAMETERS; + } + + shared_mem->buffer = vmalloc(shared_mem->size); + if (!shared_mem->buffer) + return TEEC_ERROR_OUT_OF_MEMORY; + + memref.buffer = (uintptr_t)shared_mem->buffer; + memref.flags = shared_mem->flags; + memref.size = shared_mem->size; + ret = client_gp_register_shared_mem(context->imp.client, &memref, + &gp_ret, -1); + + if (ret) { + vfree(shared_mem->buffer); + shared_mem->buffer = NULL; + shared_mem->size = 0; + return _teec_convert_error(-ret); + } + + shared_mem->imp.client = context->imp.client; + shared_mem->imp.implementation_allocated = true; + + return TEEC_SUCCESS; +} +EXPORT_SYMBOL(teec_allocate_shared_memory); + +void teec_release_shared_memory(struct teec_shared_memory *shared_mem) +{ + struct gp_shared_memory memref; + + /* No connection to "context"? */ + mc_dev_devel("== %s() ==============", __func__); + + /* The implementation MUST do nothing if shared_mem is NULL */ + if (!shared_mem) { + mc_dev_devel("shared_mem is NULL"); + return; + } + + memref.buffer = (uintptr_t)shared_mem->buffer; + memref.flags = shared_mem->flags; + memref.size = shared_mem->size; + (void)client_gp_release_shared_mem(shared_mem->imp.client, &memref); + + /* + * For a memory buffer allocated using teec_allocate_shared_memory the + * Implementation MUST free the underlying memory + */ + if (shared_mem->imp.implementation_allocated) { + if (shared_mem->buffer) { + vfree(shared_mem->buffer); + shared_mem->buffer = NULL; + shared_mem->size = 0; + } + } +} +EXPORT_SYMBOL(teec_release_shared_memory); + +void teec_request_cancellation(struct teec_operation *operation) +{ + struct teec_session_imp *session; + int ret; + + mc_dev_devel("== %s() ==============", __func__); + + ret = wait_event_interruptible(operations_wq, operation->started); + if (ret == -ERESTARTSYS) { + mc_dev_devel("signal received"); + return; + } + + mc_dev_devel("operation->started changed from 0 to %d", + operation->started); + + if (operation->started > 1) { + mc_dev_devel("the operation has finished"); + return; + } + + session = operation->imp.session; + operation->started = 2; + wake_up_interruptible(&operations_wq); + + if (!session->active) { + mc_dev_devel("Corresponding session is not active"); + return; + } + + /* TODO: handle cancellation */ + + /* Signal the Trustlet */ + ret = client_notify_session(session->context.client, + session->session_id); + if (ret) + mc_dev_devel("Notify failed: %d", ret); +} +EXPORT_SYMBOL(teec_request_cancellation); diff --git a/drivers/gud/MobiCoreDriver/user.c b/drivers/gud/MobiCoreDriver/user.c new file mode 100644 index 000000000000..8cbd63f88bf9 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/user.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2013-2018 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 +#include +#include +#include +#include /* struct vm_area_struct */ +#include + +#include "public/mc_user.h" + +#include "main.h" +#include "user.h" +#include "client.h" +#include "mcp.h" /* mcp_get_version */ + +/* + * Get client object from file pointer + */ +static inline struct tee_client *get_client(struct file *file) +{ + return (struct tee_client *)file->private_data; +} + +/* + * 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 user_open(struct inode *inode, struct file *file) +{ + struct tee_client *client; + + /* Create client */ + mc_dev_devel("from %s (%d)", current->comm, current->pid); + client = client_create(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 user_release(struct inode *inode, struct file *file) +{ + struct tee_client *client = get_client(file); + + /* Close client */ + mc_dev_devel("from %s (%d)", current->comm, current->pid); + if (!client) + return -EPROTO; + + /* Detach client from user file */ + file->private_data = NULL; + + /* Destroy client, including remaining sessions */ + client_close(client); + return 0; +} + +/* + * 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 user_ioctl(struct file *file, unsigned int id, unsigned long arg) +{ + struct tee_client *client = get_client(file); + int __user *uarg = (int __user *)arg; + int ret = -EINVAL; + + mc_dev_devel("%u from %s", _IOC_NR(id), current->comm); + + if (!client) + return -EPROTO; + + if (ioctl_check_pointer(id, uarg)) + return -EFAULT; + + switch (id) { + case MC_IO_HAS_SESSIONS: + /* Freeze the client */ + if (client_has_sessions(client)) + ret = -ENOTEMPTY; + else + ret = 0; + break; + + case MC_IO_OPEN_SESSION: { + struct mc_ioctl_open_session session; + + if (copy_from_user(&session, uarg, sizeof(session))) { + ret = -EFAULT; + break; + } + + ret = client_open_session(client, &session.sid, &session.uuid, + session.tci, session.tcilen, + session.is_gp_uuid, + &session.identity, -1); + if (ret) + break; + + if (copy_to_user(uarg, &session, sizeof(session))) { + ret = -EFAULT; + client_remove_session(client, session.sid); + break; + } + break; + } + case MC_IO_OPEN_TRUSTLET: { + struct mc_ioctl_open_trustlet trustlet; + + if (copy_from_user(&trustlet, uarg, sizeof(trustlet))) { + ret = -EFAULT; + break; + } + + ret = client_open_trustlet(client, &trustlet.sid, trustlet.spid, + trustlet.buffer, trustlet.tlen, + trustlet.tci, trustlet.tcilen, -1); + if (ret) + break; + + if (copy_to_user(uarg, &trustlet, sizeof(trustlet))) { + ret = -EFAULT; + client_remove_session(client, trustlet.sid); + break; + } + break; + } + case MC_IO_CLOSE_SESSION: { + u32 sid = (u32)arg; + + ret = client_remove_session(client, sid); + break; + } + case MC_IO_NOTIFY: { + u32 sid = (u32)arg; + + ret = client_notify_session(client, sid); + break; + } + case MC_IO_WAIT: { + struct mc_ioctl_wait wait; + + if (copy_from_user(&wait, uarg, sizeof(wait))) { + ret = -EFAULT; + break; + } + ret = client_waitnotif_session(client, wait.sid, wait.timeout, + wait.partial); + break; + } + case MC_IO_MAP: { + struct mc_ioctl_map map; + + if (copy_from_user(&map, uarg, sizeof(map))) { + ret = -EFAULT; + break; + } + + ret = client_map_session_wsms(client, map.sid, &map.buf, -1); + if (ret) + break; + + /* Fill in return struct */ + if (copy_to_user(uarg, &map, sizeof(map))) { + ret = -EFAULT; + client_unmap_session_wsms(client, map.sid, &map.buf); + break; + } + break; + } + case MC_IO_UNMAP: { + struct mc_ioctl_map map; + + if (copy_from_user(&map, uarg, sizeof(map))) { + ret = -EFAULT; + break; + } + + ret = client_unmap_session_wsms(client, map.sid, &map.buf); + break; + } + case MC_IO_ERR: { + struct mc_ioctl_geterr __user *uerr = + (struct mc_ioctl_geterr __user *)uarg; + u32 sid; + s32 exit_code; + + if (get_user(sid, &uerr->sid)) { + ret = -EFAULT; + break; + } + + ret = client_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_GP_INITIALIZE_CONTEXT: { + struct mc_ioctl_gp_initialize_context context; + + if (copy_from_user(&context, uarg, sizeof(context))) { + ret = -EFAULT; + break; + } + + ret = client_gp_initialize_context(client, &context.ret); + + if (copy_to_user(uarg, &context, sizeof(context))) { + ret = -EFAULT; + break; + } + break; + } + case MC_IO_GP_REGISTER_SHARED_MEM: { + struct mc_ioctl_gp_register_shared_mem shared_mem; + + if (copy_from_user(&shared_mem, uarg, sizeof(shared_mem))) { + ret = -EFAULT; + break; + } + + ret = client_gp_register_shared_mem(client, + &shared_mem.memref, + &shared_mem.ret, -1); + + if (copy_to_user(uarg, &shared_mem, sizeof(shared_mem))) { + ret = -EFAULT; + break; + } + break; + } + case MC_IO_GP_RELEASE_SHARED_MEM: { + struct mc_ioctl_gp_release_shared_mem shared_mem; + + if (copy_from_user(&shared_mem, uarg, sizeof(shared_mem))) { + ret = -EFAULT; + break; + } + + ret = client_gp_release_shared_mem(client, &shared_mem.memref); + break; + } + case MC_IO_GP_OPEN_SESSION: { + struct mc_ioctl_gp_open_session session; + + if (copy_from_user(&session, uarg, sizeof(session))) { + ret = -EFAULT; + break; + } + + ret = client_gp_open_session(client, &session.session_id, + &session.uuid, &session.operation, + &session.identity, + &session.ret, -1); + + if (copy_to_user(uarg, &session, sizeof(session))) { + ret = -EFAULT; + break; + } + break; + } + case MC_IO_GP_CLOSE_SESSION: { + struct mc_ioctl_gp_close_session session; + + if (copy_from_user(&session, uarg, sizeof(session))) { + ret = -EFAULT; + break; + } + + ret = client_gp_close_session(client, session.session_id); + break; + } + case MC_IO_GP_INVOKE_COMMAND: { + struct mc_ioctl_gp_invoke_command command; + + if (copy_from_user(&command, uarg, sizeof(command))) { + ret = -EFAULT; + break; + } + + ret = client_gp_invoke_command(client, command.session_id, + command.command_id, + &command.operation, + &command.ret, -1); + + if (copy_to_user(uarg, &command, sizeof(command))) { + ret = -EFAULT; + break; + } + break; + } + case MC_IO_GP_REQUEST_CANCELLATION: { + struct mc_ioctl_gp_request_cancellation cancel; + + if (copy_from_user(&cancel, uarg, sizeof(cancel))) { + ret = -EFAULT; + break; + } + + client_gp_request_cancellation(client, + cancel.operation.started); + ret = 0; + break; + } + default: + mc_dev_err("unsupported command no %d", id); + ret = -ENOIOCTLCMD; + } + + return ret; +} + +/* + * Callback for system mmap() + */ +static int user_mmap(struct file *file, struct vm_area_struct *vmarea) +{ + struct tee_client *client = get_client(file); + + if ((vmarea->vm_end - vmarea->vm_start) > BUFFER_LENGTH_MAX) + return -EINVAL; + + /* Alloc contiguous buffer for this client */ + return client_cbuf_create(client, + (u32)(vmarea->vm_end - vmarea->vm_start), + NULL, vmarea); +} + +static const struct file_operations mc_user_fops = { + .owner = THIS_MODULE, + .open = user_open, + .release = user_release, + .unlocked_ioctl = user_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = user_ioctl, +#endif + .mmap = user_mmap, +}; + +int mc_user_init(struct cdev *cdev) +{ + cdev_init(cdev, &mc_user_fops); + return 0; +} diff --git a/drivers/gud/MobiCoreDriver/user.h b/drivers/gud/MobiCoreDriver/user.h new file mode 100644 index 000000000000..8a5e6d125b41 --- /dev/null +++ b/drivers/gud/MobiCoreDriver/user.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef _USER_H_ +#define _USER_H_ + +struct cdev; + +int mc_user_init(struct cdev *cdev); +static inline void mc_user_exit(void) +{ +} + +#endif /* _USER_H_ */ diff --git a/drivers/gud/README b/drivers/gud/README new file mode 100644 index 000000000000..52954aab1f42 --- /dev/null +++ b/drivers/gud/README @@ -0,0 +1,8 @@ + +Trustonic TEE is an operating system being shipped with TZBSP +on MSM chipsets. t-base consists of several components in +the secure world(TrustZone) and non-secure world(linux +kernel, Android user space). The t-base driver +communicates with the Trustonic TEE kernel that exists in +TrustZone. +