Trusted Firmware-A Tests, version 2.0
This is the first public version of the tests for the Trusted
Firmware-A project. Please see the documentation provided in the
source tree for more details.
Change-Id: I6f3452046a1351ac94a71b3525c30a4ca8db7867
Signed-off-by: Sandrine Bailleux <sandrine.bailleux@arm.com>
Co-authored-by: amobal01 <amol.balasokamble@arm.com>
Co-authored-by: Antonio Nino Diaz <antonio.ninodiaz@arm.com>
Co-authored-by: Asha R <asha.r@arm.com>
Co-authored-by: Chandni Cherukuri <chandni.cherukuri@arm.com>
Co-authored-by: David Cunado <david.cunado@arm.com>
Co-authored-by: Dimitris Papastamos <dimitris.papastamos@arm.com>
Co-authored-by: Douglas Raillard <douglas.raillard@arm.com>
Co-authored-by: dp-arm <dimitris.papastamos@arm.com>
Co-authored-by: Jeenu Viswambharan <jeenu.viswambharan@arm.com>
Co-authored-by: Jonathan Wright <jonathan.wright@arm.com>
Co-authored-by: Kévin Petit <kevin.petit@arm.com>
Co-authored-by: Roberto Vargas <roberto.vargas@arm.com>
Co-authored-by: Sathees Balya <sathees.balya@arm.com>
Co-authored-by: Shawon Roy <Shawon.Roy@arm.com>
Co-authored-by: Soby Mathew <soby.mathew@arm.com>
Co-authored-by: Thomas Abraham <thomas.abraham@arm.com>
Co-authored-by: Vikram Kanigiri <vikram.kanigiri@arm.com>
Co-authored-by: Yatharth Kochar <yatharth.kochar@arm.com>
diff --git a/tftf/framework/aarch32/arch.c b/tftf/framework/aarch32/arch.c
new file mode 100644
index 0000000..763ea1a
--- /dev/null
+++ b/tftf/framework/aarch32/arch.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch_helpers.h>
+#include <debug.h>
+#include <tftf.h>
+
+
+void tftf_arch_setup(void)
+{
+ if (!IS_IN_HYP())
+ panic();
+
+ write_hcr(HCR_TGE_BIT);
+}
diff --git a/tftf/framework/aarch32/asm_debug.S b/tftf/framework/aarch32/asm_debug.S
new file mode 100644
index 0000000..d2b2c79
--- /dev/null
+++ b/tftf/framework/aarch32/asm_debug.S
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <asm_macros.S>
+
+#if ENABLE_ASSERTIONS
+
+ .globl asm_assert
+
+/* Since the max decimal input number is 65536 */
+#define MAX_DEC_DIVISOR 10000
+
+/* The offset to add to get ascii for numerals '0 - 9' */
+#define ASCII_OFFSET_NUM '0'
+
+.section .rodata.assert_str, "aS"
+assert_msg1:
+ .asciz "ASSERT: File "
+assert_msg2:
+ .asciz " Line "
+
+/* ---------------------------------------------------------------------------
+ * Assertion support in assembly.
+ * The below function helps to support assertions in assembly where we do not
+ * have a C runtime stack. Arguments to the function are :
+ * r0 - File name
+ * r1 - Line no
+ * Clobber list : lr, r0 - r6
+ * ---------------------------------------------------------------------------
+ */
+func asm_assert
+ /*
+ * Only print the output if LOG_LEVEL is higher or equal to
+ * LOG_LEVEL_INFO, which is the default value for builds with DEBUG=1.
+ */
+ /* Stash the parameters already in r0 and r1 */
+ mov r5, r0
+ mov r6, r1
+
+ /* Initialize crash console and verify success */
+ bl plat_crash_console_init
+ cmp r0, #0
+ beq 1f
+
+ /* Print file name */
+ ldr r4, =assert_msg1
+ bl asm_print_str
+ mov r4, r5
+ bl asm_print_str
+
+ /* Print line number string */
+ ldr r4, =assert_msg2
+ bl asm_print_str
+
+ /* Test for maximum supported line number */
+ ldr r4, =~0xffff
+ tst r6, r4
+ bne 1f
+ mov r4, r6
+
+ /* Print line number in decimal */
+ mov r6, #10 /* Divide by 10 after every loop iteration */
+ ldr r5, =MAX_DEC_DIVISOR
+dec_print_loop:
+ udiv r0, r4, r5 /* Quotient */
+ mls r4, r0, r5, r4 /* Remainder */
+ add r0, r0, #ASCII_OFFSET_NUM /* Convert to ASCII */
+ bl plat_crash_console_putc
+ udiv r5, r5, r6 /* Reduce divisor */
+ cmp r5, #0
+ bne dec_print_loop
+
+ bl plat_crash_console_flush
+
+1:
+ wfi
+ b 1b
+endfunc asm_assert
+
+/*
+ * This function prints a string from address in r4
+ * Clobber: lr, r0 - r4
+ */
+func asm_print_str
+ mov r3, lr
+1:
+ ldrb r0, [r4], #0x1
+ cmp r0, #0
+ beq 2f
+ bl plat_crash_console_putc
+ b 1b
+2:
+ bx r3
+endfunc asm_print_str
+
+/*
+ * This function prints a hexadecimal number in r4.
+ * In: r4 = the hexadecimal to print.
+ * Clobber: lr, r0 - r3, r5
+ */
+func asm_print_hex
+ mov r3, lr
+ mov r5, #32 /* No of bits to convert to ascii */
+1:
+ sub r5, r5, #4
+ lsr r0, r4, r5
+ and r0, r0, #0xf
+ cmp r0, #0xa
+ blo 2f
+ /* Add by 0x27 in addition to ASCII_OFFSET_NUM
+ * to get ascii for characters 'a - f'.
+ */
+ add r0, r0, #0x27
+2:
+ add r0, r0, #ASCII_OFFSET_NUM
+ bl plat_crash_console_putc
+ cmp r5, #0
+ bne 1b
+ bx r3
+endfunc asm_print_hex
+
+#endif /* ENABLE_ASSERTIONS */
diff --git a/tftf/framework/aarch32/entrypoint.S b/tftf/framework/aarch32/entrypoint.S
new file mode 100644
index 0000000..04a7d4c
--- /dev/null
+++ b/tftf/framework/aarch32/entrypoint.S
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <asm_macros.S>
+#include <tftf.h>
+
+ .globl tftf_entrypoint
+ .globl tftf_hotplug_entry
+
+/* ----------------------------------------------------------------------------
+ * Cold boot entry point for the primary CPU.
+ * ----------------------------------------------------------------------------
+ */
+func tftf_entrypoint
+ /* --------------------------------------------------------------------
+ * Set the exception vectors
+ * --------------------------------------------------------------------
+ */
+ ldr r0, =tftf_vector
+ stcopr r0, HVBAR
+
+ /* --------------------------------------------------------------------
+ * Enable the instruction cache and asynchronous interrupts.
+ * --------------------------------------------------------------------
+ */
+ ldcopr r0, HSCTLR
+ ldr r1, =(HSCTLR_I_BIT | HSCTLR_A_BIT)
+ orr r0, r0, r1
+ stcopr r0, HSCTLR
+ isb
+
+ /* --------------------------------------------------------------------
+ * This code is expected to be executed only by the primary CPU.
+ * Save the mpid for the first core that executes and if a secondary
+ * CPU has lost its way make it spin forever.
+ * --------------------------------------------------------------------
+ */
+ bl save_primary_mpid
+
+ /* --------------------------------------------------------------------
+ * Zero out NOBITS sections. There are 2 of them:
+ * - the .bss section;
+ * - the coherent memory section.
+ * --------------------------------------------------------------------
+ */
+ ldr r0, =__BSS_START__
+ ldr r1, =__BSS_SIZE__
+ bl zeromem
+
+ ldr r0, =__COHERENT_RAM_START__
+ ldr r1, =__COHERENT_RAM_UNALIGNED_SIZE__
+ bl zeromem
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a small coherent stack to ease the pain of
+ * initializing the MMU
+ * --------------------------------------------------------------------
+ */
+ ldcopr r0, MPIDR
+ bl platform_set_coherent_stack
+
+ bl tftf_early_platform_setup
+ bl tftf_plat_arch_setup
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a stack allocated in Normal -IS-WBWA memory
+ * --------------------------------------------------------------------
+ */
+ ldcopr r0, MPIDR
+ bl platform_set_stack
+
+ /* --------------------------------------------------------------------
+ * tftf_cold_boot_main() will perform the remaining architectural and
+ * platform setup, initialise the test framework's state, then run the
+ * tests.
+ * --------------------------------------------------------------------
+ */
+ b tftf_cold_boot_main
+endfunc tftf_entrypoint
+
+/* ----------------------------------------------------------------------------
+ * Entry point for a CPU that has just been powered up.
+ * In : r0 - context_id
+ * ----------------------------------------------------------------------------
+ */
+func tftf_hotplug_entry
+
+ /* --------------------------------------------------------------------
+ * Preserve the context_id in a callee-saved register
+ * --------------------------------------------------------------------
+ */
+ mov r4, r0
+
+ /* --------------------------------------------------------------------
+ * Set the exception vectors
+ * --------------------------------------------------------------------
+ */
+ ldr r0, =tftf_vector
+ stcopr r0, HVBAR
+
+ /* --------------------------------------------------------------------
+ * Enable the instruction cache and asynchronous interrupts.
+ * --------------------------------------------------------------------
+ */
+ ldcopr r0, HSCTLR
+ ldr r1, =(HSCTLR_I_BIT | HSCTLR_A_BIT)
+ orr r0, r0, r1
+ stcopr r0, HSCTLR
+ isb
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a small coherent stack to ease the pain of
+ * initializing the MMU
+ * --------------------------------------------------------------------
+ */
+ ldcopr r0, MPIDR
+ bl platform_set_coherent_stack
+
+ /* --------------------------------------------------------------------
+ * Enable the MMU
+ * --------------------------------------------------------------------
+ */
+ bl tftf_plat_enable_mmu
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a stack in normal memory.
+ * --------------------------------------------------------------------
+ */
+ ldcopr r0, MPIDR
+ bl platform_set_stack
+
+ /* --------------------------------------------------------------------
+ * Save the context_id for later retrieval by tests
+ * --------------------------------------------------------------------
+ */
+ ldcopr r0, MPIDR
+ ldr r1, =MPID_MASK
+ and r0, r0, r1
+ bl platform_get_core_pos
+
+ mov r1, r4
+
+ bl tftf_set_cpu_on_ctx_id
+
+ /* --------------------------------------------------------------------
+ * Jump to warm boot main function
+ * --------------------------------------------------------------------
+ */
+ b tftf_warm_boot_main
+endfunc tftf_hotplug_entry
+
+/* ----------------------------------------------------------------------------
+ * Saves the mpid of the primary core and if the primary core
+ * is already saved then it loops infinitely.
+ * ----------------------------------------------------------------------------
+ */
+func save_primary_mpid
+ ldr r1, =tftf_primary_core
+ ldr r0, [r1]
+ mov r2, #INVALID_MPID
+ cmp r0, r2
+ bne panic
+ ldr r2, =MPID_MASK
+ ldcopr r0, MPIDR
+ and r0, r0, r2
+ str r0, [r1]
+ bx lr
+panic:
+ /* Primary core MPID already saved */
+ b panic
+endfunc save_primary_mpid
diff --git a/tftf/framework/aarch32/exceptions.S b/tftf/framework/aarch32/exceptions.S
new file mode 100644
index 0000000..1e6c574
--- /dev/null
+++ b/tftf/framework/aarch32/exceptions.S
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <asm_macros.S>
+#include <tftf.h>
+
+ .globl tftf_vector
+
+vector_base tftf_vector
+ b tftf_entrypoint
+ b . /* Undef */
+ b . /* Syscall */
+ b . /* Prefetch abort */
+ b . /* Data abort */
+ b . /* Hyp trap */
+ b tftf_intr_handle/* IRQ */
+ b . /* FIQ */
+
+/* ----------------------------------------------------------------------------
+ * The IRQ exception handler
+ * ----------------------------------------------------------------------------
+ */
+func tftf_intr_handle
+ push {r0 - r3, lr}
+ bl tftf_irq_handler_dispatcher
+ pop {r0 - r3, lr}
+ eret
+endfunc tftf_intr_handle
diff --git a/tftf/framework/aarch64/arch.c b/tftf/framework/aarch64/arch.c
new file mode 100644
index 0000000..dfaa9d1
--- /dev/null
+++ b/tftf/framework/aarch64/arch.c
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch_helpers.h>
+
+void tftf_arch_setup(void)
+{
+ /* Do not try to configure EL2 if TFTF is running at NS-EL1 */
+ if (IS_IN_EL2()) {
+ write_hcr_el2(HCR_TGE_BIT);
+ isb();
+ }
+}
diff --git a/tftf/framework/aarch64/asm_debug.S b/tftf/framework/aarch64/asm_debug.S
new file mode 100644
index 0000000..32c454f
--- /dev/null
+++ b/tftf/framework/aarch64/asm_debug.S
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <asm_macros.S>
+
+#if ENABLE_ASSERTIONS
+
+ .globl asm_assert
+
+/* Since the max decimal input number is 65536 */
+#define MAX_DEC_DIVISOR 10000
+/* The offset to add to get ascii for numerals '0 - 9' */
+#define ASCII_OFFSET_NUM 0x30
+
+.section .rodata.assert_str, "aS"
+assert_msg1:
+ .asciz "ASSERT: File "
+assert_msg2:
+ .asciz " Line "
+
+ /*
+ * This macro is intended to be used to print the
+ * line number in decimal. Used by asm_assert macro.
+ * The max number expected is 65536.
+ * In: x4 = the decimal to print.
+ * Clobber: x30, x0, x1, x2, x5, x6
+ */
+ .macro asm_print_line_dec
+ mov x6, #10 /* Divide by 10 after every loop iteration */
+ mov x5, #MAX_DEC_DIVISOR
+dec_print_loop:
+ udiv x0, x4, x5 /* Get the quotient */
+ msub x4, x0, x5, x4 /* Find the remainder */
+ add x0, x0, #ASCII_OFFSET_NUM /* Convert to ascii */
+ bl plat_crash_console_putc
+ udiv x5, x5, x6 /* Reduce divisor */
+ cbnz x5, dec_print_loop
+ .endm
+
+/* ---------------------------------------------------------------------------
+ * Assertion support in assembly.
+ * The below function helps to support assertions in assembly where we do not
+ * have a C runtime stack. Arguments to the function are :
+ * x0 - File name
+ * x1 - Line no
+ * Clobber list : x30, x0, x1, x2, x3, x4, x5, x6.
+ * ---------------------------------------------------------------------------
+ */
+func asm_assert
+ mov x5, x0
+ mov x6, x1
+ /* Ensure the console is initialized */
+ bl plat_crash_console_init
+ /* Check if the console is initialized */
+ cbz x0, _assert_loop
+ /* The console is initialized */
+ adr x4, assert_msg1
+ bl asm_print_str
+ mov x4, x5
+ bl asm_print_str
+ adr x4, assert_msg2
+ bl asm_print_str
+ /* Check if line number higher than max permitted */
+ tst x6, #~0xffff
+ b.ne _assert_loop
+ mov x4, x6
+ asm_print_line_dec
+ bl plat_crash_console_flush
+_assert_loop:
+ wfi
+ b _assert_loop
+endfunc asm_assert
+
+/*
+ * This function prints a string from address in x4.
+ * In: x4 = pointer to string.
+ * Clobber: x30, x0, x1, x2, x3
+ */
+func asm_print_str
+ mov x3, x30
+1:
+ ldrb w0, [x4], #0x1
+ cbz x0, 2f
+ bl plat_crash_console_putc
+ b 1b
+2:
+ ret x3
+endfunc asm_print_str
+
+/*
+ * This function prints a hexadecimal number in x4.
+ * In: x4 = the hexadecimal to print.
+ * Clobber: x30, x0 - x3, x5
+ */
+func asm_print_hex
+ mov x3, x30
+ mov x5, #64 /* No of bits to convert to ascii */
+1:
+ sub x5, x5, #4
+ lsrv x0, x4, x5
+ and x0, x0, #0xf
+ cmp x0, #0xA
+ b.lo 2f
+ /* Add by 0x27 in addition to ASCII_OFFSET_NUM
+ * to get ascii for characters 'a - f'.
+ */
+ add x0, x0, #0x27
+2:
+ add x0, x0, #ASCII_OFFSET_NUM
+ bl plat_crash_console_putc
+ cbnz x5, 1b
+ ret x3
+endfunc asm_print_hex
+
+#endif /* ENABLE_ASSERTIONS */
diff --git a/tftf/framework/aarch64/entrypoint.S b/tftf/framework/aarch64/entrypoint.S
new file mode 100644
index 0000000..dfedeae
--- /dev/null
+++ b/tftf/framework/aarch64/entrypoint.S
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <asm_macros.S>
+#include <tftf.h>
+
+ .globl tftf_entrypoint
+ .globl tftf_hotplug_entry
+
+
+/* ----------------------------------------------------------------------------
+ * Cold boot entry point for the primary CPU.
+ * ----------------------------------------------------------------------------
+ */
+func tftf_entrypoint
+ /* --------------------------------------------------------------------
+ * Set the exception vectors
+ * --------------------------------------------------------------------
+ */
+ adr x0, tftf_vector
+ asm_write_vbar_el1_or_el2 x1
+
+ /* --------------------------------------------------------------------
+ * Enable the instruction cache, stack pointer and data access
+ * alignment checks
+ * --------------------------------------------------------------------
+ */
+ mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT)
+ asm_read_sctlr_el1_or_el2
+ orr x0, x0, x1
+ asm_write_sctlr_el1_or_el2 x1
+ isb
+
+ /* --------------------------------------------------------------------
+ * This code is expected to be executed only by the primary CPU.
+ * Save the mpid for the first core that executes and if a secondary
+ * CPU has lost its way make it spin forever.
+ * --------------------------------------------------------------------
+ */
+ bl save_primary_mpid
+
+ /* --------------------------------------------------------------------
+ * Zero out NOBITS sections. There are 2 of them:
+ * - the .bss section;
+ * - the coherent memory section.
+ * --------------------------------------------------------------------
+ */
+ ldr x0, =__BSS_START__
+ ldr x1, =__BSS_SIZE__
+ bl zeromem16
+
+ ldr x0, =__COHERENT_RAM_START__
+ ldr x1, =__COHERENT_RAM_UNALIGNED_SIZE__
+ bl zeromem16
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a small coherent stack to ease the pain of
+ * initializing the MMU
+ * --------------------------------------------------------------------
+ */
+ mrs x0, mpidr_el1
+ bl platform_set_coherent_stack
+
+ bl tftf_early_platform_setup
+ bl tftf_plat_arch_setup
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a stack allocated in Normal -IS-WBWA memory
+ * --------------------------------------------------------------------
+ */
+ mrs x0, mpidr_el1
+ bl platform_set_stack
+
+ /* --------------------------------------------------------------------
+ * tftf_cold_boot_main() will perform the remaining architectural and
+ * platform setup, initialise the test framework's state, then run the
+ * tests.
+ * --------------------------------------------------------------------
+ */
+ b tftf_cold_boot_main
+
+dead:
+ b dead
+endfunc tftf_entrypoint
+
+/* ----------------------------------------------------------------------------
+ * Entry point for a CPU that has just been powered up.
+ * In : x0 - context_id
+ * ----------------------------------------------------------------------------
+ */
+func tftf_hotplug_entry
+
+ /* --------------------------------------------------------------------
+ * Preserve the context_id in a callee-saved register
+ * --------------------------------------------------------------------
+ */
+ mov x19, x0
+
+ /* --------------------------------------------------------------------
+ * Set the exception vectors
+ * --------------------------------------------------------------------
+ */
+ adr x0, tftf_vector
+ asm_write_vbar_el1_or_el2 x1
+
+ /* --------------------------------------------------------------------
+ * Enable the instruction cache, stack pointer and data access
+ * alignment checks
+ * --------------------------------------------------------------------
+ */
+ mov x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT)
+ asm_read_sctlr_el1_or_el2
+ orr x0, x0, x1
+ asm_write_sctlr_el1_or_el2 x1
+ isb
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a small coherent stack to ease the pain of
+ * initializing the MMU
+ * --------------------------------------------------------------------
+ */
+ mrs x0, mpidr_el1
+ bl platform_set_coherent_stack
+
+ /* --------------------------------------------------------------------
+ * Enable the MMU
+ * --------------------------------------------------------------------
+ */
+ bl tftf_plat_enable_mmu
+
+ /* --------------------------------------------------------------------
+ * Give ourselves a stack in normal memory.
+ * --------------------------------------------------------------------
+ */
+ mrs x0, mpidr_el1
+ bl platform_set_stack
+
+ /* --------------------------------------------------------------------
+ * Save the context_id for later retrieval by tests
+ * --------------------------------------------------------------------
+ */
+ mrs x0, mpidr_el1
+ and x0, x0, #MPID_MASK
+ bl platform_get_core_pos
+
+ mov x1, x19
+
+ bl tftf_set_cpu_on_ctx_id
+
+ /* --------------------------------------------------------------------
+ * Jump to warm boot main function
+ * --------------------------------------------------------------------
+ */
+ b tftf_warm_boot_main
+endfunc tftf_hotplug_entry
+
+/* ----------------------------------------------------------------------------
+ * Saves the mpid of the primary core and if the primary core
+ * is already saved then it loops infinitely.
+ * ----------------------------------------------------------------------------
+ */
+func save_primary_mpid
+ adrp x1, tftf_primary_core
+ ldr w0, [x1, :lo12:tftf_primary_core]
+ mov w2, #INVALID_MPID
+ cmp w0, w2
+ b.ne panic
+ mov x2, #MPID_MASK
+ mrs x0, mpidr_el1
+ and x0, x0, x2
+ str w0, [x1, :lo12:tftf_primary_core]
+ ret
+panic:
+ /* Primary core MPID already saved */
+ b .
+ ret
+endfunc save_primary_mpid
diff --git a/tftf/framework/aarch64/exceptions.S b/tftf/framework/aarch64/exceptions.S
new file mode 100644
index 0000000..08bef46
--- /dev/null
+++ b/tftf/framework/aarch64/exceptions.S
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <asm_macros.S>
+
+ .globl tftf_vector
+
+vector_base tftf_vector
+ //-----------------------------------------------------
+ // Current EL with SP0 : 0x0 - 0x180
+ //-----------------------------------------------------
+vector_entry SynchronousExceptionSP0
+ b SynchronousExceptionSP0
+ check_vector_size SynchronousExceptionSP0
+
+vector_entry IrqSP0
+ b IrqSP0
+ check_vector_size IrqSP0
+
+vector_entry FiqSP0
+ b FiqSP0
+ check_vector_size FiqSP0
+
+vector_entry SErrorSP0
+ b SErrorSP0
+ check_vector_size SErrorSP0
+
+ //-----------------------------------------------------
+ // Current EL with SPx: 0x200 - 0x380
+ //-----------------------------------------------------
+vector_entry SynchronousExceptionSPx
+ b SynchronousExceptionSPx
+ check_vector_size SynchronousExceptionSPx
+
+vector_entry IrqSPx
+ /*
+ * TODO: Investigate whether the Trusted Firmware-A code for context
+ * save/restore could be reused
+ */
+ stp x29, x30, [sp, #-0x10]!
+ bl save_regs
+ bl tftf_irq_handler_dispatcher
+ bl restore_regs
+ ldp x29, x30, [sp], #0x10
+ eret
+ check_vector_size IrqSPx
+
+vector_entry FiqSPx
+ b FiqSPx
+ check_vector_size FiqSPx
+
+vector_entry SErrorSPx
+ b SErrorSPx
+ check_vector_size SErrorSPx
+
+ //-----------------------------------------------------
+ // Lower EL using AArch64 : 0x400 - 0x580
+ //-----------------------------------------------------
+vector_entry SynchronousExceptionA64
+ b SynchronousExceptionA64
+ check_vector_size SynchronousExceptionA64
+
+vector_entry IrqA64
+ b IrqA64
+ check_vector_size IrqA64
+
+vector_entry FiqA64
+ b FiqA64
+ check_vector_size FiqA64
+
+vector_entry SErrorA64
+ b SErrorA64
+ check_vector_size SErrorA64
+
+ //-----------------------------------------------------
+ // Lower EL using AArch32 : 0x0 - 0x180
+ //-----------------------------------------------------
+vector_entry SynchronousExceptionA32
+ b SynchronousExceptionA32
+ check_vector_size SynchronousExceptionA32
+
+vector_entry IrqA32
+ b IrqA32
+ check_vector_size IrqA32
+
+vector_entry FiqA32
+ b FiqA32
+ check_vector_size FiqA32
+
+vector_entry SErrorA32
+ b SErrorA32
+ check_vector_size SErrorA32
+
+
+// Note: Exceptions will always be from the same EL, so no need to save spsr
+func save_regs
+ sub sp, sp, #0x100
+ stp x0, x1, [sp, #0x0]
+ stp x2, x3, [sp, #0x10]
+ stp x4, x5, [sp, #0x20]
+ stp x6, x7, [sp, #0x30]
+ stp x8, x9, [sp, #0x40]
+ stp x10, x11, [sp, #0x50]
+ stp x12, x13, [sp, #0x60]
+ stp x14, x15, [sp, #0x70]
+ stp x16, x17, [sp, #0x80]
+ stp x18, x19, [sp, #0x90]
+ stp x20, x21, [sp, #0xa0]
+ stp x22, x23, [sp, #0xb0]
+ stp x24, x25, [sp, #0xc0]
+ stp x26, x27, [sp, #0xd0]
+ mrs x0, sp_el0
+ stp x28, x0, [sp, #0xe0]
+ str x0, [sp, #0xf0]
+ ret
+endfunc save_regs
+
+
+// Note: Exceptions will always be from the same EL, so no need to restore spsr
+func restore_regs
+ ldr x9, [sp, #0xf0]
+ ldp x28, x9, [sp, #0xe0]
+ msr sp_el0, x9
+ ldp x26, x27, [sp, #0xd0]
+ ldp x24, x25, [sp, #0xc0]
+ ldp x22, x23, [sp, #0xb0]
+ ldp x20, x21, [sp, #0xa0]
+ ldp x18, x19, [sp, #0x90]
+ ldp x16, x17, [sp, #0x80]
+ ldp x14, x15, [sp, #0x70]
+ ldp x12, x13, [sp, #0x60]
+ ldp x10, x11, [sp, #0x50]
+ ldp x8, x9, [sp, #0x40]
+ ldp x6, x7, [sp, #0x30]
+ ldp x4, x5, [sp, #0x20]
+ ldp x2, x3, [sp, #0x10]
+ ldp x0, x1, [sp, #0x0]
+ add sp, sp, #0x100
+ ret
+endfunc restore_regs
diff --git a/tftf/framework/debug.c b/tftf/framework/debug.c
new file mode 100644
index 0000000..4b4364d
--- /dev/null
+++ b/tftf/framework/debug.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <console.h>
+#include <debug.h>
+
+#if DEBUG
+void __attribute__((__noreturn__)) do_panic(const char *file, int line)
+{
+ printf("PANIC in file: %s line: %d\n", file, line);
+
+ console_flush();
+
+ while (1)
+ continue;
+}
+
+void __attribute__((__noreturn__)) do_bug_unreachable(const char *file, int line)
+{
+ mp_printf("BUG: Unreachable code!\n");
+ do_panic(file, line);
+}
+
+#else
+void __attribute__((__noreturn__)) do_panic(void)
+{
+ printf("PANIC\n");
+
+ console_flush();
+
+ while (1)
+ continue;
+}
+
+void __attribute__((__noreturn__)) do_bug_unreachable(void)
+{
+ mp_printf("BUG: Unreachable code!\n");
+ do_panic();
+}
+#endif
diff --git a/tftf/framework/framework.mk b/tftf/framework/framework.mk
new file mode 100644
index 0000000..f6e1d7b
--- /dev/null
+++ b/tftf/framework/framework.mk
@@ -0,0 +1,115 @@
+#
+# Copyright (c) 2018, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+AUTOGEN_DIR := $(BUILD_PLAT)/autogen
+
+include lib/xlat_tables_v2/xlat_tables.mk
+include lib/compiler-rt/compiler-rt.mk
+
+TFTF_INCLUDES := \
+ -I${AUTOGEN_DIR} \
+ -Itftf/framework/include \
+ -Iinclude/common \
+ -Iinclude/common/${ARCH} \
+ -Iinclude/drivers \
+ -Iinclude/drivers/arm \
+ -Iinclude/drivers/io \
+ -Iinclude/lib \
+ -Iinclude/lib/${ARCH} \
+ -Iinclude/lib/extensions \
+ -Iinclude/lib/stdlib \
+ -Iinclude/lib/stdlib/sys \
+ -Iinclude/lib/utils \
+ -Iinclude/lib/xlat_tables \
+ -Iinclude/plat/common \
+ -Iinclude/runtime_services \
+ -Iinclude/runtime_services/secure_el0_payloads \
+ -Iinclude/runtime_services/secure_el1_payloads
+
+# Standard C library source files
+STD_LIB_SOURCES := lib/stdlib/abort.c \
+ lib/stdlib/assert.c \
+ lib/stdlib/mem.c \
+ lib/stdlib/printf.c \
+ lib/stdlib/putchar.c \
+ lib/stdlib/puts.c \
+ lib/stdlib/rand.c \
+ lib/stdlib/strchr.c \
+ lib/stdlib/strcmp.c \
+ lib/stdlib/strlen.c \
+ lib/stdlib/strncmp.c \
+ lib/stdlib/strncpy.c \
+ lib/stdlib/subr_prf.c
+
+FRAMEWORK_SOURCES := ${AUTOGEN_DIR}/tests_list.c
+
+FRAMEWORK_SOURCES += $(addprefix tftf/, \
+ framework/${ARCH}/arch.c \
+ framework/${ARCH}/asm_debug.S \
+ framework/${ARCH}/entrypoint.S \
+ framework/${ARCH}/exceptions.S \
+ framework/debug.c \
+ framework/main.c \
+ framework/nvm_results_helpers.c \
+ framework/report.c \
+ framework/timer/timer_framework.c \
+ tests/common/test_helpers.c \
+)
+
+FRAMEWORK_SOURCES += \
+ lib/${ARCH}/cache_helpers.S \
+ lib/${ARCH}/misc_helpers.S \
+ lib/delay/delay.c \
+ lib/events/events.c \
+ lib/extensions/amu/${ARCH}/amu.c \
+ lib/extensions/amu/${ARCH}/amu_helpers.S \
+ lib/irq/irq.c \
+ lib/locks/${ARCH}/spinlock.S \
+ lib/power_management/hotplug/hotplug.c \
+ lib/power_management/suspend/${ARCH}/asm_tftf_suspend.S \
+ lib/power_management/suspend/tftf_suspend.c \
+ lib/psci/psci.c \
+ lib/sdei/sdei.c \
+ lib/smc/${ARCH}/asm_smc.S \
+ lib/smc/${ARCH}/smc.c \
+ ${STD_LIB_SOURCES} \
+ lib/trusted_os/trusted_os.c \
+ lib/utils/mp_printf.c \
+ lib/utils/uuid.c \
+ ${XLAT_TABLES_LIB_SRCS} \
+ plat/common/${ARCH}/platform_helpers.S \
+ plat/common/${ARCH}/platform_mp_stack.S \
+ plat/common/plat_common.c \
+ plat/common/plat_state_id.c \
+ plat/common/plat_topology.c \
+ plat/common/tftf_nvm_accessors.c
+
+
+FRAMEWORK_SOURCES += ${COMPILER_RT_SRCS}
+
+TFTF_LINKERFILE := tftf/framework/tftf.ld.S
+
+
+TFTF_DEFINES :=
+
+TEST_REPORTS ?= uart:raw
+$(info Selected reports: $(TEST_REPORTS))
+ifneq (,$(findstring uart:raw,$(TEST_REPORTS)))
+ TFTF_DEFINES += -DTEST_REPORT_UART_RAW
+endif
+ifneq (,$(findstring uart:junit,$(TEST_REPORTS)))
+ TFTF_DEFINES += -DTEST_REPORT_UART_JUNIT
+endif
+ifneq (,$(findstring semihosting:raw,$(TEST_REPORTS)))
+ TFTF_DEFINES += -DTEST_REPORT_SEMIHOSTING_RAW
+endif
+ifneq (,$(findstring semihosting:junit,$(TEST_REPORTS)))
+ TFTF_DEFINES += -DTEST_REPORT_SEMIHOSTING_JUNIT
+endif
+
+# Enable dynamic translation tables
+PLAT_XLAT_TABLES_DYNAMIC := 1
+$(eval $(call add_define,TFTF_DEFINES,PLAT_XLAT_TABLES_DYNAMIC))
diff --git a/tftf/framework/include/nvm.h b/tftf/framework/include/nvm.h
new file mode 100644
index 0000000..3544c2a
--- /dev/null
+++ b/tftf/framework/include/nvm.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef __NVM_H__
+#define __NVM_H__
+
+#ifndef __ASSEMBLY__
+#include <stddef.h>
+#include <tftf.h>
+#include "tests_list.h"
+
+#define TEST_BUFFER_SIZE 0x80
+
+typedef struct {
+ /*
+ * @brief Last executed TFTF build message which consists of date and
+ * time when TFTF is built.
+ *
+ * A mismatch with the build message of currently executing binary will
+ * determine whether TFTF data structures stored in NVM needs to be
+ * initialised or not.
+ */
+ char build_message[BUILD_MESSAGE_SIZE];
+
+ /*
+ * The following 2 fields track the progress in the test session. They
+ * indicate which test case we are dealing with and the progress of this
+ * test, i.e. whether it hasn't started yet, or it is being executed
+ * right now, ...
+ */
+ test_ref_t test_to_run;
+ test_progress_t test_progress;
+
+ /*
+ * @brief Scratch buffer for test internal use.
+ *
+ * A buffer that the test can use as a scratch area for whatever it is
+ * doing.
+ */
+ char testcase_buffer[TEST_BUFFER_SIZE];
+
+ /*
+ * @brief Results of tests.
+ *
+ * @note TESTCASE_RESULT_COUNT is defined in tests_list.h
+ * (auto-generated file).
+ */
+ TESTCASE_RESULT testcase_results[TESTCASE_RESULT_COUNT];
+
+ /*
+ * @brief Size of \a result_buffer.
+ */
+ unsigned result_buffer_size;
+
+ /*
+ * Buffer containing the output of all tests.
+ * Each test appends its output to the end of \a result_buffer.
+ * Tests which produce no output write nothing in \a result_buffer.
+ */
+ char *result_buffer;
+} tftf_state_t;
+
+/*
+ * Helper macros to access fields of \a tftf_state_t structure.
+ */
+#define TFTF_STATE_OFFSET(_field) offsetof(tftf_state_t, _field)
+
+/*
+ * Return 1 if we need to start a new test session;
+ * 0 if we need to resume an interrupted session.
+ */
+unsigned int new_test_session(void);
+
+/*
+ * @brief Initialize NVM if necessary.
+ *
+ * When TFTF is launched on the target, its data structures need
+ * to be initialised in NVM. However if some test resets the board
+ * (as part of its normal behaviour or because it crashed) then
+ * TFTF data structure must be left unchanged in order to resume
+ * the test session where it has been left.
+ *
+ * This function detects whether TFTF has just been launched and if so
+ * initialises its data structures. If TFTF has just reset then it does
+ * nothing.
+ *
+ * @return STATUS_SUCCESS on success, another status code on failure.
+ */
+STATUS tftf_init_nvm(void);
+
+/*
+ * @brief Clean NVM.
+ *
+ * Clean TFTF data structures in NVM.
+ * This function must be called when all tests have completed.
+ *
+ * @return STATUS_SUCCESS on success, another status code on failure.
+ */
+STATUS tftf_clean_nvm(void);
+
+/* Writes the buffer to the flash at offset with length equal to
+ * size
+ * Returns: STATUS_FAIL, STATUS_SUCCESS, STATUS_OUT_OF_RESOURCES
+ */
+STATUS tftf_nvm_write(unsigned long long offset, const void *buffer, size_t size);
+
+/* Reads the flash into buffer at offset with length equal to
+ * size
+ * Returns: STATUS_FAIL, STATUS_SUCCESS, STATUS_OUT_OF_RESOURCES
+ */
+STATUS tftf_nvm_read(unsigned long long offset, void *buffer, size_t size);
+#endif /*__ASSEMBLY__*/
+
+#endif
diff --git a/tftf/framework/include/tftf.h b/tftf/framework/include/tftf.h
new file mode 100644
index 0000000..8231e28
--- /dev/null
+++ b/tftf/framework/include/tftf.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef __TFTF_H__
+#define __TFTF_H__
+
+#ifndef __ASSEMBLY__
+#include <status.h>
+#include <stddef.h>
+#include <tftf_lib.h>
+
+#define TFTF_WELCOME_STR "Booting trusted firmware test framework"
+
+/* Maximum size of test output (in bytes) */
+#define TESTCASE_OUTPUT_MAX_SIZE 512
+
+/* Size of build message used to differentiate different TFTF binaries */
+#define BUILD_MESSAGE_SIZE 0x20
+
+extern const char build_message[];
+
+typedef test_result_t (*test_function_t)(void);
+
+typedef struct {
+ /* Test result (success, crashed, failed, ...). */
+ test_result_t result;
+ unsigned long long duration;
+ /*
+ * Offset of test output string from TEST_NVM_RESULT_BUFFER_OFFSET.
+ * Only relevant if test has an output, i.e. if \a output_size is not
+ * zero.
+ */
+ unsigned output_offset;
+ /* Size of test output string, excluding final \0. */
+ unsigned output_size;
+} TESTCASE_RESULT;
+
+typedef struct {
+ unsigned index;
+ const char *name;
+ const char *description;
+ test_function_t test;
+} test_case_t;
+
+typedef struct {
+ const char *name;
+ const char *description;
+ const test_case_t *testcases;
+} test_suite_t;
+
+/*
+ * Reference to a specific test.
+ */
+typedef struct {
+ unsigned int testsuite_idx;
+ unsigned int testcase_idx;
+} test_ref_t;
+
+/*
+ * The progress in the execution of a test.
+ * This is used to implement the following state machine.
+ *
+ * +-> TEST_READY (initial state of the test) <--------------+
+ * | | |
+ * | | Test framework prepares the test environment. |
+ * | | |
+ * | v |
+ * | TEST_IN_PROGRESS |
+ * | | |
+ * | | Hand over to the test function. |
+ * | | If the test wants to reboot the platform ---> TEST_REBOOTING |
+ * | | Test function returns into framework. | |
+ * | | | Reboot |
+ * | | | |
+ * | | +---------+
+ * | v
+ * | TEST_COMPLETE
+ * | |
+ * | | Do some framework management.
+ * | | Move to next test.
+ * +--------+
+ */
+typedef enum {
+ TEST_PROGRESS_MIN = 0,
+ TEST_READY = TEST_PROGRESS_MIN,
+ TEST_IN_PROGRESS,
+ TEST_COMPLETE,
+ TEST_REBOOTING,
+
+ TEST_PROGRESS_MAX,
+} test_progress_t;
+
+#define TEST_PROGRESS_IS_VALID(_progress) \
+ ((_progress >= TEST_PROGRESS_MIN) && (_progress < TEST_PROGRESS_MAX))
+
+/*
+ * The definition of this global variable is generated by the script
+ * 'tftf_generate_test_list' during the build process
+ */
+extern const test_suite_t testsuites[];
+
+extern TESTCASE_RESULT testcase_results[];
+
+/* Set/Get the test to run in NVM */
+STATUS tftf_set_test_to_run(const test_ref_t test_to_run);
+STATUS tftf_get_test_to_run(test_ref_t *test_to_run);
+/* Set/Get the progress of the current test in NVM */
+STATUS tftf_set_test_progress(test_progress_t test_progress);
+STATUS tftf_get_test_progress(test_progress_t *test_progress);
+
+/**
+** Save test result into NVM.
+*/
+STATUS tftf_testcase_set_result(const test_case_t *testcase,
+ test_result_t result,
+ unsigned long long duration);
+/**
+** Get a testcase result from NVM.
+**
+** @param[in] testcase The targeted testcase.
+** @param[out] result Testcase result. Only \a result.result and
+** \a result.duration are of interest for the caller and the 2 other fields
+** should be ignored (they correspond to a location in NVM).
+** @param[out] test_output Buffer to store the test output, if any.
+** \a test_output must be big enough to hold the whole test output.
+** Test output will be \a TESTCASE_OUTPUT_MAX_SIZE bytes maximum.
+*/
+STATUS tftf_testcase_get_result(const test_case_t *testcase, TESTCASE_RESULT *result, char *test_output);
+
+void tftf_report_generate(void);
+
+/*
+ * Exit the TFTF.
+ * This function can be used when a fatal error is encountered or as part of the
+ * normal termination process. It does the necessary cleanups then put the
+ * core in a low-power state.
+ */
+void __dead2 tftf_exit(void);
+
+void tftf_arch_setup(void);
+
+/*
+ * This function detects the power state format used by PSCI which can
+ * be either extended or original format. For the Original format,
+ * the State-ID can either be NULL or can be using the recommended encoding.
+ * This function needs to be invoked once during cold boot prior to the
+ * invocation of any PSCI power state helper functions.
+ */
+void tftf_detect_psci_pstate_format(void);
+
+/*
+ * Run the next test on the calling CPU.
+ * Once the test is complete, if the calling CPU is the last one to exit the
+ * test then do the necessary bookkeeping, report the overall test result and
+ * move on to the next test. Otherwise, shut down the calling CPU.
+ *
+ * This function never returns.
+ */
+void __dead2 run_tests(void);
+
+/* Entry point for a CPU that has just been powered up */
+void tftf_hotplug_entry(void);
+
+#endif /*__ASSEMBLY__*/
+
+#endif
diff --git a/tftf/framework/main.c b/tftf/framework/main.c
new file mode 100644
index 0000000..3f94dc9
--- /dev/null
+++ b/tftf/framework/main.c
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch_helpers.h>
+#include <arm_gic.h>
+#include <assert.h>
+#include <debug.h>
+#include <irq.h>
+#include <mmio.h>
+#include <nvm.h>
+#include <plat_topology.h>
+#include <platform.h>
+#include <platform_def.h>
+#include <power_management.h>
+#include <psci.h>
+#include <sgi.h>
+#include <string.h>
+#include <sys/types.h>
+#include <tftf.h>
+#include <tftf_lib.h>
+#include <timer.h>
+
+/* version information for TFTF */
+extern const char version_string[];
+
+unsigned int lead_cpu_mpid;
+
+/* Defined in hotplug.c */
+extern volatile test_function_t test_entrypoint[PLATFORM_CORE_COUNT];
+
+/* Per-CPU results for the current test */
+static test_result_t test_results[PLATFORM_CORE_COUNT];
+
+/* Context ID passed to tftf_psci_cpu_on() */
+static u_register_t cpu_on_ctx_id_arr[PLATFORM_CORE_COUNT];
+
+static unsigned int test_is_rebooting;
+
+static inline const test_suite_t *current_testsuite(void)
+{
+ test_ref_t test_to_run;
+ tftf_get_test_to_run(&test_to_run);
+ return &testsuites[test_to_run.testsuite_idx];
+}
+
+static inline const test_case_t *current_testcase(void)
+{
+ test_ref_t test_to_run;
+ tftf_get_test_to_run(&test_to_run);
+ return &testsuites[test_to_run.testsuite_idx].
+ testcases[test_to_run.testcase_idx];
+}
+
+/*
+ * Identify the next test in the tests list and update the NVM data to point to
+ * that test.
+ * If there is no more tests to execute, return NULL.
+ * Otherwise, return the test case.
+ */
+static const test_case_t *advance_to_next_test(void)
+{
+ test_ref_t test_to_run;
+ const test_case_t *testcase;
+ unsigned int testcase_idx;
+ unsigned int testsuite_idx;
+
+#if DEBUG
+ test_progress_t progress;
+ tftf_get_test_progress(&progress);
+ assert(progress == TEST_COMPLETE);
+#endif
+
+ tftf_get_test_to_run(&test_to_run);
+ testcase_idx = test_to_run.testcase_idx;
+ testsuite_idx = test_to_run.testsuite_idx;
+
+ /* Move to the next test case in the current test suite */
+ ++testcase_idx;
+ testcase = &testsuites[testsuite_idx].testcases[testcase_idx];
+
+ if (testcase->name == NULL) {
+ /*
+ * There's no more test cases in the current test suite so move
+ * to the first test case of the next test suite.
+ */
+ const test_suite_t *testsuite;
+ testcase_idx = 0;
+ ++testsuite_idx;
+ testsuite = &testsuites[testsuite_idx];
+ testcase = &testsuite->testcases[0];
+
+ if (testsuite->name == NULL) {
+ /*
+ * This was the last test suite so there's no more tests
+ * at all.
+ */
+ return NULL;
+ }
+ }
+
+ VERBOSE("Moving to test (%u,%u)\n", testsuite_idx, testcase_idx);
+ test_to_run.testsuite_idx = testsuite_idx;
+ test_to_run.testcase_idx = testcase_idx;
+ tftf_set_test_to_run(test_to_run);
+ tftf_set_test_progress(TEST_READY);
+
+ return testcase;
+}
+
+/*
+ * This function is executed only by the lead CPU.
+ * It prepares the environment for the next test to run.
+ */
+static void prepare_next_test(void)
+{
+ unsigned int mpid;
+ unsigned int core_pos;
+ unsigned int cpu_node;
+
+ /* This function should be called by the lead CPU only */
+ assert((read_mpidr_el1() & MPID_MASK) == lead_cpu_mpid);
+
+ /*
+ * Only the lead CPU should be powered on at this stage. All other CPUs
+ * should be powered off or powering off. If some CPUs are not powered
+ * off yet, wait for them to power off.
+ */
+ for_each_cpu(cpu_node) {
+ mpid = tftf_get_mpidr_from_node(cpu_node);
+ if (mpid == lead_cpu_mpid)
+ assert(tftf_is_cpu_online(mpid));
+ else
+ while (tftf_psci_affinity_info(mpid, MPIDR_AFFLVL0)
+ == PSCI_STATE_ON)
+ ;
+ }
+
+ /* No CPU should have entered the test yet */
+ assert(tftf_get_ref_cnt() == 0);
+
+ /* Populate the test entrypoint for the lead CPU */
+ core_pos = platform_get_core_pos(lead_cpu_mpid);
+ test_entrypoint[core_pos] = (test_function_t) current_testcase()->test;
+
+ for (unsigned int i = 0; i < PLATFORM_CORE_COUNT; ++i)
+ test_results[i] = TEST_RESULT_NA;
+
+ NOTICE("Starting unittest '%s - %s'\n",
+ current_testsuite()->name, current_testcase()->name);
+
+ /* Program the watchdog */
+ tftf_platform_watchdog_set();
+
+ /* TODO: Take a 1st timestamp to be able to measure test duration */
+
+ tftf_set_test_progress(TEST_IN_PROGRESS);
+}
+
+/*
+ * Go through individual CPUs' test results and determine the overall
+ * test result from that.
+ */
+static test_result_t get_overall_test_result(void)
+{
+ test_result_t result = TEST_RESULT_NA;
+ unsigned int cpu_mpid;
+ unsigned int cpu_node;
+ unsigned int core_pos;
+
+ for_each_cpu(cpu_node) {
+ cpu_mpid = tftf_get_mpidr_from_node(cpu_node);
+ core_pos = platform_get_core_pos(cpu_mpid);
+
+ switch (test_results[core_pos]) {
+ case TEST_RESULT_NA:
+ VERBOSE("CPU%u not involved in the test\n", core_pos);
+ /* Ignoring */
+ break;
+
+ case TEST_RESULT_SKIPPED:
+ /*
+ * If at least one CPU skipped the test, consider the
+ * whole test as skipped as well.
+ */
+ NOTICE("CPU%u skipped the test\n", core_pos);
+ return TEST_RESULT_SKIPPED;
+
+ case TEST_RESULT_SUCCESS:
+ result = TEST_RESULT_SUCCESS;
+ break;
+
+ case TEST_RESULT_FAIL:
+ ERROR("CPU%u failed the test\n", core_pos);
+ return TEST_RESULT_FAIL;
+
+ case TEST_RESULT_CRASHED:
+ /*
+ * Means the CPU never returned from the test whereas it
+ * was supposed to. Either there is a bug in the test's
+ * implementation or some sort of unexpected crash
+ * happened.
+ * If at least one CPU crashed, consider the whole test
+ * as crashed as well.
+ */
+ ERROR("CPU%u never returned from the test!\n", core_pos);
+ return TEST_RESULT_CRASHED;
+
+ default:
+ ERROR("Unknown test result value: %u\n",
+ test_results[core_pos]);
+ panic();
+ }
+ }
+
+ /*
+ * At least one CPU (i.e. the lead CPU) should have participated in the
+ * test.
+ */
+ assert(result != TEST_RESULT_NA);
+ return result;
+}
+
+/*
+ * This function is executed by the last CPU to exit the test only.
+ * It does the necessary bookkeeping and reports the overall test result.
+ * If it was the last test, it will also generate the final test report.
+ * Otherwise, it will reset the platform, provided that the platform
+ * supports reset from non-trusted world. This ensures that the next test
+ * runs in a clean environment
+ *
+ * Return 1 if this was the last test, 0 otherwise.
+ */
+static unsigned int close_test(void)
+{
+ const test_case_t *next_test;
+
+#if DEBUG
+ /*
+ * Check that the test didn't pretend resetting the platform, when in
+ * fact it returned into the framework.
+ *
+ * If that happens, the test implementation should be fixed.
+ * However, it is not a fatal error so just flag the problem in debug
+ * builds.
+ */
+ test_progress_t progress;
+ tftf_get_test_progress(&progress);
+ assert(progress != TEST_REBOOTING);
+#endif /* DEBUG */
+
+ tftf_set_test_progress(TEST_COMPLETE);
+ test_is_rebooting = 0;
+
+ /* TODO: Take a 2nd timestamp and compute test duration */
+
+ /* Reset watchdog */
+ tftf_platform_watchdog_reset();
+
+ /* Ensure no CPU is still executing the test */
+ assert(tftf_get_ref_cnt() == 0);
+
+ /* Save test result in NVM */
+ test_result_t overall_test_result = get_overall_test_result();
+ tftf_testcase_set_result(current_testcase(),
+ overall_test_result,
+ 0);
+
+ NOTICE("Unittest '%s - %s' complete. Result: %s\n",
+ current_testsuite()->name, current_testcase()->name,
+ test_result_to_string(overall_test_result));
+
+ /* The test is finished, let's move to the next one (if any) */
+ next_test = advance_to_next_test();
+
+ /* If this was the last test then report all results */
+ if (!next_test) {
+ tftf_report_generate();
+ tftf_clean_nvm();
+ return 1;
+ } else {
+#if (PLAT_SUPPORTS_NS_RESET && !NEW_TEST_SESSION && USE_NVM)
+ /*
+ * Reset the platform so that the next test runs in a clean
+ * environment.
+ */
+ INFO("Reset platform before executing next test:%p\n",
+ (void *) &(next_test->test));
+ tftf_plat_reset();
+ bug_unreachable();
+#endif
+ }
+
+ return 0;
+}
+
+/*
+ * Hand over to lead CPU, i.e.:
+ * 1) Power on lead CPU
+ * 2) Power down calling CPU
+ */
+static void __dead2 hand_over_to_lead_cpu(void)
+{
+ int ret;
+ unsigned int mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int core_pos = platform_get_core_pos(mpid);
+
+ VERBOSE("CPU%u: Hand over to lead CPU%u\n", core_pos,
+ platform_get_core_pos(lead_cpu_mpid));
+
+ /*
+ * Power on lead CPU.
+ * The entry point address passed as the 2nd argument of tftf_cpu_on()
+ * doesn't matter because it will be overwritten by prepare_next_test().
+ * Pass a NULL pointer to easily catch the problem in case something
+ * goes wrong.
+ */
+ ret = tftf_cpu_on(lead_cpu_mpid, 0, 0);
+ if (ret != PSCI_E_SUCCESS) {
+ ERROR("CPU%u: Failed to power on lead CPU%u (%d)\n",
+ core_pos, platform_get_core_pos(lead_cpu_mpid), ret);
+ panic();
+ }
+
+ /* Wait for lead CPU to be actually powered on */
+ while (!tftf_is_cpu_online(lead_cpu_mpid))
+ ;
+
+ /*
+ * Lead CPU has successfully booted, let's now power down the calling
+ * core.
+ */
+ tftf_cpu_off();
+ panic();
+}
+
+void __dead2 run_tests(void)
+{
+ unsigned int mpid = read_mpidr_el1() & MPID_MASK;
+ unsigned int core_pos = platform_get_core_pos(mpid);
+ unsigned int test_session_finished;
+ unsigned int cpus_cnt;
+
+ while (1) {
+ if (mpid == lead_cpu_mpid && (tftf_get_ref_cnt() == 0))
+ prepare_next_test();
+
+ /*
+ * Increment the reference count to indicate that the CPU is
+ * participating in the test.
+ */
+ tftf_inc_ref_cnt();
+
+ /*
+ * Mark the CPU's test result as "crashed". This is meant to be
+ * overwritten by the actual test result when the CPU returns
+ * from the test function into the framework. In case the CPU
+ * crashes in the test (and thus, never returns from it), this
+ * variable will hold the right value.
+ */
+ test_results[core_pos] = TEST_RESULT_CRASHED;
+
+ /*
+ * Jump to the test entrypoint for this core.
+ * - For the lead CPU, it has been populated by
+ * prepare_next_test()
+ * - For other CPUs, it has been populated by tftf_cpu_on() or
+ * tftf_try_cpu_on()
+ */
+ while (test_entrypoint[core_pos] == 0)
+ ;
+
+ test_results[core_pos] = test_entrypoint[core_pos]();
+ test_entrypoint[core_pos] = 0;
+
+ /*
+ * Decrement the reference count to indicate that the CPU is not
+ * participating in the test any longer.
+ */
+ cpus_cnt = tftf_dec_ref_cnt();
+
+ /*
+ * Last CPU to exit the test gets to do the necessary
+ * bookkeeping and to report the overall test result.
+ * Other CPUs shut down.
+ */
+ if (cpus_cnt == 0) {
+ test_session_finished = close_test();
+ if (test_session_finished)
+ break;
+
+ if (mpid != lead_cpu_mpid) {
+ hand_over_to_lead_cpu();
+ bug_unreachable();
+ }
+ } else {
+ tftf_cpu_off();
+ panic();
+ }
+ }
+
+ tftf_exit();
+
+ /* Should never reach this point */
+ bug_unreachable();
+}
+
+u_register_t tftf_get_cpu_on_ctx_id(unsigned int core_pos)
+{
+ assert(core_pos < PLATFORM_CORE_COUNT);
+
+ return cpu_on_ctx_id_arr[core_pos];
+}
+
+void tftf_set_cpu_on_ctx_id(unsigned int core_pos, u_register_t context_id)
+{
+ assert(core_pos < PLATFORM_CORE_COUNT);
+
+ cpu_on_ctx_id_arr[core_pos] = context_id;
+}
+
+unsigned int tftf_is_rebooted(void)
+{
+ return test_is_rebooting;
+}
+
+/*
+ * Return 0 if the test session can be resumed
+ * -1 otherwise.
+ */
+static int resume_test_session(void)
+{
+ test_ref_t test_to_run;
+ test_progress_t test_progress;
+ const test_case_t *next_test;
+
+ /* Get back on our feet. Where did we stop? */
+ tftf_get_test_to_run(&test_to_run);
+ tftf_get_test_progress(&test_progress);
+ assert(TEST_PROGRESS_IS_VALID(test_progress));
+
+ switch (test_progress) {
+ case TEST_READY:
+ /*
+ * The TFTF has reset in the framework code, before the test
+ * actually started.
+ * Nothing to update, just start the test from scratch.
+ */
+ break;
+
+ case TEST_IN_PROGRESS:
+ /*
+ * The test crashed, i.e. it couldn't complete.
+ * Update the test result in NVM then move to the next test.
+ */
+ INFO("Test has crashed, moving to the next one\n");
+ tftf_testcase_set_result(current_testcase(),
+ TEST_RESULT_CRASHED,
+ 0);
+ next_test = advance_to_next_test();
+ if (!next_test) {
+ INFO("No more tests\n");
+ return -1;
+ }
+ break;
+
+ case TEST_COMPLETE:
+ /*
+ * The TFTF has reset in the framework code, after the test had
+ * completed but before we finished the framework maintenance
+ * required to move to the next test.
+ *
+ * In this case, we don't know the exact state of the data:
+ * maybe we had the time to update the test result,
+ * maybe we had the time to move to the next test.
+ * We can't be sure so let's stay on the safe side and just
+ * restart the test session from the beginning...
+ */
+ NOTICE("The test framework has been interrupted in the middle "
+ "of critical maintenance operations.\n");
+ NOTICE("Can't recover execution.\n");
+ return -1;
+
+ case TEST_REBOOTING:
+ /*
+ * Nothing to update about the test session, as we want to
+ * re-enter the same test. Just remember that the test is
+ * rebooting in case it queries this information.
+ */
+ test_is_rebooting = 1;
+ break;
+
+ default:
+ bug_unreachable();
+ }
+
+ return 0;
+}
+
+/*
+ * C entry point in the TFTF.
+ * This function is executed by the primary CPU only.
+ */
+void __dead2 tftf_cold_boot_main(void)
+{
+ STATUS status;
+ int rc;
+
+ NOTICE("%s\n", TFTF_WELCOME_STR);
+ NOTICE("%s\n", build_message);
+ NOTICE("%s\n\n", version_string);
+
+#ifndef AARCH32
+ NOTICE("Running at NS-EL%u\n", IS_IN_EL(1) ? 1 : 2);
+#else
+ NOTICE("Running in AArch32 HYP mode\n");
+#endif
+
+ tftf_arch_setup();
+ tftf_platform_setup();
+ tftf_init_topology();
+
+ tftf_irq_setup();
+
+ rc = tftf_initialise_timer();
+ if (rc != 0) {
+ ERROR("Failed to initialize the timer subsystem (%d).\n", rc);
+ tftf_exit();
+ }
+
+ /* Enable the SGI used by the timer management framework */
+ tftf_irq_enable(IRQ_WAKE_SGI, GIC_HIGHEST_NS_PRIORITY);
+ enable_irq();
+
+ if (new_test_session()) {
+ NOTICE("Starting a new test session\n");
+ status = tftf_init_nvm();
+ if (status != STATUS_SUCCESS) {
+ /*
+ * TFTF will have an undetermined behavior if its data
+ * structures have not been initialised. There's no
+ * point in continuing execution.
+ */
+ ERROR("FATAL: Failed to initialise internal data structures in NVM.\n");
+ tftf_clean_nvm();
+ tftf_exit();
+ }
+ } else {
+ NOTICE("Resuming interrupted test session\n");
+ rc = resume_test_session();
+ if (rc < 0) {
+ tftf_report_generate();
+ tftf_clean_nvm();
+ tftf_exit();
+ }
+ }
+
+ /* Initialise the CPUs status map */
+ tftf_init_cpus_status_map();
+
+ /*
+ * Detect power state format and get power state information for
+ * a platform.
+ */
+ tftf_init_pstate_framework();
+
+ /* The lead CPU is always the primary core. */
+ lead_cpu_mpid = read_mpidr_el1() & MPID_MASK;
+
+ /*
+ * Hand over to lead CPU if required.
+ * If the primary CPU is not the lead CPU for the first test then:
+ * 1) Power on the lead CPU
+ * 2) Power down the primary CPU
+ */
+ if ((read_mpidr_el1() & MPID_MASK) != lead_cpu_mpid) {
+ hand_over_to_lead_cpu();
+ bug_unreachable();
+ }
+
+ /* Enter the test session */
+ run_tests();
+
+ /* Should never reach this point */
+ bug_unreachable();
+}
+
+void __dead2 tftf_exit(void)
+{
+ NOTICE("Exiting tests.\n");
+
+ /* Let the platform code clean up if required */
+ tftf_platform_end();
+
+ while (1)
+ wfi();
+}
diff --git a/tftf/framework/nvm_results_helpers.c b/tftf/framework/nvm_results_helpers.c
new file mode 100644
index 0000000..34ef19f
--- /dev/null
+++ b/tftf/framework/nvm_results_helpers.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch_helpers.h>
+#include <assert.h>
+#include <debug.h>
+#include <nvm.h>
+#include <platform.h>
+#include <spinlock.h>
+#include <stdarg.h>
+#include <string.h>
+
+/*
+ * Temporary buffer to store 1 test output.
+ * This will eventually be saved into NVM at the end of the execution
+ * of this test.
+ */
+static char testcase_output[TESTCASE_OUTPUT_MAX_SIZE];
+/*
+ * A test output can be written in several pieces by calling
+ * tftf_testcase_printf() multiple times. testcase_output_idx keeps the position
+ * of the last character written in testcase_output buffer and allows to easily
+ * append a new string at next call to tftf_testcase_printf().
+ */
+static unsigned int testcase_output_idx;
+
+/* Lock to avoid concurrent accesses to the testcase output buffer */
+static spinlock_t testcase_output_lock;
+
+static tftf_state_t tftf_init_state = {
+ .build_message = "",
+ .test_to_run = {
+ .testsuite_idx = 0,
+ .testcase_idx = 0,
+ },
+ .test_progress = TEST_READY,
+ .testcase_buffer = { 0 },
+ .testcase_results = {
+ {
+ .result = TEST_RESULT_NA,
+ .duration = 0,
+ .output_offset = 0,
+ .output_size = 0,
+ }
+ },
+ .result_buffer_size = 0,
+ .result_buffer = NULL,
+};
+
+unsigned int new_test_session(void)
+{
+/* NEW_TEST_SESSION == 1 => we always want to start a new session */
+#if NEW_TEST_SESSION
+ INFO("Always starting a new test session (NEW_TEST_SESSION == 1)\n");
+ return 1;
+#endif
+ char saved_build_msg[BUILD_MESSAGE_SIZE];
+
+ /*
+ * Check the validity of the build message stored in NVM.
+ * It is invalid when it doesn't match with the TFTF binary currently
+ * executing.
+ */
+ tftf_nvm_read(TFTF_STATE_OFFSET(build_message), saved_build_msg,
+ BUILD_MESSAGE_SIZE);
+ return !!strncmp(build_message, saved_build_msg, BUILD_MESSAGE_SIZE);
+}
+
+STATUS tftf_init_nvm(void)
+{
+ INFO("Initialising NVM\n");
+
+ /* Copy the build message to identify the TFTF */
+ strncpy(tftf_init_state.build_message, build_message, BUILD_MESSAGE_SIZE);
+ return tftf_nvm_write(0, &tftf_init_state, sizeof(tftf_init_state));
+}
+
+STATUS tftf_clean_nvm(void)
+{
+ unsigned char corrupt_build_message = '\0';
+
+ /*
+ * This will cause TFTF to re-initialise its data structures next time
+ * it runs.
+ */
+ return tftf_nvm_write(TFTF_STATE_OFFSET(build_message),
+ &corrupt_build_message,
+ sizeof(corrupt_build_message));
+}
+
+STATUS tftf_set_test_to_run(const test_ref_t test_to_run)
+{
+ return tftf_nvm_write(TFTF_STATE_OFFSET(test_to_run), &test_to_run,
+ sizeof(test_to_run));
+}
+
+STATUS tftf_get_test_to_run(test_ref_t *test_to_run)
+{
+ assert(test_to_run != NULL);
+ return tftf_nvm_read(TFTF_STATE_OFFSET(test_to_run), test_to_run,
+ sizeof(*test_to_run));
+}
+
+STATUS tftf_set_test_progress(test_progress_t test_progress)
+{
+ return tftf_nvm_write(TFTF_STATE_OFFSET(test_progress), &test_progress,
+ sizeof(test_progress));
+}
+
+STATUS tftf_get_test_progress(test_progress_t *test_progress)
+{
+ assert(test_progress != NULL);
+ return tftf_nvm_read(TFTF_STATE_OFFSET(test_progress), test_progress,
+ sizeof(*test_progress));
+}
+
+STATUS tftf_testcase_set_result(const test_case_t *testcase,
+ test_result_t result,
+ unsigned long long duration)
+{
+ STATUS status;
+ unsigned result_buffer_size = 0;
+ TESTCASE_RESULT test_result;
+
+ assert(testcase != NULL);
+
+ /* Initialize Test case result */
+ test_result.result = result;
+ test_result.duration = duration;
+ test_result.output_offset = 0;
+ test_result.output_size = strlen(testcase_output);
+
+ /* Does the test have an output? */
+ if (test_result.output_size != 0) {
+ /* Get the size of the buffer containing all tests outputs */
+ status = tftf_nvm_read(TFTF_STATE_OFFSET(result_buffer_size),
+ &result_buffer_size, sizeof(unsigned));
+ if (status != STATUS_SUCCESS)
+ goto reset_test_output;
+
+ /*
+ * Write the output buffer at the end of the string buffer in
+ * NVM
+ */
+ test_result.output_offset = result_buffer_size;
+ status = tftf_nvm_write(
+ TFTF_STATE_OFFSET(result_buffer) + result_buffer_size,
+ testcase_output, test_result.output_size + 1);
+ if (status != STATUS_SUCCESS)
+ goto reset_test_output;
+
+ /* And update the buffer size into NVM */
+ result_buffer_size += test_result.output_size + 1;
+ status = tftf_nvm_write(TFTF_STATE_OFFSET(result_buffer_size),
+ &result_buffer_size, sizeof(unsigned));
+ if (status != STATUS_SUCCESS)
+ goto reset_test_output;
+ }
+
+ /* Write the test result into NVM */
+ status = tftf_nvm_write(TFTF_STATE_OFFSET(testcase_results) +
+ (testcase->index * sizeof(TESTCASE_RESULT)),
+ &test_result, sizeof(TESTCASE_RESULT));
+
+reset_test_output:
+ /* Reset test output buffer for the next test */
+ testcase_output_idx = 0;
+ testcase_output[0] = 0;
+
+ return status;
+}
+
+STATUS tftf_testcase_get_result(const test_case_t *testcase,
+ TESTCASE_RESULT *result,
+ char *test_output)
+{
+ STATUS status;
+ unsigned output_size;
+
+ assert(testcase != NULL);
+ assert(result != NULL);
+ assert(test_output != NULL);
+
+ status = tftf_nvm_read(TFTF_STATE_OFFSET(testcase_results)
+ + (testcase->index * sizeof(TESTCASE_RESULT)),
+ result, sizeof(TESTCASE_RESULT));
+ if (status != STATUS_SUCCESS) {
+ return status;
+ }
+
+ output_size = result->output_size;
+
+ if (output_size != 0) {
+ status = tftf_nvm_read(TFTF_STATE_OFFSET(result_buffer)
+ + result->output_offset,
+ test_output, output_size);
+ if (status != STATUS_SUCCESS)
+ return status;
+ }
+
+ test_output[output_size] = 0;
+
+ return STATUS_SUCCESS;
+}
+
+int tftf_testcase_printf(const char *format, ...)
+{
+ va_list ap;
+ int available;
+ int written = -1;
+
+ spin_lock(&testcase_output_lock);
+
+ assert(sizeof(testcase_output) >= testcase_output_idx);
+ available = sizeof(testcase_output) - testcase_output_idx;
+ if (available == 0) {
+ ERROR("%s: Output buffer is full ; the string won't be printed.\n",
+ __func__);
+ ERROR("%s: Consider increasing TESTCASE_OUTPUT_MAX_SIZE value.\n",
+ __func__);
+ goto release_lock;
+ }
+
+ va_start(ap, format);
+ written = vsnprintf(&testcase_output[testcase_output_idx], available,
+ format, ap);
+ va_end(ap);
+
+ if (written < 0) {
+ ERROR("%s: Output error (%d)", __func__, written);
+ goto release_lock;
+ }
+ /*
+ * If vsnprintf() truncated the string due to the size limit passed as
+ * an argument then its return value is the number of characters (not
+ * including the trailing '\0') which would have been written to the
+ * final string if enough space had been available. Thus, a return value
+ * of size or more means that the output was truncated.
+ *
+ * Adjust the value of 'written' to reflect what has been actually
+ * written.
+ */
+ if (written >= available) {
+ ERROR("%s: String has been truncated (%u/%u bytes written).\n",
+ __func__, available - 1, written);
+ ERROR("%s: Consider increasing TESTCASE_OUTPUT_MAX_SIZE value.\n",
+ __func__);
+ written = available - 1;
+ }
+
+ /*
+ * Update testcase_output_idx to point to the '\0' of the buffer.
+ * The next call of tftf_testcase_printf() will overwrite '\0' to
+ * append its new string to the buffer.
+ */
+ testcase_output_idx += written;
+
+release_lock:
+ spin_unlock(&testcase_output_lock);
+ return written;
+}
+
+void tftf_notify_reboot(void)
+{
+#if DEBUG
+ /* This function must be called by tests, not by the framework */
+ test_progress_t test_progress;
+ tftf_get_test_progress(&test_progress);
+ assert(test_progress == TEST_IN_PROGRESS);
+#endif /* DEBUG */
+
+ VERBOSE("Test intends to reset\n");
+ tftf_set_test_progress(TEST_REBOOTING);
+}
diff --git a/tftf/framework/report.c b/tftf/framework/report.c
new file mode 100644
index 0000000..4b0a857
--- /dev/null
+++ b/tftf/framework/report.c
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <debug.h>
+#include <platform_def.h> /* For TESTCASE_OUTPUT_MAX_SIZE */
+#include <semihosting.h>
+#include <stdio.h>
+#include <string.h>
+#include <tftf.h>
+
+struct tftf_report_ops {
+ long (*open)(const char *fname);
+ void (*write)(long handle, const char *str);
+ void (*close)(long handle);
+};
+
+#define TEST_REPORT_JUNIT_FILENAME "tftf_report_junit.xml"
+#define TEST_REPORT_RAW_FILENAME "tftf_report_raw.txt"
+
+#if defined(TEST_REPORT_UART_RAW) || defined(TEST_REPORT_UART_JUNIT)
+static long tftf_report_uart_open(const char *fname)
+{
+ printf("********** %s **********\n", fname);
+ return 0;
+}
+
+static void tftf_report_uart_write(long handle, const char *str)
+{
+ (void)handle;
+ assert(str);
+ /* Not using printf to avoid doing two copies. */
+ while (*str) {
+ putchar(*str++);
+ }
+}
+
+static void tftf_report_uart_close(long handle)
+{
+ (void)handle;
+ printf("************************\n");
+}
+
+const struct tftf_report_ops tftf_report_uart_ops = {
+ .open = tftf_report_uart_open,
+ .write = tftf_report_uart_write,
+ .close = tftf_report_uart_close,
+};
+#endif /* defined(TEST_REPORT_UART_RAW) || defined(TEST_REPORT_UART_JUNIT) */
+
+#if defined(TEST_REPORT_UART_RAW) || defined(TEST_REPORT_SEMIHOSTING_RAW)
+static unsigned int total_tests;
+static unsigned int tests_stats[TEST_RESULT_MAX];
+
+static void tftf_update_tests_statistics(test_result_t result)
+{
+ assert(TEST_RESULT_IS_VALID(result));
+ total_tests++;
+ tests_stats[result]++;
+}
+
+static const char *test_result_strings[TEST_RESULT_MAX] = {
+ "Skipped", "Passed", "Failed", "Crashed",
+};
+
+const char *test_result_to_string(test_result_t result)
+{
+ assert(TEST_RESULT_IS_VALID(result));
+ return test_result_strings[result];
+}
+
+static void tftf_report_generate_raw(const struct tftf_report_ops *rops,
+ const char *fname)
+{
+#define WRITE(str) rops->write(file_handle, str)
+#define BUFFER_SIZE 200
+ unsigned i, j;
+ long file_handle;
+ char buffer[BUFFER_SIZE];
+ const test_case_t *testcases;
+ TESTCASE_RESULT testcase_result;
+ char test_output[TESTCASE_OUTPUT_MAX_SIZE];
+ STATUS status;
+
+ file_handle = rops->open(fname);
+ if (file_handle == -1)
+ return;
+
+ /* Extract the result of all the testcases */
+ WRITE("========== TEST REPORT ==========\n");
+ for (i = 0; testsuites[i].name != NULL; i++) {
+ snprintf(buffer, BUFFER_SIZE, "# Test suite '%s':\n", testsuites[i].name);
+ WRITE(buffer);
+ testcases = testsuites[i].testcases;
+
+ for (j = 0; testcases[j].name != NULL; j++) {
+ status = tftf_testcase_get_result(&testcases[j], &testcase_result, test_output);
+ if (status != STATUS_SUCCESS) {
+ WRITE("Failed to get test result.\n");
+ continue;
+ }
+
+ tftf_update_tests_statistics(testcase_result.result);
+ /* TODO: print test duration */
+ snprintf(buffer, BUFFER_SIZE, "\t - %s: %s\n", testcases[j].name,
+ test_result_to_string(testcase_result.result));
+ WRITE(buffer);
+
+ if (strlen(test_output) != 0) {
+ WRITE("--- output ---\n");
+ snprintf(buffer, BUFFER_SIZE, "%s", test_output);
+ WRITE(buffer);
+ WRITE("--------------\n");
+ }
+ }
+ }
+ WRITE("=================================\n");
+
+ for (i = TEST_RESULT_MIN; i < TEST_RESULT_MAX; i++) {
+ snprintf(buffer, BUFFER_SIZE, "Tests %-8s: %d\n",
+ test_result_to_string(i), tests_stats[i]);
+ WRITE(buffer);
+ }
+ snprintf(buffer, BUFFER_SIZE, "%-14s: %d\n", "Total tests", total_tests);
+ WRITE(buffer);
+ WRITE("=================================\n");
+
+ rops->close(file_handle);
+#undef BUFFER_SIZE
+#undef WRITE
+}
+#endif /* defined(TEST_REPORT_UART_RAW) || defined(TEST_REPORT_SEMIHOSTING_RAW) */
+
+#if defined(TEST_REPORT_SEMIHOSTING_RAW) || defined(TEST_REPORT_SEMIHOSTING_JUNIT)
+static long tftf_report_semihosting_open(const char *fname)
+{
+ /* Create the report on the semihosting */
+ long handle = semihosting_file_open(fname, FOPEN_MODE_WPLUS);
+ if (handle == -1) {
+ ERROR("Failed to create test report file \"%s\" on semihosting"
+ " [status = %ld].\n", fname, handle);
+ }
+ NOTICE("Opened file \"%s\" on semihosting with handle %ld.\n", fname, handle);
+ return handle;
+}
+
+static void tftf_report_semihosting_write(long handle, const char *str)
+{
+ size_t length = strlen(str);
+ semihosting_file_write(handle, &length, (const uintptr_t) str);
+}
+
+static void tftf_report_semihosting_close(long handle)
+{
+ semihosting_file_close(handle);
+ NOTICE("Closing file with handle %ld on semihosting.\n", handle);
+}
+
+const struct tftf_report_ops tftf_report_semihosting_ops = {
+ .open = tftf_report_semihosting_open,
+ .write = tftf_report_semihosting_write,
+ .close = tftf_report_semihosting_close,
+};
+#endif /* defined(TEST_REPORT_SEMIHOSTING_RAW) || defined(TEST_REPORT_SEMIHOSTING_JUNIT) */
+
+
+#if defined(TEST_REPORT_UART_JUNIT) || defined(TEST_REPORT_SEMIHOSTING_JUNIT)
+static void tftf_report_generate_junit(const struct tftf_report_ops *rops,
+ const char *fname)
+{
+#define WRITE(str) rops->write(file_handle, str)
+#define BUFFER_SIZE 200
+
+ long file_handle;
+ unsigned i, j;
+ const test_case_t *testcases;
+ TESTCASE_RESULT result;
+ char buffer[BUFFER_SIZE];
+ char test_output[TESTCASE_OUTPUT_MAX_SIZE];
+
+ file_handle = rops->open(fname);
+
+ if (file_handle == -1) {
+ return;
+ }
+ WRITE("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
+ WRITE("<testsuites>\n");
+
+ /* Extract the result of all the testcases */
+ for (i = 0; testsuites[i].name != NULL; i++) {
+ snprintf(buffer, BUFFER_SIZE, "<testsuite name=\"%s\">\n",
+ testsuites[i].name);
+ WRITE(buffer);
+ testcases = testsuites[i].testcases;
+ for (j = 0; testcases[j].name != NULL; j++) {
+ tftf_testcase_get_result(&testcases[j], &result, test_output);
+
+ snprintf(buffer, BUFFER_SIZE, " <testcase name=\"%s\" time=\"%llu\"",
+ testcases[j].name, result.duration);
+ WRITE(buffer);
+ if (result.result == TEST_RESULT_SUCCESS) {
+ WRITE("/>\n");
+ } else {
+ WRITE(">\n");
+ if (result.result == TEST_RESULT_SKIPPED) {
+ WRITE(" <skipped/>\n");
+ } else {
+ WRITE(" <error type=\"failed\">\n");
+ WRITE(test_output);
+ WRITE(" </error>\n");
+ }
+ WRITE(" </testcase>\n");
+ }
+ }
+ WRITE("</testsuite>\n");
+ }
+
+ WRITE("</testsuites>\n");
+ rops->close(file_handle);
+#undef BUFFER_SIZE
+#undef WRITE
+}
+#endif /* defined(TEST_REPORT_UART_JUNIT) || defined(TEST_REPORT_SEMIHOSTING_JUNIT) */
+
+void tftf_report_generate(void)
+{
+ int nb_reports = 0;
+#ifdef TEST_REPORT_UART_RAW
+ tftf_report_generate_raw(&tftf_report_uart_ops, "raw");
+ nb_reports++;
+#endif
+#ifdef TEST_REPORT_UART_JUNIT
+ tftf_report_generate_junit(&tftf_report_uart_ops, "junit");
+ nb_reports++;
+#endif
+#ifdef TEST_REPORT_SEMIHOSTING_RAW
+ tftf_report_generate_raw(&tftf_report_semihosting_ops,
+ TEST_REPORT_RAW_FILENAME);
+ nb_reports++;
+#endif
+#ifdef TEST_REPORT_SEMIHOSTING_JUNIT
+ tftf_report_generate_junit(&tftf_report_semihosting_ops,
+ TEST_REPORT_JUNIT_FILENAME);
+ nb_reports++;
+#endif
+ assert(nb_reports > 0);
+}
diff --git a/tftf/framework/tftf.ld.S b/tftf/framework/tftf.ld.S
new file mode 100644
index 0000000..9432a74
--- /dev/null
+++ b/tftf/framework/tftf.ld.S
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <platform_def.h>
+#include <xlat_tables_defs.h>
+
+OUTPUT_FORMAT(PLATFORM_LINKER_FORMAT)
+OUTPUT_ARCH(PLATFORM_LINKER_ARCH)
+ENTRY(tftf_entrypoint)
+
+MEMORY {
+ RAM (rwx): ORIGIN = DRAM_BASE, LENGTH = DRAM_SIZE
+}
+
+
+SECTIONS
+{
+ . = TFTF_BASE;
+ __TFTF_BASE__ = .;
+
+ ro . : {
+ __RO_START__ = .;
+ *entrypoint.o(.text*)
+ *(.text*)
+ *(.rodata*)
+ *(.vectors)
+ __RO_END_UNALIGNED__ = .;
+ /*
+ * Memory page(s) mapped to this section will be marked as
+ * read-only, executable. No RW data from the next section must
+ * creep in. Ensure the rest of the current memory page is unused.
+ */
+ . = NEXT(PAGE_SIZE);
+ __RO_END__ = .;
+ } >RAM
+
+ .data : {
+ __DATA_START__ = .;
+ *(.data*)
+ __DATA_END__ = .;
+ } >RAM
+
+ stacks (NOLOAD) : {
+ __STACKS_START__ = .;
+ *(tftf_normal_stacks)
+ __STACKS_END__ = .;
+ } >RAM
+
+ /*
+ * The .bss section gets initialised to 0 at runtime.
+ * Its base address must be 16-byte aligned.
+ */
+ .bss : ALIGN(16) {
+ __BSS_START__ = .;
+ *(SORT_BY_ALIGNMENT(.bss*))
+ *(COMMON)
+ __BSS_END__ = .;
+ } >RAM
+
+ /*
+ * The xlat_table section is for full, aligned page tables (4K).
+ * Removing them from .bss avoids forcing 4K alignment on
+ * the .bss section and eliminates the unecessary zero init
+ */
+ xlat_table (NOLOAD) : {
+ *(xlat_table)
+ } >RAM
+
+ /*
+ * The base address of the coherent memory section must be page-aligned (4K)
+ * to guarantee that the coherent data are stored on their own pages and
+ * are not mixed with normal data. This is required to set up the correct
+ * memory attributes for the coherent data page tables.
+ */
+ coherent_ram (NOLOAD) : ALIGN(PAGE_SIZE) {
+ __COHERENT_RAM_START__ = .;
+ *(tftf_coherent_stacks)
+ *(tftf_coherent_mem)
+ __COHERENT_RAM_END_UNALIGNED__ = .;
+ /*
+ * Memory page(s) mapped to this section will be marked
+ * as device memory. No other unexpected data must creep in.
+ * Ensure the rest of the current memory page is unused.
+ */
+ . = NEXT(PAGE_SIZE);
+ __COHERENT_RAM_END__ = .;
+ } >RAM
+
+ __COHERENT_RAM_UNALIGNED_SIZE__ =
+ __COHERENT_RAM_END_UNALIGNED__ - __COHERENT_RAM_START__;
+
+
+ __TFTF_END__ = .;
+
+ __BSS_SIZE__ = SIZEOF(.bss);
+
+}
diff --git a/tftf/framework/timer/timer_framework.c b/tftf/framework/timer/timer_framework.c
new file mode 100644
index 0000000..e5e9a0f
--- /dev/null
+++ b/tftf/framework/timer/timer_framework.c
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) 2018, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <arch.h>
+#include <arch_helpers.h>
+#include <arm_gic.h>
+#include <debug.h>
+#include <errno.h>
+#include <irq.h>
+#include <mmio.h>
+#include <platform.h>
+#include <platform_def.h>
+#include <power_management.h>
+#include <sgi.h>
+#include <spinlock.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <tftf.h>
+#include <timer.h>
+
+
+/* Helper macros */
+#define TIMER_STEP_VALUE (plat_timer_info->timer_step_value)
+#define TIMER_IRQ (plat_timer_info->timer_irq)
+#define PROGRAM_TIMER(a) plat_timer_info->program(a)
+#define INVALID_CORE UINT32_MAX
+#define INVALID_TIME UINT64_MAX
+#define MAX_TIME_OUT_MS 10000
+
+/*
+ * Pointer containing available timer information for the platform.
+ */
+static const plat_timer_t *plat_timer_info;
+/*
+ * Interrupt requested time by cores in terms of absolute time.
+ */
+static volatile unsigned long long interrupt_req_time[PLATFORM_CORE_COUNT];
+/*
+ * Contains the target core number of the timer interrupt.
+ */
+static unsigned int current_prog_core = INVALID_CORE;
+/*
+ * Lock to get a consistent view for programming the timer
+ */
+static spinlock_t timer_lock;
+/*
+ * Number of system ticks per millisec
+ */
+static unsigned int systicks_per_ms;
+
+/*
+ * Stores per CPU timer handler invoked on expiration of the requested timeout.
+ */
+static irq_handler_t timer_handler[PLATFORM_CORE_COUNT];
+
+/* Helper function */
+static inline unsigned long long get_current_time_ms(void)
+{
+ assert(systicks_per_ms);
+ return mmio_read_64(SYS_CNT_BASE1 + CNTPCT_LO) / systicks_per_ms;
+}
+
+static inline unsigned long long get_current_prog_time(void)
+{
+ return current_prog_core == INVALID_CORE ?
+ 0 : interrupt_req_time[current_prog_core];
+}
+
+int tftf_initialise_timer(void)
+{
+ int rc;
+ unsigned int i;
+
+ /*
+ * Get platform specific timer information
+ */
+ rc = plat_initialise_timer_ops(&plat_timer_info);
+ if (rc) {
+ ERROR("%s %d: No timer data found\n", __func__, __LINE__);
+ return rc;
+ }
+
+ /* Systems can't support single tick as a step value */
+ assert(TIMER_STEP_VALUE);
+
+ /* Initialise the array to max possible time */
+ for (i = 0; i < PLATFORM_CORE_COUNT; i++)
+ interrupt_req_time[i] = INVALID_TIME;
+
+ tftf_irq_register_handler(TIMER_IRQ, tftf_timer_framework_handler);
+ arm_gic_set_intr_priority(TIMER_IRQ, GIC_HIGHEST_NS_PRIORITY);
+ arm_gic_intr_enable(TIMER_IRQ);
+
+ /* Save the systicks per millisecond */
+ systicks_per_ms = read_cntfrq_el0() / 1000;
+
+ return 0;
+}
+
+/*
+ * It returns the core number of next timer request to be serviced or
+ * -1 if there is no request from any core. The next service request
+ * is the core whose interrupt needs to be fired first.
+ */
+static inline unsigned int get_lowest_req_core(void)
+{
+ unsigned long long lowest_timer = INVALID_TIME;
+ unsigned int lowest_core_req = INVALID_CORE;
+ unsigned int i;
+
+ /*
+ * If 2 cores requested same value, give precedence
+ * to the core with lowest core number
+ */
+ for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
+ if (interrupt_req_time[i] < lowest_timer) {
+ lowest_timer = interrupt_req_time[i];
+ lowest_core_req = i;
+ }
+ }
+
+ return lowest_core_req;
+}
+
+int tftf_program_timer(unsigned long time_out_ms)
+{
+ unsigned int core_pos;
+ unsigned long long current_time;
+ u_register_t flags;
+ int rc = 0;
+
+ /*
+ * Some timer implementations have a very small max timeouts due to
+ * this if a request is asked for greater than the max time supported
+ * by them either it has to be broken down and remembered or use
+ * some other technique. Since that use case is not intended and
+ * and to make the timer framework simple, max timeout requests
+ * accepted by timer implementations can't be greater than
+ * 10 seconds. Hence, all timer peripherals used in timer framework
+ * has to support a timeout with interval of at least MAX_TIMEOUT.
+ */
+ if ((time_out_ms > MAX_TIME_OUT_MS) || (time_out_ms == 0)) {
+ ERROR("%s : Greater than max timeout request\n", __func__);
+ return -1;
+ } else if (time_out_ms < TIMER_STEP_VALUE) {
+ time_out_ms = TIMER_STEP_VALUE;
+ }
+
+ core_pos = platform_get_core_pos(read_mpidr_el1());
+ /* A timer interrupt request is already available for the core */
+ assert(interrupt_req_time[core_pos] == INVALID_TIME);
+
+ flags = read_daif();
+ disable_irq();
+ spin_lock(&timer_lock);
+
+ assert((current_prog_core < PLATFORM_CORE_COUNT) ||
+ (current_prog_core == INVALID_CORE));
+
+ /*
+ * Read time after acquiring timer_lock to account for any time taken
+ * by lock contention.
+ */
+ current_time = get_current_time_ms();
+
+ /* Update the requested time */
+ interrupt_req_time[core_pos] = current_time + time_out_ms;
+
+ VERBOSE("Need timer interrupt at: %lld current_prog_time:%lld\n"
+ " current time: %lld\n", interrupt_req_time[core_pos],
+ get_current_prog_time(),
+ get_current_time_ms());
+
+ /*
+ * If the interrupt request time is less than the current programmed
+ * by timer_step_value or timer is not programmed. Program it with
+ * requested time and retarget the timer interrupt to the current
+ * core.
+ */
+ if ((!get_current_prog_time()) || (interrupt_req_time[core_pos] <
+ (get_current_prog_time() - TIMER_STEP_VALUE))) {
+
+ arm_gic_set_intr_target(TIMER_IRQ, core_pos);
+
+ rc = PROGRAM_TIMER(time_out_ms);
+ /* We don't expect timer programming to fail */
+ if (rc)
+ ERROR("%s %d: rc = %d\n", __func__, __LINE__, rc);
+
+ current_prog_core = core_pos;
+ }
+
+ spin_unlock(&timer_lock);
+ /* Restore DAIF flags */
+ write_daif(flags);
+ isb();
+
+ return rc;
+}
+
+int tftf_program_timer_and_suspend(unsigned long milli_secs,
+ unsigned int pwr_state,
+ int *timer_rc, int *suspend_rc)
+{
+ int rc = 0;
+ u_register_t flags;
+
+ /* Default to successful return codes */
+ int timer_rc_val = 0;
+ int suspend_rc_val = PSCI_E_SUCCESS;
+
+ /* Preserve DAIF flags. IRQs need to be disabled for this to work. */
+ flags = read_daif();
+ disable_irq();
+
+ /*
+ * Even with IRQs masked, the timer IRQ will wake the CPU up.
+ *
+ * If the timer IRQ happens before entering suspend mode (because the
+ * timer took too long to program, for example) the fact that the IRQ is
+ * pending will prevent the CPU from entering suspend mode and not being
+ * able to wake up.
+ */
+ timer_rc_val = tftf_program_timer(milli_secs);
+ if (timer_rc_val == 0) {
+ suspend_rc_val = tftf_cpu_suspend(pwr_state);
+ if (suspend_rc_val != PSCI_E_SUCCESS) {
+ rc = -1;
+ INFO("%s %d: suspend_rc = %d\n", __func__, __LINE__,
+ suspend_rc_val);
+ }
+ } else {
+ rc = -1;
+ INFO("%s %d: timer_rc = %d\n", __func__, __LINE__, timer_rc_val);
+ }
+
+ /* Restore previous DAIF flags */
+ write_daif(flags);
+ isb();
+
+ if (timer_rc)
+ *timer_rc = timer_rc_val;
+ if (suspend_rc)
+ *suspend_rc = suspend_rc_val;
+ /*
+ * If IRQs were disabled when calling this function, the timer IRQ
+ * handler won't be called and the timer interrupt will be pending, but
+ * that isn't necessarily a problem.
+ */
+
+ return rc;
+}
+
+int tftf_program_timer_and_sys_suspend(unsigned long milli_secs,
+ int *timer_rc, int *suspend_rc)
+{
+ int rc = 0;
+ u_register_t flags;
+
+ /* Default to successful return codes */
+ int timer_rc_val = 0;
+ int suspend_rc_val = PSCI_E_SUCCESS;
+
+ /* Preserve DAIF flags. IRQs need to be disabled for this to work. */
+ flags = read_daif();
+ disable_irq();
+
+ /*
+ * Even with IRQs masked, the timer IRQ will wake the CPU up.
+ *
+ * If the timer IRQ happens before entering suspend mode (because the
+ * timer took too long to program, for example) the fact that the IRQ is
+ * pending will prevent the CPU from entering suspend mode and not being
+ * able to wake up.
+ */
+ timer_rc_val = tftf_program_timer(milli_secs);
+ if (timer_rc_val == 0) {
+ suspend_rc_val = tftf_system_suspend();
+ if (suspend_rc_val != PSCI_E_SUCCESS) {
+ rc = -1;
+ INFO("%s %d: suspend_rc = %d\n", __func__, __LINE__,
+ suspend_rc_val);
+ }
+ } else {
+ rc = -1;
+ INFO("%s %d: timer_rc = %d\n", __func__, __LINE__, timer_rc_val);
+ }
+
+ /* Restore previous DAIF flags */
+ write_daif(flags);
+ isb();
+
+ /*
+ * If IRQs were disabled when calling this function, the timer IRQ
+ * handler won't be called and the timer interrupt will be pending, but
+ * that isn't necessarily a problem.
+ */
+ if (timer_rc)
+ *timer_rc = timer_rc_val;
+ if (suspend_rc)
+ *suspend_rc = suspend_rc_val;
+
+ return rc;
+}
+
+int tftf_timer_sleep(unsigned long milli_secs)
+{
+ int ret, power_state;
+ uint32_t stateid;
+
+ ret = tftf_psci_make_composite_state_id(MPIDR_AFFLVL0,
+ PSTATE_TYPE_STANDBY, &stateid);
+ if (ret != PSCI_E_SUCCESS)
+ return -1;
+
+ power_state = tftf_make_psci_pstate(MPIDR_AFFLVL0, PSTATE_TYPE_STANDBY,
+ stateid);
+ ret = tftf_program_timer_and_suspend(milli_secs, power_state,
+ NULL, NULL);
+ if (ret != 0)
+ return -1;
+
+ return 0;
+}
+
+int tftf_cancel_timer(void)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+ unsigned int next_timer_req_core_pos;
+ unsigned long long current_time;
+ u_register_t flags;
+ int rc = 0;
+
+ /*
+ * IRQ is disabled so that if a timer is fired after taking a lock,
+ * it will remain pending and a core does not hit IRQ handler trying
+ * to acquire an already locked spin_lock causing dead lock.
+ */
+ flags = read_daif();
+ disable_irq();
+ spin_lock(&timer_lock);
+
+ interrupt_req_time[core_pos] = INVALID_TIME;
+
+ if (core_pos == current_prog_core) {
+ /*
+ * Cancel the programmed interrupt at the peripheral. If the
+ * timer interrupt is level triggered and fired this also
+ * deactivates the pending interrupt.
+ */
+ rc = plat_timer_info->cancel();
+ /* We don't expect cancel timer to fail */
+ if (rc) {
+ ERROR("%s %d: rc = %d\n", __func__, __LINE__, rc);
+ goto exit;
+ }
+
+ /*
+ * For edge triggered interrupts, if an IRQ is fired before
+ * cancel timer is executed, the signal remains pending. So,
+ * clear the Timer IRQ if it is already pending.
+ */
+ if (arm_gic_is_intr_pending(TIMER_IRQ))
+ arm_gic_intr_clear(TIMER_IRQ);
+
+ /* Get next timer consumer */
+ next_timer_req_core_pos = get_lowest_req_core();
+ if (next_timer_req_core_pos != INVALID_CORE) {
+
+ /* Retarget to the next_timer_req_core_pos */
+ arm_gic_set_intr_target(TIMER_IRQ, next_timer_req_core_pos);
+ current_prog_core = next_timer_req_core_pos;
+
+ current_time = get_current_time_ms();
+
+ /*
+ * If the next timer request is lesser than or in a
+ * window of TIMER_STEP_VALUE from current time,
+ * program it to fire after TIMER_STEP_VALUE.
+ */
+ if (interrupt_req_time[next_timer_req_core_pos] >
+ current_time + TIMER_STEP_VALUE)
+ rc = PROGRAM_TIMER(interrupt_req_time[next_timer_req_core_pos] - current_time);
+ else
+ rc = PROGRAM_TIMER(TIMER_STEP_VALUE);
+ VERBOSE("Cancel and program new timer for core_pos: "
+ "%d %lld\n",
+ next_timer_req_core_pos,
+ get_current_prog_time());
+ /* We don't expect timer programming to fail */
+ if (rc)
+ ERROR("%s %d: rc = %d\n", __func__, __LINE__, rc);
+ } else {
+ current_prog_core = INVALID_CORE;
+ VERBOSE("Cancelling timer : %d\n", core_pos);
+ }
+ }
+exit:
+ spin_unlock(&timer_lock);
+
+ /* Restore DAIF flags */
+ write_daif(flags);
+ isb();
+
+ return rc;
+}
+
+int tftf_timer_framework_handler(void *data)
+{
+ unsigned int handler_core_pos = platform_get_core_pos(read_mpidr_el1());
+ unsigned int next_timer_req_core_pos;
+ unsigned long long current_time;
+ int rc = 0;
+
+ assert(interrupt_req_time[handler_core_pos] != INVALID_TIME);
+ spin_lock(&timer_lock);
+
+ current_time = get_current_time_ms();
+ /* Check if we interrupt is targeted correctly */
+ assert(handler_core_pos == current_prog_core);
+
+ interrupt_req_time[handler_core_pos] = INVALID_TIME;
+
+ /* Execute the driver handler */
+ if (plat_timer_info->handler)
+ plat_timer_info->handler();
+
+ if (arm_gic_is_intr_pending(TIMER_IRQ)) {
+ /*
+ * We might never manage to acquire the printf lock here
+ * (because we are in ISR context) but we're gonna panic right
+ * after anyway so it doesn't really matter.
+ */
+ ERROR("Timer IRQ still pending. Fatal error.\n");
+ panic();
+ }
+
+ /*
+ * Execute the handler requested by the core, the handlers for the
+ * other cores will be executed as part of handling IRQ_WAKE_SGI.
+ */
+ if (timer_handler[handler_core_pos])
+ timer_handler[handler_core_pos](data);
+
+ /* Send interrupts to all the CPUS in the min time block */
+ for (int i = 0; i < PLATFORM_CORE_COUNT; i++) {
+ if ((interrupt_req_time[i] <=
+ (current_time + TIMER_STEP_VALUE))) {
+ interrupt_req_time[i] = INVALID_TIME;
+ tftf_send_sgi(IRQ_WAKE_SGI, i);
+ }
+ }
+
+ /* Get the next lowest requested timer core and program it */
+ next_timer_req_core_pos = get_lowest_req_core();
+ if (next_timer_req_core_pos != INVALID_CORE) {
+ /* Check we have not exceeded the time for next core */
+ assert(interrupt_req_time[next_timer_req_core_pos] >
+ current_time);
+ arm_gic_set_intr_target(TIMER_IRQ, next_timer_req_core_pos);
+ rc = PROGRAM_TIMER(interrupt_req_time[next_timer_req_core_pos]
+ - current_time);
+ }
+ /* Update current program core to the newer one */
+ current_prog_core = next_timer_req_core_pos;
+
+ spin_unlock(&timer_lock);
+
+ return rc;
+}
+
+int tftf_timer_register_handler(irq_handler_t irq_handler)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+ int ret;
+
+ /* Validate no handler is registered */
+ assert(!timer_handler[core_pos]);
+ timer_handler[core_pos] = irq_handler;
+
+ /*
+ * Also register same handler to IRQ_WAKE_SGI, as it can be waken
+ * by it.
+ */
+ ret = tftf_irq_register_handler(IRQ_WAKE_SGI, irq_handler);
+ assert(!ret);
+
+ return ret;
+}
+
+int tftf_timer_unregister_handler(void)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+ int ret;
+
+ /*
+ * Unregister the handler for IRQ_WAKE_SGI also
+ */
+ ret = tftf_irq_unregister_handler(IRQ_WAKE_SGI);
+ assert(!ret);
+ /* Validate a handler is registered */
+ assert(timer_handler[core_pos]);
+ timer_handler[core_pos] = 0;
+
+ return ret;
+}
+
+unsigned int tftf_get_timer_irq(void)
+{
+ /*
+ * Check if the timer info is initialised
+ */
+ assert(TIMER_IRQ);
+ return TIMER_IRQ;
+}
+
+unsigned int tftf_get_timer_step_value(void)
+{
+ assert(TIMER_STEP_VALUE);
+
+ return TIMER_STEP_VALUE;
+}
+
+/*
+ * There are 4 cases that could happen when a system is resuming from system
+ * suspend. The cases are:
+ * 1. The resumed core is the last core to power down and the
+ * timer interrupt was targeted to it. In this case, target the
+ * interrupt to our core and set the appropriate priority and enable it.
+ *
+ * 2. The resumed core was the last core to power down but the timer interrupt
+ * is targeted to another core because of timer request grouping within
+ * TIMER_STEP_VALUE. In this case, re-target the interrupt to our core
+ * and set the appropriate priority and enable it
+ *
+ * 3. The system suspend request was down-graded by firmware and the timer
+ * interrupt is targeted to another core which woke up first. In this case,
+ * that core will wake us up and the interrupt_req_time[] corresponding to
+ * our core will be cleared. In this case, no need to do anything as GIC
+ * state is preserved.
+ *
+ * 4. The system suspend is woken up by another external interrupt other
+ * than the timer framework interrupt. In this case, just enable the
+ * timer interrupt and set the correct priority at GICD.
+ */
+void tftf_timer_gic_state_restore(void)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+ spin_lock(&timer_lock);
+
+ arm_gic_set_intr_priority(TIMER_IRQ, GIC_HIGHEST_NS_PRIORITY);
+ arm_gic_intr_enable(TIMER_IRQ);
+
+ /* Check if the programmed core is the woken up core */
+ if (interrupt_req_time[core_pos] == INVALID_TIME) {
+ INFO("The programmed core is not the one woken up\n");
+ } else {
+ current_prog_core = core_pos;
+ arm_gic_set_intr_target(TIMER_IRQ, core_pos);
+ }
+
+ spin_unlock(&timer_lock);
+}
+