SCMI Comms: Add secure test suite

Adds support for local testing of the SCMI comms partition using the
TFM_TIMER0 interrupt to exercise the partition's interrupt handler.

Signed-off-by: Jamie Fox <jamie.fox@arm.com>
Change-Id: I21c7f9c93f4f057984a290f415888326df7dce71
diff --git a/docs/partitions/scmi_comms.rst b/docs/partitions/scmi_comms.rst
index 15fa036..cf07a8c 100644
--- a/docs/partitions/scmi_comms.rst
+++ b/docs/partitions/scmi_comms.rst
@@ -67,6 +67,26 @@
 :doc:`TF-M Secure IRQ integration guide<TF-M:integration_guide/tfm_secure_irq_integration_guide>`
 for more details).
 
+*******
+Testing
+*******
+
+A regression test suite for the Secure processing environment is provided in
+``test/secure/scmi_s_testsuite.c``. To test the partition locally, the tests
+rely on modifying the partition to use the ``TFM_TIMER0_IRQ`` IRQ source to
+trigger its interrupt handler. The tests then use the ``tfm_plat_test.h`` APIs
+to trigger the timer interrupt and cause the partition to handle an SCMI
+message. They also reimplement the HAL so that the shared memory and doorbell
+state are in local memory.
+
+To run the tests, all of the following build options need to be supplied:
+
+- ``TFM_EXTRA_MANIFEST_LIST_FILES``: Change to use
+  ``<tf-m-extras-repo>/partitions/scmi/test/secure/scmi_comms_manifest_list.yaml``
+  instead of the standard manifest.
+- ``EXTRA_S_TEST_SUITE_PATH``: ``<tfm_extras_dir>/partitions/scmi/test/secure``
+- ``TEST_S_SCMI_COMMS``: Set to ``ON`` to enable the tests and test HAL.
+
 **********
 References
 **********
diff --git a/partitions/scmi/CMakeLists.txt b/partitions/scmi/CMakeLists.txt
index cf19d96..131b8c6 100644
--- a/partitions/scmi/CMakeLists.txt
+++ b/partitions/scmi/CMakeLists.txt
@@ -44,7 +44,8 @@
 target_link_libraries(tfm_psa_rot_partition_scmi_comms
     PRIVATE
         tfm_sprt
-        scmi_hal
+        $<$<BOOL:${TEST_S_SCMI_COMMS}>:scmi_test_hal>
+        $<$<NOT:$<BOOL:${TEST_S_SCMI_COMMS}>>:scmi_hal>
         platform_s
 )
 
diff --git a/partitions/scmi/test/secure/CMakeLists.txt b/partitions/scmi/test/secure/CMakeLists.txt
new file mode 100644
index 0000000..032c20e
--- /dev/null
+++ b/partitions/scmi/test/secure/CMakeLists.txt
@@ -0,0 +1,40 @@
+#-------------------------------------------------------------------------------
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+#-------------------------------------------------------------------------------
+
+cmake_policy(SET CMP0079 NEW)
+
+if(NOT TEST_S_SCMI_COMMS)
+    return()
+endif()
+
+add_library(scmi_test_hal INTERFACE)
+
+target_include_directories(scmi_test_hal
+    INTERFACE
+        hal
+)
+
+target_sources(scmi_test_hal
+    INTERFACE
+        hal/scmi_test_hal.c
+        ${PLATFORM_DIR}/ext/common/scmi_hal_common.c
+)
+
+target_sources(tfm_test_suite_extra_s
+    PRIVATE
+        scmi_s_testsuite.c
+)
+
+target_include_directories(tfm_test_suite_extra_s
+    PRIVATE
+        ../..
+)
+
+target_link_libraries(tfm_test_suite_extra_s
+    PRIVATE
+        scmi_test_hal
+        tfm_psa_rot_partition_scmi_comms # Required for psa_manifest/scmi_comms.h
+)
diff --git a/partitions/scmi/test/secure/hal/scmi_hal_defs.h b/partitions/scmi/test/secure/hal/scmi_hal_defs.h
new file mode 100644
index 0000000..2b24758
--- /dev/null
+++ b/partitions/scmi/test/secure/hal/scmi_hal_defs.h
@@ -0,0 +1,28 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ */
+
+#ifndef __SCMI_HAL_DEFS_H__
+#define __SCMI_HAL_DEFS_H__
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern char test_shared_mem[];
+extern volatile bool test_doorbell_sender;
+extern volatile bool test_doorbell_receiver;
+
+/* Base address and size of shared memory with SCP for SCMI transport */
+#define SCP_SHARED_MEMORY_BASE (&test_shared_mem)
+#define SCP_SHARED_MEMORY_SIZE 128U
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SCMI_HAL_DEFS_H__ */
diff --git a/partitions/scmi/test/secure/hal/scmi_test_hal.c b/partitions/scmi/test/secure/hal/scmi_test_hal.c
new file mode 100644
index 0000000..0cbe2e5
--- /dev/null
+++ b/partitions/scmi/test/secure/hal/scmi_test_hal.c
@@ -0,0 +1,56 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ */
+
+#include "psa/service.h"
+#include "psa_manifest/scmi_comms.h"
+#include "scmi_hal.h"
+#include "scmi_protocol.h"
+#include "tfm_hal_platform.h"
+#include "tfm_plat_test.h"
+#include "cmsis_compiler.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+char test_shared_mem[SCP_SHARED_MEMORY_SIZE] __ALIGNED(8);
+volatile bool test_doorbell_sender;
+volatile bool test_doorbell_receiver;
+
+scmi_comms_err_t scmi_hal_shared_memory_init(void)
+{
+    return SCMI_COMMS_SUCCESS;
+}
+
+scmi_comms_err_t scmi_hal_doorbell_init(void)
+{
+    return SCMI_COMMS_SUCCESS;
+}
+
+scmi_comms_err_t scmi_hal_doorbell_ring(void)
+{
+    /* Set the sender doorbell */
+    test_doorbell_sender = true;
+
+    /* Add an extra wait on the doorbell to allow the test partition to run
+     * (only required because testing is done locally).
+     */
+    psa_wait(SCP_DOORBELL_SIGNAL, PSA_BLOCK);
+    scmi_hal_doorbell_clear();
+    psa_eoi(SCP_DOORBELL_SIGNAL);
+
+    return SCMI_COMMS_SUCCESS;
+}
+
+scmi_comms_err_t scmi_hal_doorbell_clear(void)
+{
+    /* Clear the receiver doorbell */
+    test_doorbell_receiver = false;
+
+    /* Stop the timer that was used to trigger the doorbell */
+    tfm_plat_test_secure_timer_stop();
+
+    return SCMI_COMMS_SUCCESS;
+}
diff --git a/partitions/scmi/test/secure/scmi_comms.yaml b/partitions/scmi/test/secure/scmi_comms.yaml
new file mode 100644
index 0000000..c04e801
--- /dev/null
+++ b/partitions/scmi/test/secure/scmi_comms.yaml
@@ -0,0 +1,22 @@
+#-------------------------------------------------------------------------------
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+#-------------------------------------------------------------------------------
+
+{
+  "psa_framework_version": 1.1,
+  "name": "SCMI_COMMS_PARTITION",
+  "type": "PSA-ROT",
+  "priority": "NORMAL",
+  "model": "IPC",
+  "entry_point": "scmi_comms_main",
+  "stack_size": "0x400",
+  "irqs": [
+    {
+      "source": "TFM_TIMER0_IRQ",
+      "name": "SCP_DOORBELL",
+      "handling": "SLIH",
+    }
+  ],
+}
diff --git a/partitions/scmi/test/secure/scmi_comms_manifest_list.yaml b/partitions/scmi/test/secure/scmi_comms_manifest_list.yaml
new file mode 100644
index 0000000..614f478
--- /dev/null
+++ b/partitions/scmi/test/secure/scmi_comms_manifest_list.yaml
@@ -0,0 +1,28 @@
+#-------------------------------------------------------------------------------
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+#-------------------------------------------------------------------------------
+
+{
+  "description": "SCMI Comms partition manifest",
+  "type": "manifest_list",
+  "version_major": 0,
+  "version_minor": 1,
+  "manifest_list": [
+    {
+      "description": "SCMI Comms Partition",
+      "manifest": "scmi_comms.yaml",
+      "output_path": "secure_fw/partitions/scmi",
+      "conditional": "TFM_PARTITION_SCMI_COMMS",
+      "version_major": 0,
+      "version_minor": 1,
+      "pid": 279,
+      "linker_pattern": {
+        "library_list": [
+           "*tfm_*partition_scmi_comms.*"
+        ]
+      }
+    }
+  ]
+}
diff --git a/partitions/scmi/test/secure/scmi_s_testsuite.c b/partitions/scmi/test/secure/scmi_s_testsuite.c
new file mode 100644
index 0000000..0a24834
--- /dev/null
+++ b/partitions/scmi/test/secure/scmi_s_testsuite.c
@@ -0,0 +1,310 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+ *
+ */
+
+#include "test_framework.h"
+#include "scmi_hal_defs.h"
+#include "tfm_hal_device_header.h"
+#include "tfm_plat_test.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+/* Duplicates of definitions in SCMI partition */
+#define TRANSPORT_BUFFER_STATUS_FREE_POS  0
+#define TRANSPORT_BUFFER_STATUS_FREE_MASK \
+    (UINT32_C(0x1) << TRANSPORT_BUFFER_STATUS_FREE_POS)
+
+#define TRANSPORT_BUFFER_STATUS_ERROR_POS 1
+#define TRANSPORT_BUFFER_STATUS_ERROR_MASK \
+    (UINT32_C(0x1) << TRANSPORT_BUFFER_STATUS_ERROR_POS)
+
+#define TRANSPORT_BUFFER_FLAGS_INTERRUPT_POS 0
+#define TRANSPORT_BUFFER_FLAGS_INTERRUPT_MASK \
+    (UINT32_C(0x1) << TRANSPORT_BUFFER_FLAGS_INTERRUPT_POS)
+
+struct transport_buffer_t {
+    uint32_t reserved0; /**< Reserved, must be zero */
+    volatile uint32_t status; /**< Channel status */
+    uint64_t reserved1; /**< Implementation defined field */
+    uint32_t flags; /**< Channel flags */
+    volatile uint32_t length; /**< Length in bytes of the message header and payload */
+    uint32_t message_header; /**< Message header */
+    uint32_t message_payload[]; /**< Message payload */
+};
+
+static struct transport_buffer_t *const shared_memory =
+    (struct transport_buffer_t *)SCP_SHARED_MEMORY_BASE;
+
+/**
+ * \brief Raise the SCMI partition's receiver doorbell and use the timer
+ *        interrupt to trigger the partition's IRQ handler. Wait until the SCMI
+ *        partition has cleared the doorbell.
+ */
+static void raise_partition_receiver_doorbell(void)
+{
+    /* Set the partition's receiver doorbell */
+    test_doorbell_receiver = true;
+
+    /* Start the timer to trigger the partition's interrupt */
+    tfm_plat_test_secure_timer_start();
+
+    /* Wait until the partition clears the doorbell */
+    while (test_doorbell_receiver) {
+        __WFE();
+    }
+}
+
+/**
+ * \brief Wait for the SCMI partition to raise its sender doorbell and then
+ *        clear it.
+ */
+static void wait_for_partition_sender_doorbell(void)
+{
+    /* Wait for the partition to raise its sender doorbell */
+    while (!test_doorbell_sender) {
+        __WFE();
+    }
+
+    /* Reset the partition's sender doorbell */
+    test_doorbell_sender = false;
+}
+
+/**
+ * \brief Checks that the SCMI partition sends a System Power State Notify
+ *        message on initialization to subscribe to system power state
+ *        notifications.
+ */
+static void scmi_test_subscribe(struct test_result_t *ret)
+{
+    const uint32_t expected_payload[] = { 1 /* notify_enable */ };
+    const struct transport_buffer_t expected_transport = {
+        .reserved0 = 0,
+        .status = 0,
+        .reserved1 = 0,
+        .flags = 0,
+        .length = 4 + sizeof(expected_payload),
+        .message_header = (0x12 << 10) /* protocol_id=system_power */ |
+                          (0x0 << 8) /* message_type=command */ |
+                          0x5 /* message_id=system_power_state_notify */,
+    };
+
+    /* First raise the partition's doorbell to signal SCP ready */
+    raise_partition_receiver_doorbell();
+
+    /* Wait for the partition to send SCMI notify subscription command */
+    wait_for_partition_sender_doorbell();
+
+    /* Check the transport buffer up to the message payload */
+    if (memcmp(shared_memory, &expected_transport,
+        offsetof(struct transport_buffer_t, message_payload)) != 0) {
+        TEST_FAIL("Transport buffer contained unexpected values\r\n");
+        return;
+    }
+
+    /* Check the message payload */
+    if (memcmp(&shared_memory->message_payload, expected_payload,
+        sizeof(expected_payload)) != 0) {
+        TEST_FAIL("Message payload contained unexpected values\r\n");
+        return;
+    }
+
+    /* Write a success response */
+    shared_memory->length = 4 + 4;
+    shared_memory->message_payload[0] = 0; /* SCMI_SUCCESS */
+    shared_memory->status |= TRANSPORT_BUFFER_STATUS_FREE_MASK;
+
+    /* Raise the partition's doorbell again to allow its execution to continue
+     * (only required because testing is done locally).
+     */
+    raise_partition_receiver_doorbell();
+
+    ret->val = TEST_PASSED;
+}
+
+/**
+ * \brief Tests sending a valid notification to the SCMI partition.
+ */
+static void scmi_test_valid_notification(struct test_result_t *ret)
+{
+    const uint32_t message_header = (0x12 << 10) /* protocol_id=system_power */ |
+                                    (0x3 << 8) /* message_type=notification */ |
+                                    0x0 /* message_id=system_power_state_notifier */;
+    const uint32_t notification_payload[] = { 0x1234 /* agent_id */,
+                                              0x0 /* flags */,
+                                              0x0 /* system_state=shutdown */ };
+    const struct transport_buffer_t expected_transport = {
+        .reserved0 = 0,
+        .status = 1,
+        .reserved1 = 0,
+        .flags = 0,
+        .length = 4,
+        .message_header = message_header,
+    };
+
+    /* Write notification */
+    shared_memory->length = 4 + sizeof(notification_payload);
+    shared_memory->message_header = message_header;
+    memcpy(shared_memory->message_payload, notification_payload,
+           sizeof(notification_payload));
+    shared_memory->status &= ~TRANSPORT_BUFFER_STATUS_FREE_MASK;
+
+    /* Raise the partition's doorbell to signal message */
+    raise_partition_receiver_doorbell();
+
+    /* Check the response in the transport buffer up to the message payload */
+    if (memcmp(shared_memory, &expected_transport,
+        offsetof(struct transport_buffer_t, message_payload)) != 0) {
+        TEST_FAIL("Transport buffer contained unexpected values\r\n");
+        return;
+    }
+
+    /* No response payload for notifications */
+
+    ret->val = TEST_PASSED;
+}
+
+/**
+ * \brief Tests sending messages with invalid lengths to the SCMI partition.
+ */
+static void scmi_test_invalid_message_length(struct test_result_t *ret)
+{
+    const uint32_t message_header = (0x12 << 10) /* protocol_id=system_power */ |
+                                    (0x3 << 8) /* message_type=notification */ |
+                                    0x0 /* message_id=system_power_state_notifier */;
+    const uint32_t notification_payload[] = { 0x1234 /* agent_id */,
+                                              0x0 /* flags */,
+                                              0x0 /* system_state=shutdown */ };
+    struct transport_buffer_t expected_transport = {
+        .reserved0 = 0,
+        .status = 1,
+        .reserved1 = 0,
+        .flags = 0,
+        .length = 4 + 4,
+        .message_header = message_header,
+    };
+
+    /* Write notification with length too small for header */
+    shared_memory->length = 3;
+    shared_memory->message_header = message_header;
+    memcpy(shared_memory->message_payload, notification_payload,
+           sizeof(notification_payload));
+    shared_memory->status &= ~TRANSPORT_BUFFER_STATUS_FREE_MASK;
+
+    raise_partition_receiver_doorbell();
+
+    if (memcmp(shared_memory, &expected_transport,
+        offsetof(struct transport_buffer_t, message_payload)) != 0) {
+        TEST_FAIL("Transport buffer contained unexpected values\r\n");
+        return;
+    }
+
+    if (shared_memory->message_payload[0] != (uint32_t)-10 /* PROTOCOL_ERROR */) {
+        TEST_FAIL("Invalid length did not return PROTOCOL_ERROR\r\n");
+        return;
+    }
+
+    /* Write notification with length that does not match message */
+    shared_memory->length = 8;
+    shared_memory->message_header = message_header;
+    memcpy(shared_memory->message_payload, notification_payload,
+           sizeof(notification_payload));
+    shared_memory->status &= ~TRANSPORT_BUFFER_STATUS_FREE_MASK;
+
+    raise_partition_receiver_doorbell();
+
+    /* No response payload for notifications */
+    expected_transport.length = 4;
+
+    if (memcmp(shared_memory, &expected_transport,
+        offsetof(struct transport_buffer_t, message_payload)) != 0) {
+        TEST_FAIL("Transport buffer contained unexpected values\r\n");
+        return;
+    }
+
+    /* Write notification with length too large for transport */
+    shared_memory->length = UINT32_MAX;
+    shared_memory->message_header = message_header;
+    memcpy(shared_memory->message_payload, notification_payload,
+           sizeof(notification_payload));
+    shared_memory->status &= ~TRANSPORT_BUFFER_STATUS_FREE_MASK;
+
+    raise_partition_receiver_doorbell();
+
+    expected_transport.length = 4 + 4;
+
+    if (memcmp(shared_memory, &expected_transport,
+        offsetof(struct transport_buffer_t, message_payload)) != 0) {
+        TEST_FAIL("Transport buffer contained unexpected values\r\n");
+        return;
+    }
+
+    if (shared_memory->message_payload[0] != (uint32_t)-10) {
+        TEST_FAIL("Invalid length did not return PROTOCOL_ERROR\r\n");
+        return;
+    }
+
+    ret->val = TEST_PASSED;
+}
+
+/**
+ * \brief Tests sending a message with an invalid header to the SCMI partition.
+ */
+static void scmi_test_invalid_message_header(struct test_result_t *ret)
+{
+    const uint32_t message_header = 0xDEADBEEF;
+    const uint32_t message_payload[] = { 0xF00 };
+    const struct transport_buffer_t expected_transport = {
+        .reserved0 = 0,
+        .status = 1,
+        .reserved1 = 0,
+        .flags = 0,
+        .length = 4 + 4,
+        .message_header = message_header,
+    };
+
+    /* Write message with an unknown message header */
+    shared_memory->length = 4 + sizeof(message_payload);
+    shared_memory->message_header = message_header;
+    memcpy(shared_memory->message_payload, message_payload,
+           sizeof(message_payload));
+    shared_memory->status &= ~TRANSPORT_BUFFER_STATUS_FREE_MASK;
+
+    raise_partition_receiver_doorbell();
+
+    if (memcmp(shared_memory, &expected_transport,
+        offsetof(struct transport_buffer_t, message_payload)) != 0) {
+        TEST_FAIL("Transport buffer contained unexpected values\r\n");
+        return;
+    }
+
+    if (shared_memory->message_payload[0] != (uint32_t)-1 /* NOT_SUPPORTED */) {
+        TEST_FAIL("Invalid message type did not return NOT_SUPPORTED\r\n");
+        return;
+    }
+
+    ret->val = TEST_PASSED;
+}
+
+static struct test_t scmi_s_tests[] = {
+    {&scmi_test_subscribe, "SCMI_S_TEST_1001",
+     "SCMI notification subscription test"},
+    {&scmi_test_valid_notification, "SCMI_S_TEST_1002",
+     "SCMI valid notification test"},
+    {&scmi_test_invalid_message_length, "SCMI_S_TEST_1003",
+     "SCMI invalid message length test"},
+    {&scmi_test_invalid_message_header, "SCMI_S_TEST_1004",
+     "SCMI invalid message header test"},
+};
+
+void register_testsuite_extra_s_interface(struct test_suite_t *p_test_suite)
+{
+    uint32_t list_size = sizeof(scmi_s_tests) / sizeof(scmi_s_tests[0]);
+
+    set_testsuite("SCMI Secure Tests (SCMI_S_TEST_1XXX)",
+                  scmi_s_tests, list_size, p_test_suite);
+}