API Reference

Trusted applications

Custom Pseudo Trusted Application

Trusted Applications (TA) normally run in user mode in the Trusted Execution Environment. They are dynamically loaded when executed. When you want to run them in kernel mode (to write peripheral drivers for example), a Pseudo TA (PTA) is necessary. During compilation, PTA’s are statically linked against the OP-TEE kernel.

It is possible to create a PTA by adding the sources to optee_os/core/pta and including them in the Makefile in the same directory. This way the PTA is included in the os when it is built. It is then possible to create an application for the normal world that calls this PTA using its UUID and the right parameters, just like a normal TA.

The example host application is created by copying an existing example application from optee_examples and removing the ta subfolder (as there will be no TA compiled separately). The Makefile of the host application should be updated with the right binary name and the build instructions for a TA should be removed from the parent (C)Makefiles.

Example PTA

optee_os/core/pta/testpta.c

This example shows a simple PTA that prints some text when called from the normal world.

#include <kernel/pseudo_ta.h>

#define PTA_NAME "test.pta"
#define PTA_TEST_PRINT_UUID { 0xd4be3f91, 0xc4e1, 0x436c, \
    { 0xb2, 0x92, 0xbf, 0xf5, 0x3e, 0x43, 0x04, 0xd5 } }

#define PTA_TEST_PRINT 0

static TEE_Result test_print(uint32_t type, TEE_Param p[TEE_NUM_PARAMS]) {
    IMSG("This is a test of a pseudo trusted application.");

    return TEE_SUCCESS;
}

/*
 * Trusted Application Etnry Points
 */

static TEE_Result invoke_command(void *session __unused, uint32_t cmd,
                                 uint32_t ptypes,
                                 TEE_Param params[TEE_NUM_PARAMS]) {
  switch (cmd) {
    case PTA_TEST_PRINT:
        return test_print(ptypes, params);
        break;
    default:
        break;
  }

  return TEE_ERROR_NOT_IMPLEMENTED;
}

pseudo_ta_register(.uuid = PTA_TEST_PRINT_UUID, .name = PTA_NAME,
    .flags = PTA_DEFAULT_FLAGS, .invoke_command_entry_point = invoke_command);
Append to optee_os/core/pta/sub.mk
ifeq ($(PLATFORM),vexpress-qemu_virt)
srcs-y += testpta.c
endif

Example Host application

host/main.c
/*
 * Copyright (c) 2016, Linaro Limited
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <err.h>
#include <stdio.h>
#include <string.h>

/* OP-TEE TEE client API (built by optee_client) */
#include <tee_client_api.h>

#define PTA_TEST_PRINT_UUID { 0xd4be3f91, 0xc4e1, 0x436c, \
    { 0xb2, 0x92, 0xbf, 0xf5, 0x3e, 0x43, 0x04, 0xd5 } }

#define PTA_TEST_PRINT 0

int main(void)
{
    TEEC_Result res;
    TEEC_Context ctx;
    TEEC_Session sess;
    TEEC_Operation op;
    TEEC_UUID uuid = PTA_TEST_PRINT_UUID;
    uint32_t err_origin;

    /* Initialize a context connecting us to the TEE */
    res = TEEC_InitializeContext(NULL, &ctx);
    if (res != TEEC_SUCCESS)
        errx(1, "TEEC_InitializeContext failed with code 0x%x", res);

    /*
     * Open a session to the "hello world" TA, the TA will print "hello
     * world!" in the log when the session is created.
     */
    res = TEEC_OpenSession(&ctx, &sess, &uuid,
                   TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin);
    if (res != TEEC_SUCCESS)
        errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x",
            res, err_origin);

    /*
     * Execute a function in the TA by invoking it, in this case
     * we're incrementing a number.
     *
     * The value of command ID part and how the parameters are
     * interpreted is part of the interface provided by the TA.
     */

    /* Clear the TEEC_Operation struct */
    memset(&op, 0, sizeof(op));

    /*
     * Prepare the argument. Pass a value in the first parameter,
     * the remaining three parameters are unused.
     */
    op.paramTypes = TEEC_PARAM_TYPES(TEEC_NONE, TEEC_NONE,
                     TEEC_NONE, TEEC_NONE);

    /*
     * TA_HELLO_WORLD_CMD_INC_VALUE is the actual function in the TA to be
     * called.
     */
    printf("Invoking PTA to print test.");
    res = TEEC_InvokeCommand(&sess, PTA_TEST_PRINT, &op, &err_origin);
    if (res != TEEC_SUCCESS)
        errx(1, "TEEC_InvokeCommand failed with code 0x%x origin 0x%x",
            res, err_origin);
    printf("Test printed.");

    /*
     * We're done with the TA, close the session and
     * destroy the context.
     *
     * The TA will print "Goodbye!" in the log when the
     * session is closed.
     */

    TEEC_CloseSession(&sess);

    TEEC_FinalizeContext(&ctx);

    return 0;
}
Makefile
export V?=0

# If _HOST or _TA specific compilers are not specified, then use CROSS_COMPILE
HOST_CROSS_COMPILE ?= $(CROSS_COMPILE)

.PHONY: all
all:
    $(MAKE) -C host CROSS_COMPILE="$(HOST_CROSS_COMPILE)" --no-builtin-variables

.PHONY: clean
clean:
    $(MAKE) -C host clean
host/Makefile

CC      ?= $(CROSS_COMPILE)gcc
LD      ?= $(CROSS_COMPILE)ld
AR      ?= $(CROSS_COMPILE)ar
NM      ?= $(CROSS_COMPILE)nm
OBJCOPY ?= $(CROSS_COMPILE)objcopy
OBJDUMP ?= $(CROSS_COMPILE)objdump
READELF ?= $(CROSS_COMPILE)readelf

OBJS = main.o

CFLAGS += -Wall -I../ta/include -I$(TEEC_EXPORT)/include -I./include
#Add/link other required libraries here
LDADD += -lteec -L$(TEEC_EXPORT)/lib

BINARY = optee_example_testpta

.PHONY: all
all: $(BINARY)

$(BINARY): $(OBJS)
    $(CC) $(LDFLAGS) -o $@ $< $(LDADD)

.PHONY: clean
clean:
    rm -f $(OBJS) $(BINARY)

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@
CMakeLists.txt
project (optee_example_testpta C)

set (SRC host/main.c)

add_executable (${PROJECT_NAME} ${SRC})

target_include_directories(${PROJECT_NAME}
            #    PRIVATE ta/include
               PRIVATE include)

target_link_libraries (${PROJECT_NAME} PRIVATE teec)

install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
Android.mk
###################### optee-testpta ######################
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_CFLAGS += -DANDROID_BUILD
LOCAL_CFLAGS += -Wall

LOCAL_SRC_FILES += host/main.c

# LOCAL_C_INCLUDES := $(LOCAL_PATH)/ta/include

LOCAL_SHARED_LIBRARIES := libteec
LOCAL_MODULE := optee_example_testpta
LOCAL_VENDOR_MODULE := true
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)

# include $(LOCAL_PATH)/ta/Android.mk

Scheduling

Mr-TEE uses FreeRTOS as its scheduler, so its API is available to all OP-TEE OS kernel applications. The code snippets are based on the ones provided in the official FreeRTOS documentation: FreeRTOS Developer Docs.

This page lists a few important types and functions to get started. Further information on FreeRTOS can be found at About FreeRTOS Kernel.

Tasks

Signature

void task(void *parameters);
Parameter Type Description
parameters void * Params given to the task whenever it is created.

Methods

xTaskCreate

Register a task with the scheduler. The task will be scheduled according to its priority from the moment this function is called.

BaseType_t xTaskCreate(
    TaskFunction_t         pvTaskCode,
    const char * const     pcName,
    configSTACK_DEPTH_TYPE usStackDepth,
    void *                 pvParameters,
    UBaseType_t            uxPriority,
    TaskHandle_t *         pxCreatedTask
);
Parameter Type Description
pvTaskCode TaskFunction_t The function with the control logic of the task.
pcName const char * const The name of the task.
usStackDepth configSTACK_DEPTH_TYPE The type of stack.
pvParameters void * The parameter to pass when the task is first called.
uxPriority UBaseType_t The task priority.
pxCreatedTask TaskHandle_t * A handle to the created task, used for task management.
vTaskDelete

Delete a task, stopping its execution and removing all its memory allocations.

void vTaskDelete(TaskHandle_t xTaskToDelete);
Parameter Type Description
xTaskToDelete TaskHandle_t The handle to the task to delete.
vTaskDelay

Delay the execution of a task for a given amount of ticks.

void vTaskDelay(const TickType_t xTicksToDelay);
Parameter Type Description
xTicksToDelay const TickType_t The amount of ticks to delay execution for.
xTaskDelayUntil

Delay a task until a given time. Will not delay if that time is in the past.

BaseType_t xTaskDelayUntil(
    TickType_t * const pxPreviousWakeTime,
    const TickType_t   xTimeIncrement
);
Parameter Type Description
pxPreviousWakeTime TickType_t * const Pointer to last time task was revived.
xTimeIncrement const TickType_t Amount of ticks past the previous wake time the task should wait.
Return Description
pdTrue Task was actually delayed.
pdFalse Task was not delayed.

Example: Scheduling a task

#include <FreeRTOS/task.h>
#include <FreeRTOS/FreeRTOS.h>

void task( void * parameters )
{
    while (true)
    {
        // Code to execute

        vTaskDelay(n); // Delay execution of task by n ticks.
    }
    
    // Break out of loop and delete task when not needed.
    vTaskDelete(NULL);
}

int main (void) {
    BaseType_t xReturn;
    TaskHandle_t xHandle = NULL;

    xReturn = xTaskCreate(
        task,            /* Function that implements the task. */
        "Task name",     /* Text name for the task. */
        configMINMAL_STACK_SIZE, /* Stack size in words, not bytes. */
        (void *) NULL,   /* Parameter passed into the task. */
        (UBaseType_t) 1, /* Priority at which the task is created. */
        &xHandle         /* Used to pass out the created task's handle. */
    );

    if( xReturn != pdPASS )
    {
        EMSG("Could not create task\n");
        return -1;
    }

    return 0;
}

Shared Secure Peripherals

SSP Notifier

Normal World Notifier

norm_notif_register
int norm_notif_register(uuid_t uuid, void (*notif_handler)(void));

Register a normal world driver for receiving notifications from a secure driver.

Parameter Type Description
uuid uuid_t The UUID of the secure driver.
notif_handler void (* handler)(void) The handler to call whenever a notification is received.
Return Description
0 The normal world driver has been registered successfully.
-EINVAL There already is a normal world driver registered for the given uuid.

Secure World Notifier

sec_notify
TEE_Result sec_notify(TEE_UUID uuid);

Notify the normal world.

Parameter Type Description
uuid uuid_t The UUID of the secure driver.
Return Description
TEE_SUCCESS The notification was sent successfully.
!TEE_SUCCESS Something went wrong.

Example: Notifying normal world driver

Normal world driver

#include <linux/init.h>
#include <linux/module.h>
#include <linux/uuid.h>
#include <normal-notifier.h>

static const uuid_t sec_drv_uuid = UUID_INIT(a,b,c,d0,d1,d2,d3,d4,d5,d6,d7);

static void notif_handler(void)
{
    // Retrieve data from secure driver, do some processing etc.
}

static int __init mod_init(void)
{
    int ret;
    
    if ((ret = norm_notif_register(sec_drv_uuid, notif_handler)) < 0) {
        pr_err("Could not register with the normal world notifier\n.");
        return ret;
    }
}

module_init(mod_init);

Secure world driver

#include <kernel/pseudo_ta.h>
#include <kernel/interrupt.h>
#include <drivers/secure_notifier.h>

static const TEE_UUID uuid = {a, b, c, {d0,d1,d2,d3,d4,d5,d6,d7}}

static enum itr_return handler(struct itr_handler *handler) {
    // Do stuff with peripheral.

    // If necessary:
    if (sec_notify(uuid)) {
        EMSG("Error sending notification.\n");
    }

    return ITRR_HANDLED;
}

static struct itr_handler itr = {
    .it = <interrupt_number>,
    .flags = ITRF_TRIGGER_LEVEL,
    .handler = handler,
};
DECLARE_KEEP_PAGER(console_itr);

static TEE_Result invoke_command(
    void *pSessionContext,
    uint32_t cmd,
    uint32_t ptypes,
    TEE_Param params[TEE_NUM_PARAMS]
)
{
    switch (cmd) {
    case ...:
        // Do stuff
    default:
        return TEE_ERROR_NOT_IMPLEMENTED;
    }

    return TEE_SUCCESS;
}

static TEE_Result create_entry_point(void)
{
    itr_add(&itr);
    itr_enable(console_itr.it);

    return TEE_SUCCESS;
}

pseudo_ta_register(.uuid = uuid, .name = "Name",
                   .flags = PTA_DEFAULT_FLAGS,
                   .invoke_command_entry_point = invoke_command,
                   .create_entry_point = create_entry_point);