Add Trustonic TEE driver module

Add the Trustonic TEE driver module to the kernel.  This is needed
to run trusted applications in the Trustonic TEE.

Change-Id: I24cf50b78f7dc1bb4085ba2cd979966e45957a79
Signed-off-by: Balachandar Swami <w60013@motorola.com>
Reviewed-on: https://gerrit.mot.com/1270005
SLTApproved: Slta Waiver
SME-Granted: SME Approvals Granted
Tested-by: Jira Key
Reviewed-by: Igor Kovalenko <igork@motorola.com>
Submit-Approved: Jira Key
This commit is contained in:
Balachandar Swami 2018-11-15 22:14:16 -06:00 • committed by Balachandar Swami
commit c8633cde17
52 changed files with 13814 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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_ */

View file

@ -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_ */

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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 <linux/list.h>
#include <linux/sched.h> /* 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_ */

View file

@ -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 <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/list.h>
#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);

View file

@ -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 <linux/device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/of.h>
#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 */

View file

@ -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_ */

View file

@ -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 <linux/kthread.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/workqueue.h>
#include <linux/cpu.h>
#include <linux/moduleparam.h>
#include <linux/debugfs.h>
#include <linux/sched.h> /* local_clock */
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/clock.h> /* local_clock */
#endif
#include <linux/uaccess.h>
#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

View file

@ -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_ */

View file

@ -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 <linux/err.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/completion.h>
#include <linux/circ_buf.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/of_irq.h>
#include <linux/freezer.h>
#include <asm/barrier.h>
#include <linux/irq.h>
#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)
{
}

View file

@ -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_ */

View file

@ -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 <linux/workqueue.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/version.h>
#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");
}

View file

@ -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_ */

View file

@ -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 <linux/platform_device.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/reboot.h>
#include <linux/suspend.h>
#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");

View file

@ -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 <linux/device.h> /* dev_* macros */
#include <linux/slab.h> /* gfp_t */
#include <linux/fs.h> /* struct inode and struct file */
#include <linux/mutex.h>
#include <linux/version.h>
#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_ */

View file

@ -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_ */

View file

@ -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_ */
/** @} */

View file

@ -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_ */

View file

@ -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_ */

View file

@ -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<operation> 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_ */

View file

@ -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_ */

View file

@ -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_ */

View file

@ -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 <linux/err.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/completion.h>
#include <linux/circ_buf.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/of_irq.h>
#include <linux/freezer.h>
#include <asm/barrier.h>
#include <linux/irq.h>
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/clock.h> /* 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;
}

View file

@ -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_ */

View file

@ -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 <asm/pgtable.h>
#include <linux/semaphore.h>
#include <linux/completion.h>
#include <linux/mutex.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/pagemap.h>
#include <linux/device.h>
#include <linux/version.h>
#include <linux/dma-buf.h>
#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);
}

View file

@ -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_ */

View file

@ -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 <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kthread.h>
#include <linux/of_irq.h>
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/clock.h> /* 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);
}

View file

@ -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 <linux/list.h>
#include <linux/mutex.h>
/* 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_ */

View file

@ -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 <linux/types.h>
/*--------------- Implementation -------------- */
#include <soc/qcom/scm.h>
#include <soc/qcom/qseecomi.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <asm/cacheflush.h>
#include <linux/errno.h>
#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_ */

View file

@ -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 <asm/bL_switcher.h>
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 */

View file

@ -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_ */

View file

@ -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__ */

View file

@ -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__ */

View file

@ -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__ */

View file

@ -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__ */

View file

@ -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 <linux/types.h>
#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__ */

View file

@ -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 <linux/types.h>
/*
* 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_ */

View file

@ -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 <linux/types.h>
#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_ */

View file

@ -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_ */

View file

@ -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 <linux/slab.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/kthread.h>
#include <linux/completion.h>
#include <linux/stringify.h>
#include <linux/version.h>
#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;
}

View file

@ -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__ */

View file

@ -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 <linux/types.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <crypto/hash.h>
#include <linux/scatterlist.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/net.h>
#include <net/sock.h> /* sockfd_lookup */
#include <linux/version.h>
#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
#include <linux/sched/clock.h> /* local_clock */
#include <linux/sched/task.h> /* put_task_struct */
#endif
#include "public/mc_user.h"
#include "public/mc_admin.h"
#if KERNEL_VERSION(3, 5, 0) <= LINUX_VERSION_CODE
#include <linux/uidgid.h>
#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 ? " <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;
}

View file

@ -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 <linux/list.h>
#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_ */

View file

@ -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 <linux/device.h>
#include <linux/delay.h>
#include <linux/vmalloc.h>
#include <linux/export.h>
#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);

View file

@ -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 <linux/cdev.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/fs.h>
#include <linux/mm_types.h> /* struct vm_area_struct */
#include <linux/uaccess.h>
#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;
}

View file

@ -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_ */

8
drivers/gud/README Normal file
View file

@ -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.