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:
parent
d3f21af047
commit
c00f33dcb0
6 changed files with 314 additions and 0 deletions
140
arch/arm/include/aarch32/cortex_m/tz_ns.h
Normal file
140
arch/arm/include/aarch32/cortex_m/tz_ns.h
Normal 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_ */
|
12
tests/arch/arm/arm_tz_wrap_func/CMakeLists.txt
Normal file
12
tests/arch/arm/arm_tz_wrap_func/CMakeLists.txt
Normal 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
|
||||
)
|
1
tests/arch/arm/arm_tz_wrap_func/prj.conf
Normal file
1
tests/arch/arm/arm_tz_wrap_func/prj.conf
Normal file
|
@ -0,0 +1 @@
|
|||
CONFIG_ZTEST=y
|
48
tests/arch/arm/arm_tz_wrap_func/src/README.txt
Normal file
48
tests/arch/arm/arm_tz_wrap_func/src/README.txt
Normal 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
|
108
tests/arch/arm/arm_tz_wrap_func/src/main.c
Normal file
108
tests/arch/arm/arm_tz_wrap_func/src/main.c
Normal 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);
|
||||
}
|
5
tests/arch/arm/arm_tz_wrap_func/testcase.yaml
Normal file
5
tests/arch/arm/arm_tz_wrap_func/testcase.yaml
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue