Test: Add FP interrupt test case

Add test cases for interrupt:
1. Enable secure timer interrupt in non-secure thread, check FP
   context protection after secure interrupt return to non-secure
   thread.
2. Enable non-secure timer interrupt in secure thread, check FP
   context protection after non-secure interrupt entry.

Signed-off-by: Feder Liang <Feder.Liang@arm.com>
Change-Id: I368a8ee8790f18e00a2b86644a1844e9a903aab0
diff --git a/app/os_wrapper_cmsis_rtos_v2.c b/app/os_wrapper_cmsis_rtos_v2.c
index ec785f2..1e188ab 100755
--- a/app/os_wrapper_cmsis_rtos_v2.c
+++ b/app/os_wrapper_cmsis_rtos_v2.c
@@ -8,6 +8,7 @@
 #include "os_wrapper/thread.h"
 #include "os_wrapper/mutex.h"
 #include "os_wrapper/semaphore.h"
+#include "os_wrapper/delay.h"
 
 #include "cmsis_os2.h"
 
@@ -260,3 +261,15 @@
 
     return OS_WRAPPER_ERROR;
 }
+
+int32_t os_wrapper_delay_until(uint32_t ticks)
+{
+    osStatus_t status;
+
+    status = osDelayUntil(ticks);
+    if (status == osOK) {
+        return OS_WRAPPER_SUCCESS;
+    }
+
+    return OS_WRAPPER_ERROR;
+}
diff --git a/ns_interface/os_wrapper/delay.h b/ns_interface/os_wrapper/delay.h
new file mode 100644
index 0000000..9eaf95f
--- /dev/null
+++ b/ns_interface/os_wrapper/delay.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#ifndef __OS_WRAPPER_DELAY_H__
+#define __OS_WRAPPER_DELAY_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "common.h"
+
+/**
+ * \brief Wait until an absolute time (specified in kernel ticks) is reached.
+ *
+ * \return \ref OS_WRAPPER_SUCCESS if the time delay is executed, or
+ *         \ref OS_WRAPPER_ERROR in case of error
+ */
+int32_t os_wrapper_delay_until(uint32_t ticks);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __OS_WRAPPER_DELAY_H__ */
diff --git a/test/secure_fw/suites/fpu/fpu_tests_common.c b/test/secure_fw/suites/fpu/fpu_tests_common.c
index 98cb83f..e31ae4e 100644
--- a/test/secure_fw/suites/fpu/fpu_tests_common.c
+++ b/test/secure_fw/suites/fpu/fpu_tests_common.c
@@ -92,8 +92,8 @@
 /**
  * Check whether FP registers are restored correctly.
  * Return:
- *   1 - FP registers are restored correctly
- *   0 - FP registers are not restored correctly
+ *   True - FP registers are restored correctly
+ *   False - FP registers are not restored correctly
  */
 static bool check_fp_restored_client(void)
 {
@@ -170,8 +170,8 @@
 /**
  * Check invalidation of FP registers.
  * Return:
- *   1 - FP registers are invalidated
- *   0 - FP registers are not invalidated
+ *   True - FP registers are invalidated
+ *   False - FP registers are not invalidated
  */
 static bool check_fp_invalidated(void)
 {
diff --git a/test/secure_fw/suites/fpu/fpu_tests_common.h b/test/secure_fw/suites/fpu/fpu_tests_common.h
index 56b2ca1..1787032 100644
--- a/test/secure_fw/suites/fpu/fpu_tests_common.h
+++ b/test/secure_fw/suites/fpu/fpu_tests_common.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, Arm Limited. All rights reserved.
+ * Copyright (c) 2021-2022, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
@@ -20,10 +20,22 @@
 #endif
 
 
-#define LOOP_ITERATIONS                         (300U)
+#define LOOP_ITERATIONS             (300U)
+#define LOOPS_S_INT_TEST            (50U)
+#define LOOPS_NS_INT_TEST           (5000000U)
+#define WAIT_S_INT                  (20000U)
 
-#define NR_FP_REG                               (32U)
-#define FP_BUF_SIZE                             (NR_FP_REG * sizeof(uint32_t))
+#define NR_FP_REG                   (32U)
+#define NR_FP_CALLER_REG            (NR_FP_REG / 2)
+#define NR_FP_CALLEE_REG            (NR_FP_REG / 2)
+#define FP_BUF_SIZE                 (NR_FP_REG * sizeof(uint32_t))
+#define FP_CALLER_BUF_SIZE          (NR_FP_CALLER_REG * sizeof(uint32_t))
+#define FP_CALLEE_BUF_SIZE          (NR_FP_CALLEE_REG * sizeof(uint32_t))
+
+#define REL_VALUE_FP_REGS_INVALIDATED        (0xDEADBEEF)
+
+#define S_TIMER_TRIGGERED                    (0x19)
+#define S_TIMER_NOT_TRIGGERED                (0x91)
 
 /**
  * Test FP context protection after psa calls.
diff --git a/test/secure_fw/suites/fpu/non_secure/fpu_ns_interface_testsuite.c b/test/secure_fw/suites/fpu/non_secure/fpu_ns_interface_testsuite.c
index f39fd9a..a72cde6 100644
--- a/test/secure_fw/suites/fpu/non_secure/fpu_ns_interface_testsuite.c
+++ b/test/secure_fw/suites/fpu/non_secure/fpu_ns_interface_testsuite.c
@@ -6,8 +6,18 @@
  */
 
 #include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
 #include "fpu_ns_tests.h"
 #include "../fpu_tests_common.h"
+#include "os_wrapper/delay.h"
+
+static void tfm_fpu_test_fp_protection_secure_interrupt(
+                                                struct test_result_t *ret);
+static void tfm_fpu_test_fp_protection_non_secure_interrupt(
+                                                struct test_result_t *ret);
+static bool check_fp_caller_restored(void);
+static bool check_fp_callee_restored(void);
 
 static struct test_t fpu_ns_tests[] = {
     {
@@ -19,6 +29,17 @@
         &tfm_fpu_test_fp_protection_psa_call, "TFM_NS_FPU_TEST_1002",
         "Test FP context protection after psa calls",
         {TEST_PASSED}
+    },
+    {
+        &tfm_fpu_test_fp_protection_secure_interrupt, "TFM_NS_FPU_TEST_1003",
+        "Test FP context protection in S interrupt after interrupt return",
+        {TEST_PASSED}
+    },
+    {
+        &tfm_fpu_test_fp_protection_non_secure_interrupt,
+        "TFM_NS_FPU_TEST_1004",
+        "Test FP context protection in S thread after NS interrupt",
+        {TEST_PASSED}
     }
 };
 
@@ -31,3 +52,243 @@
     set_testsuite("FPU non-secure interface test (TFM_NS_FPU_TEST_1XXX)",
                   fpu_ns_tests, list_size, p_test_suite);
 }
+
+/**
+ * Change FP registers.
+ */
+__attribute__((naked)) void change_fp_in_ns_thread(void)
+{
+    __asm volatile(
+        "mov       r0, #0xC0000000         \n"
+        "vmov      s0, r0                  \n"
+        "mov       r0, #0xC1000000         \n"
+        "vmov      s1, r0                  \n"
+        "mov       r0, #0xC2000000         \n"
+        "vmov      s2, r0                  \n"
+        "mov       r0, #0xC3000000         \n"
+        "vmov      s3, r0                  \n"
+        "mov       r0, #0xC4000000         \n"
+        "vmov      s4, r0                  \n"
+        "mov       r0, #0xC5000000         \n"
+        "vmov      s5, r0                  \n"
+        "mov       r0, #0xC6000000         \n"
+        "vmov      s6, r0                  \n"
+        "mov       r0, #0xC7000000         \n"
+        "vmov      s7, r0                  \n"
+        "mov       r0, #0xC8000000         \n"
+        "vmov      s8, r0                  \n"
+        "mov       r0, #0xC9000000         \n"
+        "vmov      s9, r0                  \n"
+        "mov       r0, #0xCA000000         \n"
+        "vmov      s10, r0                 \n"
+        "mov       r0, #0xCB000000         \n"
+        "vmov      s11, r0                 \n"
+        "mov       r0, #0xCC000000         \n"
+        "vmov      s12, r0                 \n"
+        "mov       r0, #0xCD000000         \n"
+        "vmov      s13, r0                 \n"
+        "mov       r0, #0xCE000000         \n"
+        "vmov      s14, r0                 \n"
+        "mov       r0, #0xCF000000         \n"
+        "vmov      s15, r0                 \n"
+        "mov       r0, #0xD0000000         \n"
+        "vmov      s16, r0                 \n"
+        "mov       r0, #0xD1000000         \n"
+        "vmov      s17, r0                 \n"
+        "mov       r0, #0xD2000000         \n"
+        "vmov      s18, r0                 \n"
+        "mov       r0, #0xD3000000         \n"
+        "vmov      s19, r0                 \n"
+        "mov       r0, #0xD4000000         \n"
+        "vmov      s20, r0                 \n"
+        "mov       r0, #0xD5000000         \n"
+        "vmov      s21, r0                 \n"
+        "mov       r0, #0xD6000000         \n"
+        "vmov      s22, r0                 \n"
+        "mov       r0, #0xD7000000         \n"
+        "vmov      s23, r0                 \n"
+        "mov       r0, #0xD8000000         \n"
+        "vmov      s24, r0                 \n"
+        "mov       r0, #0xD9000000         \n"
+        "vmov      s25, r0                 \n"
+        "mov       r0, #0xDA000000         \n"
+        "vmov      s26, r0                 \n"
+        "mov       r0, #0xDB000000         \n"
+        "vmov      s27, r0                 \n"
+        "mov       r0, #0xDC000000         \n"
+        "vmov      s28, r0                 \n"
+        "mov       r0, #0xDD000000         \n"
+        "vmov      s29, r0                 \n"
+        "mov       r0, #0xDE000000         \n"
+        "vmov      s30, r0                 \n"
+        "mov       r0, #0xDF000000         \n"
+        "vmov      s31, r0                 \n"
+
+        "bx        lr                      \n"
+    );
+}
+
+/**
+ * Check whether FP caller registers are restored correctly.
+ * Return:
+ *   True - FP caller registers are restored correctly
+ *   False - FP caller registers are not restored correctly
+ */
+static bool check_fp_caller_restored(void)
+{
+    static uint32_t fp_buffer[NR_FP_CALLER_REG] = {0};
+    const uint32_t fp_expect[NR_FP_CALLER_REG] = {
+        0xC0000000, 0xC1000000, 0xC2000000, 0xC3000000,
+        0xC4000000, 0xC5000000, 0xC6000000, 0xC7000000,
+        0xC8000000, 0xC9000000, 0xCA000000, 0xCB000000,
+        0xCC000000, 0xCD000000, 0xCE000000, 0xCF000000
+    };
+
+    __asm volatile(
+        "vstm      %0, {S0-S15}            \n"
+        :
+        :"r"(fp_buffer)
+        :"memory"
+    );
+
+    if (!memcmp(fp_buffer, fp_expect, FP_CALLER_BUF_SIZE)) {
+        return true;
+    }
+
+    return false;
+}
+
+/**
+ * Check whether FP callee registers are restored correctly.
+ * Return:
+ *   True - FP callee registers are restored correctly
+ *   False - FP callee registers are not restored correctly
+ */
+static bool check_fp_callee_restored(void)
+{
+    static uint32_t fp_buffer[NR_FP_CALLEE_REG] = {0};
+    const uint32_t fp_expect[NR_FP_CALLEE_REG] = {
+        0xD0000000, 0xD1000000, 0xD2000000, 0xD3000000,
+        0xD4000000, 0xD5000000, 0xD6000000, 0xD7000000,
+        0xD8000000, 0xD9000000, 0xDA000000, 0xDB000000,
+        0xDC000000, 0xDD000000, 0xDE000000, 0xDF000000
+    };
+
+    __asm volatile(
+        "vstm      %0, {S16-S31}            \n"
+        :
+        :"r"(fp_buffer)
+        :"memory"
+    );
+
+    if (!memcmp(fp_buffer, fp_expect, FP_CALLEE_BUF_SIZE)) {
+        return true;
+    }
+
+    return false;
+}
+
+/**
+ * \brief Test FP context protection in S interrupt. Change FP registers in
+ * non-secure thread first, then change them in S interrupt. After interrupt
+ * return, check FP context protection in non-secure thread.
+ * Expectation: FP registers in S interrupt should not be view in non-secure
+ * thread.
+ */
+static void tfm_fpu_test_fp_protection_secure_interrupt(
+                                                struct test_result_t *ret)
+{
+    psa_handle_t handle;
+    psa_status_t status;
+    uint8_t outvec_data[1] = {0};
+    struct psa_outvec outvecs[1] = {{outvec_data, sizeof(outvec_data[0])}};
+    static uint8_t i;
+
+    ret->val = TEST_FAILED;
+
+    /* Change FP regs */
+    change_fp_in_ns_thread();
+
+    /* Start the timer */
+    handle = psa_connect(TFM_FPU_SERVICE_START_S_TIMER_SID,
+                            TFM_FPU_SERVICE_START_S_TIMER_VERSION);
+    if (!PSA_HANDLE_IS_VALID(handle)) {
+        return;
+    }
+    status = psa_call(handle, PSA_IPC_CALL, NULL, 0, NULL, 0);
+    if (status != PSA_SUCCESS) {
+        return;
+    }
+    psa_close(handle);
+
+    handle = psa_connect(TFM_FPU_SERVICE_CHECK_S_TIMER_TRIGGERED_SID,
+                            TFM_FPU_SERVICE_CHECK_S_TIMER_TRIGGERED_VERSION);
+    if (!PSA_HANDLE_IS_VALID(handle)) {
+        return;
+    }
+    /* Spin here */
+    while (1) {
+        /* Wait S interrupt triggered */
+        os_wrapper_delay_until(WAIT_S_INT);
+
+        status = psa_call(handle, PSA_IPC_CALL, NULL, 0, outvecs, 1);
+        if (status == PSA_SUCCESS) {
+            /* S interrupt triggered */
+            if (outvec_data[0] == S_TIMER_TRIGGERED) {
+                break;
+            } else {
+                i++;
+                if (i > LOOPS_S_INT_TEST) {
+                    TEST_FAIL("Time out: NS thread not interrupted!\r\n");
+                    psa_close(handle);
+                    return;
+                }
+            }
+        } else {
+            TEST_FAIL("Check S interrupt triggered failed!\r\n");
+            return;
+        }
+    }
+
+    psa_close(handle);
+
+    /* FP caller registers should be restored correctly after S interrupt */
+    if (!check_fp_caller_restored()) {
+        return;
+    }
+
+    /* FP callee registers should be restored correctly */
+    if (check_fp_callee_restored()) {
+        ret->val = TEST_PASSED;
+    }
+}
+
+/**
+ * \brief In secure thread, trigger non-secure timer interrupt, check FP
+ *  context protection after NS interrupt.
+ * Expectation: FP register in secure thread should be restored after NS
+ *  interrupt.
+ */
+static void tfm_fpu_test_fp_protection_non_secure_interrupt(
+                                                    struct test_result_t *ret)
+{
+    psa_handle_t handle;
+    psa_status_t status;
+    uint8_t *outvec_data[1] = {0};
+    struct psa_outvec outvecs[1] = {{outvec_data, sizeof(outvec_data[0])}};
+
+    ret->val = TEST_FAILED;
+
+    handle = psa_connect(TFM_FPU_SERVICE_CHECK_NS_INTERRUPT_S_TEST_SID,
+                            TFM_FPU_SERVICE_CHECK_NS_INTERRUPT_S_TEST_VERSION);
+    if (!PSA_HANDLE_IS_VALID(handle)) {
+        return;
+    }
+
+    status = psa_call(handle, PSA_IPC_CALL, NULL, 0, outvecs, 1);
+    if (status == PSA_SUCCESS) {
+        ret->val = TEST_PASSED;
+    }
+
+    psa_close(handle);
+}
diff --git a/test/secure_fw/suites/fpu/service/CMakeLists.txt b/test/secure_fw/suites/fpu/service/CMakeLists.txt
index 05ace67..038d210 100644
--- a/test/secure_fw/suites/fpu/service/CMakeLists.txt
+++ b/test/secure_fw/suites/fpu/service/CMakeLists.txt
@@ -15,7 +15,7 @@
 
 target_sources(tfm_app_rot_partition_fpu_service
     PRIVATE
-        ./tfm_fpu_service_test.c
+        tfm_fpu_service_test.c
 )
 
 # The generated sources
@@ -36,6 +36,7 @@
         .
     PRIVATE
         ${CMAKE_BINARY_DIR}/generated/secure_fw/test_services/tfm_fpu_service
+        ${TFM_TEST_PATH}/secure_fw/suites/fpu
 )
 
 target_include_directories(tfm_partitions
diff --git a/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.c b/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.c
index 91bee8c..206b27a 100644
--- a/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.c
+++ b/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, Arm Limited. All rights reserved.
+ * Copyright (c) 2021-2022, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  *
@@ -12,21 +12,16 @@
 #include "tfm_api.h"
 #include "tfm_hal_isolation.h"
 #include "tfm_secure_api.h"
+#include "tfm_memory_utils.h"
+#include "tfm_sp_log.h"
+#include "tfm_plat_test.h"
+#include "device_definition.h"
+#include "fpu_tests_common.h"
 
 /* Define the return status */
 #define FPU_SP_TEST_SUCCESS     (0)
 #define FPU_SP_TEST_FAILED      (-1)
 
-/*
- * Fixme: Temporarily implement abort as infinite loop,
- * will replace it later.
- */
-static void tfm_abort(void)
-{
-    while (1)
-        ;
-}
-
 /**
  * Clear FP registers.
  */
@@ -73,148 +68,32 @@
 /**
  * Check whether FP registers are restored correctly.
  * Return:
- *   1 - FP registers are restored correctly
- *   0 - FP registers are not restored correctly
+ *   True - FP registers are restored correctly
+ *   False - FP registers are not restored correctly
  */
-__attribute__((naked)) static bool check_fp_restored_service(void)
+bool check_fp_restored_service(void)
 {
+    uint32_t fp_buffer[NR_FP_REG] = {0};
+    const uint32_t fp_expect[NR_FP_REG] = {
+        0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
+        0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+        0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+        0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+     };
+
+    /* Dump FP data from FP registers to buffer */
     __asm volatile(
-        "mov       r3, #0                  \n"
-
-        "vmov      r2, s0                  \n"
-        "cmp       r2, 0x000000E0          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s1                  \n"
-        "cmp       r2, 0x000000E1          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s2                  \n"
-        "cmp       r2, 0x000000E2          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s3                  \n"
-        "cmp       r2, 0x000000E3          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s4                  \n"
-        "cmp       r2, 0x000000E4          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s5                  \n"
-        "cmp       r2, 0x000000E5          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s6                  \n"
-        "cmp       r2, 0x000000E6          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s7                  \n"
-        "cmp       r2, 0x000000E7          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s8                  \n"
-        "cmp       r2, 0x000000E8          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s9                  \n"
-        "cmp       r2, 0x000000E9          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s10                 \n"
-        "cmp       r2, 0x000000EA          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s11                 \n"
-        "cmp       r2, 0x000000EB          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s12                 \n"
-        "cmp       r2, 0x000000EC          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s13                 \n"
-        "cmp       r2, 0x000000ED          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s14                 \n"
-        "cmp       r2, 0x000000EE          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s15                 \n"
-        "cmp       r2, 0x000000EF          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s16                 \n"
-        "cmp       r2, 0x000000F0          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s17                 \n"
-        "cmp       r2, 0x000000F1          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s18                 \n"
-        "cmp       r2, 0x000000F2          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s19                 \n"
-        "cmp       r2, 0x000000F3          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s20                 \n"
-        "cmp       r2, 0x000000F4          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s21                 \n"
-        "cmp       r2, 0x000000F5          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s22                 \n"
-        "cmp       r2, 0x000000F6          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s23                 \n"
-        "cmp       r2, 0x000000F7          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s24                 \n"
-        "cmp       r2, 0x000000F8          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s25                 \n"
-        "cmp       r2, 0x000000F9          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s26                 \n"
-        "cmp       r2, 0x000000FA          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s27                 \n"
-        "cmp       r2, 0x000000FB          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s28                 \n"
-        "cmp       r2, 0x000000FC          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s29                 \n"
-        "cmp       r2, 0x000000FD          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s30                 \n"
-        "cmp       r2, 0x000000FE          \n"
-        "bne       quit                    \n"
-
-        "vmov      r2, s31                 \n"
-        "cmp       r2, 0x000000FF          \n"
-        "bne       quit                    \n"
-
-        "mov       r3, #1                  \n"
-        "quit:                             \n"
-        "mov       r0, r3                  \n"
-
-        "bx        lr                      \n"
+        "vstm      %0, {S0-S31}            \n"
+        :
+        :"r"(fp_buffer)
+        :"memory"
     );
+
+    if (!tfm_memcmp(fp_buffer, fp_expect, FP_BUF_SIZE)) {
+        return true;
+    }
+
+    return false;
 }
 
 /**
@@ -295,39 +174,141 @@
 /**
  * Check whether FP registers is invalidated.
  */
-__attribute__((naked)) static bool is_fp_cleaned(void)
+bool is_fp_cleaned(void)
+{
+    uint32_t fp_buffer[NR_FP_REG] = {0};
+    uint32_t i;
+
+    /* Dump FP data from FP registers to buffer */
+    __asm volatile(
+        "vstm      %0, {S0-S31}            \n"
+        :
+        :"r"(fp_buffer)
+        :"memory"
+    );
+
+    for (i = 0; i < NR_FP_REG; i++) {
+        if (fp_buffer[i] != 0) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Change FP registers in secure thread.
+ */
+__attribute__((naked)) void change_fp_in_s_thread(void)
 {
     __asm volatile(
-        "mov       r3, #1                  \n"
-        "mov       r2, #0                  \n"
-        "vadd.f32  s2, s1, s0              \n"
-        "vadd.f32  s4, s3, s2              \n"
-        "vadd.f32  s6, s5, s4              \n"
-        "vadd.f32  s8, s7, s6              \n"
-        "vadd.f32  s10, s9, s8             \n"
-        "vadd.f32  s12, s11, s10           \n"
-        "vadd.f32  s14, s13, s12           \n"
-        "vadd.f32  s16, s15, s14           \n"
-        "vadd.f32  s18, s17, s16           \n"
-        "vadd.f32  s20, s19, s18           \n"
-        "vadd.f32  s22, s21, s20           \n"
-        "vadd.f32  s24, s23, s22           \n"
-        "vadd.f32  s26, s25, s24           \n"
-        "vadd.f32  s28, s27, s26           \n"
-        "vadd.f32  s30, s29, s28           \n"
-        "vadd.f32  s31, s29, s30           \n"
-        "vcmp.f32  s31, #0.0               \n"
-        "vmrs      APSR_nzcv, fpscr        \n"
-        "beq       cleaned                 \n"
-        "mov       r3, r2                  \n"
-        "cleaned:                          \n"
-        "mov       r0, r3                  \n"
+        "push      {r4, lr}                    \n"
 
-        "bx       lr                      \n"
+        "mov       r0, #0xB0000000         \n"
+        "vmov      s0, r0                  \n"
+        "mov       r0, #0xB1000000         \n"
+        "vmov      s1, r0                  \n"
+        "mov       r0, #0xB2000000         \n"
+        "vmov      s2, r0                  \n"
+        "mov       r0, #0xB3000000         \n"
+        "vmov      s3, r0                  \n"
+        "mov       r0, #0xB4000000         \n"
+        "vmov      s4, r0                  \n"
+        "mov       r0, #0xB5000000         \n"
+        "vmov      s5, r0                  \n"
+        "mov       r0, #0xB6000000         \n"
+        "vmov      s6, r0                  \n"
+        "mov       r0, #0xB7000000         \n"
+        "vmov      s7, r0                  \n"
+        "mov       r0, #0xB8000000         \n"
+        "vmov      s8, r0                  \n"
+        "mov       r0, #0xB9000000         \n"
+        "vmov      s9, r0                  \n"
+        "mov       r0, #0xBA000000         \n"
+        "vmov      s10, r0                 \n"
+        "mov       r0, #0xBB000000         \n"
+        "vmov      s11, r0                 \n"
+        "mov       r0, #0xBC000000         \n"
+        "vmov      s12, r0                 \n"
+        "mov       r0, #0xBD000000         \n"
+        "vmov      s13, r0                 \n"
+        "mov       r0, #0xBE000000         \n"
+        "vmov      s14, r0                 \n"
+        "mov       r0, #0xBF000000         \n"
+        "vmov      s15, r0                 \n"
+        "mov       r0, #0xC0000000         \n"
+        "vmov      s16, r0                 \n"
+        "mov       r0, #0xC1000000         \n"
+        "vmov      s17, r0                 \n"
+        "mov       r0, #0xC2000000         \n"
+        "vmov      s18, r0                 \n"
+        "mov       r0, #0xC3000000         \n"
+        "vmov      s19, r0                 \n"
+        "mov       r0, #0xC4000000         \n"
+        "vmov      s20, r0                 \n"
+        "mov       r0, #0xC5000000         \n"
+        "vmov      s21, r0                 \n"
+        "mov       r0, #0xC6000000         \n"
+        "vmov      s22, r0                 \n"
+        "mov       r0, #0xC7000000         \n"
+        "vmov      s23, r0                 \n"
+        "mov       r0, #0xC8000000         \n"
+        "vmov      s24, r0                 \n"
+        "mov       r0, #0xC9000000         \n"
+        "vmov      s25, r0                 \n"
+        "mov       r0, #0xCA000000         \n"
+        "vmov      s26, r0                 \n"
+        "mov       r0, #0xCB000000         \n"
+        "vmov      s27, r0                 \n"
+        "mov       r0, #0xCC000000         \n"
+        "vmov      s28, r0                 \n"
+        "mov       r0, #0xCD000000         \n"
+        "vmov      s29, r0                 \n"
+        "mov       r0, #0xCE000000         \n"
+        "vmov      s30, r0                 \n"
+        "mov       r0, #0xCF000000         \n"
+        "vmov      s31, r0                 \n"
+
+        "pop       {r4, pc}                \n"
     );
 }
 
 /**
+ * Check whether FP registers are restored correctly.
+ * Return:
+ *   True - FP registers are restored correctly
+ *   False - FP registers are not restored correctly
+ */
+bool check_fp_restored_s(void)
+{
+    uint32_t fp_buffer[NR_FP_REG] = {0};
+    const uint32_t fp_expect[NR_FP_REG] = {
+        0xB0000000, 0xB1000000, 0xB2000000, 0xB3000000,
+        0xB4000000, 0xB5000000, 0xB6000000, 0xB7000000,
+        0xB8000000, 0xB9000000, 0xBA000000, 0xBB000000,
+        0xBC000000, 0xBD000000, 0xBE000000, 0xBF000000,
+        0xC0000000, 0xC1000000, 0xC2000000, 0xC3000000,
+        0xC4000000, 0xC5000000, 0xC6000000, 0xC7000000,
+        0xC8000000, 0xC9000000, 0xCA000000, 0xCB000000,
+        0xCC000000, 0xCD000000, 0xCE000000, 0xCF000000
+    };
+
+    /* Dump FP data from FP registers to buffer */
+    __asm volatile(
+        "vstm      %0, {S0-S31}            \n"
+        :
+        :"r"(fp_buffer)
+        :"memory"
+    );
+
+    if (!tfm_memcmp(fp_buffer, fp_expect, FP_BUF_SIZE)) {
+        return true;
+    }
+
+    return false;
+}
+
+/**
  * Service handler for clear FP register.
  */
 static void fpu_service_clear_fp_register(void)
@@ -353,8 +334,7 @@
         psa_reply(msg.handle, PSA_SUCCESS);
         break;
     default:
-        /* Should not come here */
-        tfm_abort();
+        psa_panic();
         break;
     }
 }
@@ -383,8 +363,129 @@
         psa_reply(msg.handle, PSA_SUCCESS);
         break;
     default:
-        /* Should not come here */
-        tfm_abort();
+        psa_panic();
+        break;
+    }
+}
+
+/**
+ * Start S timer interrupt.
+ * Expectation: S timer should be started.
+ */
+static void fpu_client_start_secure_timer(void)
+{
+    psa_msg_t msg;
+    psa_status_t r;
+
+    r = psa_get(TFM_FPU_SERVICE_START_S_TIMER_SIGNAL, &msg);
+    switch (msg.type) {
+    case PSA_IPC_CONNECT:
+    case PSA_IPC_DISCONNECT:
+        psa_reply(msg.handle, PSA_SUCCESS);
+        break;
+    case PSA_IPC_CALL:
+        /* Start the timer */
+        tfm_plat_test_secure_timer_start();
+        psa_reply(msg.handle, PSA_SUCCESS);
+        break;
+    default:
+        psa_panic();
+        break;
+    }
+}
+
+/**
+ * Read S timer reload value.
+ * Expectation: S timer reload value should be read.
+ */
+static void fpu_client_check_secure_timer_triggered(void)
+{
+    psa_msg_t msg;
+    psa_status_t r;
+    int val;
+
+    r = psa_get(TFM_FPU_SERVICE_CHECK_S_TIMER_TRIGGERED_SIGNAL, &msg);
+    switch (msg.type) {
+    case PSA_IPC_CONNECT:
+    case PSA_IPC_DISCONNECT:
+        psa_reply(msg.handle, PSA_SUCCESS);
+        break;
+    case PSA_IPC_CALL:
+        if (msg.out_size[0] != 0) {
+            /* Read the timer reload value */
+            if (tfm_plat_test_secure_timer_get_reload_value()
+                            == REL_VALUE_FP_REGS_INVALIDATED) {
+                val = S_TIMER_TRIGGERED;
+            } else {
+                val = S_TIMER_NOT_TRIGGERED;
+            }
+            psa_write(msg.handle, 0, &val, 1);
+            r = PSA_SUCCESS;
+        } else {
+            r = PSA_ERROR_PROGRAMMER_ERROR;
+        }
+        psa_reply(msg.handle, r);
+        break;
+    default:
+        psa_panic();
+        break;
+    }
+}
+
+/**
+ * Test FP context protection after NS interrupt.
+ * Expectation: FP register in secure thread should be restored after NS
+ * interrupt.
+ */
+int fpu_client_non_secure_interrupt_secure_test(void)
+{
+    psa_msg_t msg;
+    psa_status_t r;
+    static uint32_t i;
+
+    r = psa_get(TFM_FPU_SERVICE_CHECK_NS_INTERRUPT_S_TEST_SIGNAL, &msg);
+    switch (msg.type) {
+    case PSA_IPC_CONNECT:
+    case PSA_IPC_DISCONNECT:
+        psa_reply(msg.handle, PSA_SUCCESS);
+        break;
+    case PSA_IPC_CALL:
+        /* Change FP regs */
+        change_fp_in_s_thread();
+        /* Start the timer */
+        tfm_plat_test_non_secure_timer_start();
+        LOG_DBGFMT("Wait for NS timer interrupt!\r\n");
+        /* Spin here */
+        while (1) {
+            /* NS interrupt triggered */
+            if (tfm_plat_test_non_secure_timer_get_reload_value()
+                                == REL_VALUE_FP_REGS_INVALIDATED) {
+                LOG_DBGFMT("S thread interrupted by NS timer interrupt!\r\n");
+                break;
+            } else {
+                i++;
+                if (i > LOOPS_NS_INT_TEST) {
+                    LOG_DBGFMT("Time out: S thread is not interrupted!\r\n");
+                    break;
+                }
+            }
+        }
+        if (i > LOOPS_NS_INT_TEST) {
+            /* Error for time out */
+            r = PSA_ERROR_GENERIC_ERROR;
+        } else {
+            /* FP register should be restored after NS interrupt. */
+            if (check_fp_restored_s()) {
+                r = PSA_SUCCESS;
+            } else {
+                r = PSA_ERROR_GENERIC_ERROR;
+            }
+        }
+        /* Reply with status */
+        psa_reply(msg.handle, r);
+        break;
+    default:
+        psa_panic();
         break;
     }
 }
@@ -403,9 +504,14 @@
             fpu_service_clear_fp_register();
         } else if (signals & TFM_FPU_SERVICE_CHECK_FP_REGISTER_SIGNAL) {
             fpu_service_check_fp_register();
+        } else if (signals & TFM_FPU_SERVICE_START_S_TIMER_SIGNAL) {
+            fpu_client_start_secure_timer();
+        } else if (signals & TFM_FPU_SERVICE_CHECK_S_TIMER_TRIGGERED_SIGNAL) {
+            fpu_client_check_secure_timer_triggered();
+        }  else if (signals & TFM_FPU_SERVICE_CHECK_NS_INTERRUPT_S_TEST_SIGNAL) {
+            fpu_client_non_secure_interrupt_secure_test();
         } else {
-            /* Should not come here */
-            tfm_abort();
+            psa_panic();
         }
     }
 }
diff --git a/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.yaml b/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.yaml
index 4976446..e826b01 100644
--- a/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.yaml
+++ b/test/secure_fw/suites/fpu/service/tfm_fpu_service_test.yaml
@@ -10,14 +10,16 @@
   "name": "TFM_SP_FPU_SERVICE_TEST",
   "type": "APPLICATION-ROT",
   "priority": "NORMAL",
+  "model": "IPC",
   "entry_point": "fpu_service_test_main",
-  "stack_size": "0x0220",
+  "stack_size": "0x0400",
   "secure_functions": [
   ],
   "services" : [
     {
       "name": "TFM_FPU_SERVICE_CLEAR_FP_REGISTER",
       "sid": "0x0000F090",
+      "connection_based": true,
       "non_secure_clients": true,
       "version": 1,
       "version_policy": "STRICT"
@@ -25,9 +27,44 @@
     {
       "name": "TFM_FPU_SERVICE_CHECK_FP_REGISTER",
       "sid": "0x0000F091",
+      "connection_based": true,
       "non_secure_clients": true,
       "version": 1,
       "version_policy": "STRICT"
+    },
+    {
+      "name": "TFM_FPU_SERVICE_START_S_TIMER",
+      "sid": "0x0000F092",
+      "connection_based": true,
+      "non_secure_clients": true,
+      "version": 1,
+      "version_policy": "STRICT"
+    },
+    {
+      "name": "TFM_FPU_SERVICE_CHECK_S_TIMER_TRIGGERED",
+      "sid": "0x0000F093",
+      "connection_based": true,
+      "non_secure_clients": true,
+      "version": 1,
+      "version_policy": "STRICT"
+    },
+    {
+      "name": "TFM_FPU_SERVICE_CHECK_NS_INTERRUPT_S_TEST",
+      "sid": "0x0000F094",
+      "connection_based": true,
+      "non_secure_clients": true,
+      "version": 1,
+      "version_policy": "STRICT"
+    }
+  ],
+  "mmio_regions": [
+    {
+      "name": "TFM_PERIPHERAL_TIMER0",
+      "permission": "READ-WRITE"
+    },
+    {
+      "name": "TFM_PERIPHERAL_TIMER1",
+      "permission": "READ-WRITE"
     }
   ]
 }