arch: arm: cortex_m: Add tz_ns.h

Provide a TZ_SAFE_ENTRY_FUNC() macro for wrapping non-secure entry
functions in calls to k_sched_lock()/k_sched_unlock()

Provide a __TZ_WRAP_FUNC() macro which helps in creating a function
that "wraps" another in a preface and postface function call.

	int foo(char *arg); // Implemented somewhere else.
	int __attribute__((naked)) foo_wrapped(char *arg)
	{
		WRAP_FUNC(bar, foo, baz);
	}

is equivalent to

	int foo(char *arg); // Implemented somewhere else.
	int foo_wrapped(char *arg)
	{
		bar();
		int res = foo(arg);
		baz();
		return res;
	}

This commit also adds tests for __TZ_WRAP_FUNC().

Signed-off-by: Øyvind Rønningstad <oyvind.ronningstad@nordicsemi.no>
This commit is contained in:
Øyvind Rønningstad 2020-06-25 14:30:03 +02:00 committed by Carles Cufí
commit c00f33dcb0
6 changed files with 314 additions and 0 deletions

View file

@ -0,0 +1,140 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief TrustZone API for use in nonsecure firmware
*
* TrustZone API for Cortex-M23/M33 CPUs implementing the Security Extension.
* The following API can be used by the nonsecure firmware to interact with the
* secure firmware.
*/
#ifndef ZEPHYR_ARCH_ARM_INCLUDE_AARCH32_CORTEX_M_TZ_NS_H_
#define ZEPHYR_ARCH_ARM_INCLUDE_AARCH32_CORTEX_M_TZ_NS_H_
#ifdef _ASMLANGUAGE
/* nothing */
#else
/**
* @brief Macro for "sandwiching" a function call (@p name) in two other calls
*
* This macro should be called via @ref __TZ_WRAP_FUNC.
*
* This macro creates the function body of an "outer" function which behaves
* exactly like the wrapped function (@p name), except that the preface function
* is called before, and the postface function afterwards.
*
* @param preface The function to call first. Must have no parameters and no
* return value.
* @param name The main function, i.e. the function to wrap. This function
* will receive the arguments, and its return value will be
* returned.
* @param postface The function to call last. Must have no parameters and no
* return value.
* @param store_lr The assembly instruction for storing away the LR value
* before the functions are called. This instruction must leave
* r0-r3 unmodified.
* @param load_lr The assembly instruction for restoring the LR value after
* the functions have been called. This instruction must leave
* r0-r3 unmodified.
*/
#define __TZ_WRAP_FUNC_RAW(preface, name, postface, store_lr, load_lr) \
do { \
__asm(".global "#preface"; .type "#preface", %function"); \
__asm(".global "#name"; .type "#name", %function"); \
__asm(".global "#postface"; .type "#postface", %function"); \
\
__asm(store_lr); \
__asm("push {r0-r3}"); \
__asm("bl "#preface); \
__asm("pop {r0-r3}"); \
__asm("bl "#name); \
__asm("push {r0-r3}"); \
__asm("bl "#postface); \
__asm("pop {r0-r3}"); \
__asm(load_lr); \
} while (0)
/**
* @brief Macro for "sandwiching" a function call (@p name) in two other calls
*
* @pre The wrapped function MUST not pass arguments or return values via
* the stack. I.e. the arguments and return values must each fit within 4
* words, after accounting for alignment.
* Since nothing is passed on the stack, the stack can safely be used to
* store LR.
*
* Usage example:
*
* int foo(char *arg); // Implemented elsewhere.
* int __attribute__((naked)) foo_wrapped(char *arg)
* {
* __TZ_WRAP_FUNC(bar, foo, baz)
* }
*
* is equivalent to
*
* int foo(char *arg); // Implemented elsewhere.
* int foo_wrapped(char *arg)
* {
* bar();
* int res = foo(arg);
* baz();
* return res;
* }
*
* @note __attribute__((naked)) is not mandatory, but without it, GCC gives a
* warning for functions with a return value. It also saves a bit of space
* since it removes a little code that is not necessary.
*
* See @ref __TZ_WRAP_FUNC_RAW for more information.
*/
#define __TZ_WRAP_FUNC(preface, name, postface) \
__TZ_WRAP_FUNC_RAW(preface, name, postface, "push {r4, lr}", \
"pop {r4, pc}")
#ifdef CONFIG_ARM_FIRMWARE_USES_SECURE_ENTRY_FUNCS
/**
* @brief Create a thread safe wrapper function for an non-secure entry function
*
* This locks the scheduler before calling the function by wrapping the NS entry
* function in @ref k_sched_lock / @ref k_sched_unlock, using
* @ref __TZ_WRAP_FUNC.
*
* In non-secure code:
*
* int foo(char *arg); // Declaration of entry function.
* TZ_THREAD_SAFE_NONSECURE_ENTRY_FUNC(foo_safe, int, foo, char *arg)
*
* Usage in non-secure code:
*
* int ret = foo_safe("my arg");
*
* If NS entry functions are called without such a wrapper, and a thread switch
* happens while execution is in the secure binary, the possibly app will crash
* upon returning to the non-secure binary.
*
* @param ret The return type of the NS entry function.
* @param name The desired name of the safe function. This assumes there is a
* corresponding NS entry function called nsc_name.
* @param ... The rest of the signature of the function. This must be the same
* signature as the corresponding NS entry function.
*/
#define TZ_THREAD_SAFE_NONSECURE_ENTRY_FUNC(name, ret, nsc_name, ...) \
ret __attribute__((naked)) name(__VA_ARGS__) \
{ \
__TZ_WRAP_FUNC(k_sched_lock, nsc_name, k_sched_unlock); \
}
#endif /* CONFIG_ARM_FIRMWARE_USES_SECURE_ENTRY_FUNCS */
#endif /* _ASMLANGUAGE */
#endif /* ZEPHYR_ARCH_ARM_INCLUDE_AARCH32_CORTEX_M_TZ_NS_H_ */

View file

@ -0,0 +1,12 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(arm_wrap_func)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
target_include_directories(app PRIVATE
${ARCH_DIR}/${ARCH}/include
)

View file

@ -0,0 +1 @@
CONFIG_ZTEST=y

View file

@ -0,0 +1,48 @@
Title: Test to verify the __TZ_WRAP_FUNC() macro.
Description:
__TZ_WRAP_FUNC() is part of the nonsecure TrustZone API, but is itself
independent of TrustZone functionality, so it is tested here outside the context
of secure/nonsecure firmware.
The test verifies that:
- The wrapper functions are correctly called.
- The arguments are passed to the wrapped function.
- The return value from the wrapped function is correctly returned from the
wrapper function.
---------------------------------------------------------------------------
Building and Running Project:
This project outputs to the console. It can be built and executed on QEMU as
follows:
ninja/make run
---------------------------------------------------------------------------
Troubleshooting:
Problems caused by out-dated project information can be addressed by
issuing one of the following commands then rebuilding the project:
ninja/make clean # discard results of previous builds
# but keep existing configuration info
or
ninja/make pristine # discard results of previous builds
# and restore pre-defined configuration info
---------------------------------------------------------------------------
Sample Output:
*** Booting Zephyr OS build zephyr-v2.3.0-2427-g6a7e2dc314b2 ***
Running test suite tz_wrap_func
===================================================================
START - test_tz_wrap_func
PASS - test_tz_wrap_func
===================================================================
Test suite tz_wrap_func succeeded
===================================================================
PROJECT EXECUTION SUCCESSFUL

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ztest.h>
#include <aarch32/cortex_m/tz_ns.h>
#include <arch/arm/aarch32/cortex_m/cmsis.h>
static bool expect_preface;
static bool expect_postface;
static bool expect_foo1;
static bool preface_called;
static bool postface_called;
static bool foo1_called;
static uint32_t foo1_retval;
static uint32_t foo1_arg1;
static uint32_t foo1_arg2;
static uint32_t foo1_arg3;
static uint32_t foo1_arg4;
void reset_mocks(void)
{
expect_preface = true;
foo1_called = false;
preface_called = false;
postface_called = false;
foo1_retval = 0;
foo1_arg1 = 0;
foo1_arg2 = 0;
foo1_arg3 = 0;
foo1_arg4 = 0;
}
void preface(void)
{
zassert_true(expect_preface, "%s unexpectedly called", __func__);
expect_preface = false;
preface_called = true;
expect_foo1 = true;
}
uint32_t foo1(uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t arg4)
{
zassert_true(expect_foo1, "%s unexpectedly called", __func__);
zassert_equal(arg1, foo1_arg1, "Was 0x%"PRIx32", expected 0x%"PRIx32,
arg1, foo1_arg1);
zassert_equal(arg2, foo1_arg2, NULL);
zassert_equal(arg3, foo1_arg3, NULL);
zassert_equal(arg4, foo1_arg4, NULL);
expect_foo1 = false;
foo1_called = true;
expect_postface = true;
return foo1_retval;
}
void postface(void)
{
zassert_true(expect_postface, "%s unexpectedly called", __func__);
expect_postface = false;
postface_called = true;
}
uint32_t __attribute__((naked)) wrap_foo1(uint32_t arg1, uint32_t arg2,
uint32_t arg3, uint32_t arg4)
{
__TZ_WRAP_FUNC(preface, foo1, postface);
}
void test_tz_wrap_func(void)
{
reset_mocks();
foo1_retval = 0x01234567;
foo1_arg1 = 0x12345678;
foo1_arg2 = 0x23456789;
foo1_arg3 = 0x3456789a;
foo1_arg4 = 0x456789ab;
uint32_t msp1, psp1;
msp1 = __get_MSP();
psp1 = __get_PSP();
zassert_equal(foo1_retval,
wrap_foo1(foo1_arg1, foo1_arg2, foo1_arg3, foo1_arg4), NULL);
zassert_equal(msp1, __get_MSP(), NULL);
zassert_equal(psp1, __get_PSP(), NULL);
zassert_true(preface_called, NULL);
zassert_true(foo1_called, NULL);
zassert_true(postface_called, NULL);
zassert_false(expect_preface, NULL);
zassert_false(expect_foo1, NULL);
zassert_false(expect_postface, NULL);
}
void test_main(void)
{
ztest_test_suite(tz_wrap_func,
ztest_unit_test(test_tz_wrap_func));
ztest_run_test_suite(tz_wrap_func);
}

View file

@ -0,0 +1,5 @@
tests:
arch.arm.tz_wrap_func:
arch_allow: arm
tags: arm tz_ns tz_wrap_func
filter: CONFIG_CPU_CORTEX_M