tests: arm: Add arm_thread_swap_tz test

For testing secure->non-secure thread swapping.
This also tests that the FP context is correctly preserved
when calling a secure function.

Signed-off-by: Øyvind Rønningstad <oyvind.ronningstad@nordicsemi.no>
This commit is contained in:
Øyvind Rønningstad 2021-03-24 12:37:07 +01:00 committed by Ioannis Glaropoulos
commit 17442cece9
7 changed files with 306 additions and 0 deletions

View file

@ -0,0 +1,13 @@
#
# Copyright (c) 2021 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(NONE)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

View file

@ -0,0 +1,12 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2021 Nordic Semicondutor ASA
config ARM_STORE_EXC_RETURN
bool
default y if ARM_NONSECURE_FIRMWARE
help
Redefine this with looser requirements, so the EXC_RETURN value can
be checked in the test. This essentially tests that the official
requirements are sane.
source "Kconfig.zephyr"

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020 Linaro Limited.
*
* SPDX-License-Identifier: Apache-2.0
*/
/* This partition table should be used along with TFM configuration:
* - TFM_PSA_API=ON (IPC)
* - ISOLATION_LEVEL 2
* - TEST_S=ON (REGRESSION)
* - TEST_NS=ON (REGRESSION)
*
* In this configuration, TFM binary includes tests. As a consequence,
* its size is bloated and it is not possible to set secondary partitions
* for secured or non secured images.
*/
/ {
chosen {
zephyr,code-partition = &slot1_partition;
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00013000>;
read-only;
};
/* Secure image primary slot */
slot0_partition: partition@00013000 {
label = "image-0";
reg = <0x00013000 0x00038000>;
};
/* Non-secure image primary slot */
slot1_partition: partition@0004B000 {
label = "image-1";
reg = <0x0004B000 0x0002A000>;
};
/*
* The flash starting at 0x7F000 and ending at
* 0x80000 is reserved for the application.
*/
storage_partition: partition@7F000 {
label = "storage";
reg = <0x0007F000 0x00001000>;
};
};
};

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2021 Yestin Sun
*
* SPDX-License-Identifier: Apache-2.0
*/
/* This partition table should be used along with TFM configuration:
* - TFM_PSA_API=ON (IPC)
* - ISOLATION_LEVEL 2
* - TEST_S=ON (REGRESSION)
* - TEST_NS=OFF (By default)
*
* In this configuration, TFM binary includes tests. As a consequence,
* its size is bloated and it is not possible to set secondary partitions
* for secured or non secured images.
*/
/ {
chosen {
zephyr,code-partition = &slot1_partition;
};
};
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x00019000>;
read-only;
};
/* Secure image primary slot */
slot0_partition: partition@00019000 {
label = "image-0";
reg = <0x00019000 0x00038000>;
};
/* Non-secure image primary slot */
slot1_partition: partition@00051000 {
label = "image-1";
reg = <0x00051000 0x0002A000>;
};
/*
* The flash starting at 0x7F000 and ending at
* 0x80000 is reserved for the application.
*/
storage_partition: partition@7F000 {
label = "storage";
reg = <0x0007F000 0x00001000>;
};
};
};

View file

@ -0,0 +1,13 @@
#
# Copyright (c) 2021 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#
CONFIG_ZTEST=y
CONFIG_ZTEST_THREAD_PRIORITY=0
CONFIG_BUILD_WITH_TFM=y
CONFIG_TFM_IPC=y
CONFIG_TFM_PARTITION_AUDIT_LOG=n
CONFIG_FPU=y
CONFIG_FPU_SHARING=y

View file

@ -0,0 +1,148 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ztest.h>
#include <psa/crypto.h>
#include <kernel.h>
#ifndef EXC_RETURN_S
/* bit [6] stack used to push registers: 0=Non-secure 1=Secure */
#define EXC_RETURN_S (0x00000040UL)
#endif
#define HASH_LEN 32
static struct k_work_delayable interrupting_work;
static volatile bool work_done;
static char dummy_string[0x1000];
static char dummy_digest_correct[HASH_LEN];
static const uint32_t delay_ms = 4;
static volatile const struct k_thread *main_thread;
static void do_hash(char *hash)
{
size_t len;
/* Calculate correct hash. */
psa_status_t status = psa_hash_compute(PSA_ALG_SHA_256, dummy_string,
sizeof(dummy_string), hash, HASH_LEN, &len);
zassert_equal(PSA_SUCCESS, status, NULL);
zassert_equal(HASH_LEN, len, NULL);
}
static void work_func(struct k_work *work)
{
#ifdef CONFIG_ARM_NONSECURE_PREEMPTIBLE_SECURE_CALLS
/* Check that the main thread was executing in secure mode. */
zassert_true(main_thread->arch.mode_exc_return & EXC_RETURN_S,
"EXC_RETURN not secure: 0x%x\n", main_thread->arch.mode_exc_return);
#else
/* Check that the main thread was executing in nonsecure mode. */
zassert_false(main_thread->arch.mode_exc_return & EXC_RETURN_S,
"EXC_RETURN not nonsecure: 0x%x\n", main_thread->arch.mode_exc_return);
#endif
work_done = true;
/* If FPU is available, clobber FPU context in this thread to check that
* the correct context is restored in the other thread.
*/
#ifdef CONFIG_CPU_HAS_FPU
uint32_t clobber_val[16] = {
0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef,
};
__asm__ volatile(
"vldmia %0, {s0-s15}\n"
"vldmia %0, {s16-s31}\n"
:: "r" (clobber_val) :
);
#endif /* CONFIG_CPU_HAS_FPU */
/* Call a secure service here as well, to test the added complexity of
* calling secure services from two threads.
*/
psa_status_t status = psa_hash_compare(PSA_ALG_SHA_256, dummy_string,
sizeof(dummy_string), dummy_digest_correct, HASH_LEN);
zassert_equal(PSA_SUCCESS, status, NULL);
}
void test_thread_swap(void)
{
int err;
char dummy_digest[HASH_LEN];
k_timeout_t delay = K_MSEC(delay_ms);
k_tid_t curr;
psa_status_t status;
curr = k_current_get();
main_thread = (struct k_thread *)curr;
status = psa_crypto_init();
zassert_equal(PSA_SUCCESS, status, NULL);
/* Calculate correct hash. */
do_hash(dummy_digest_correct);
/* Set up interrupting_work to fire while call_tfm() is executing.
* This tests that it is safe to switch threads while a secure service
* is running.
*/
k_work_init_delayable(&interrupting_work, work_func);
err = k_work_reschedule(&interrupting_work, delay);
zassert_equal(1, err, "k_work_reschedule failed: %d\n", err);
/* If FPU is available, check that FPU context is preserved when calling
* a secure function.
*/
#ifdef CONFIG_CPU_HAS_FPU
uint32_t test_val[16] = {
0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d,
0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d,
0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d,
0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d, 0x1a2b3c4d,
};
uint32_t test_val_res0[16];
uint32_t test_val_res1[16];
__asm__ volatile(
"vldmia %0, {s0-s15}\n"
"vldmia %0, {s16-s31}\n"
:: "r" (test_val) :
);
#endif /* CONFIG_CPU_HAS_FPU */
work_done = false;
do_hash(dummy_digest);
zassert_true(work_done, "Interrupting work never happened\n");
#ifdef CONFIG_CPU_HAS_FPU
__asm__ volatile(
"vstmia %0, {s0-s15}\n"
"vstmia %1, {s16-s31}\n"
:: "r" (test_val_res0), "r" (test_val_res1) :
);
zassert_mem_equal(dummy_digest, dummy_digest_correct, HASH_LEN, NULL);
zassert_mem_equal(test_val, test_val_res0, sizeof(test_val), NULL);
zassert_mem_equal(test_val, test_val_res1, sizeof(test_val), NULL);
#endif /* CONFIG_CPU_HAS_FPU */
}
void test_main(void)
{
ztest_test_suite(test_thread_swap,
ztest_unit_test(test_thread_swap)
);
ztest_run_test_suite(test_thread_swap);
}

View file

@ -0,0 +1,9 @@
common:
filter: (CONFIG_TFM_BOARD != "") and CONFIG_ARM_NONSECURE_FIRMWARE
tags: arm
arch_allow: arm
tests:
arch.arm.swap.tz: {}
arch.arm.swap.tz_off:
extra_configs:
- CONFIG_ARM_NONSECURE_PREEMPTIBLE_SECURE_CALLS=n