diff --git a/apps/uefi/Bsa.inf b/apps/uefi/Bsa.inf
index d79e29c0..955a7ad5 100644
--- a/apps/uefi/Bsa.inf
+++ b/apps/uefi/Bsa.inf
@@ -85,6 +85,7 @@
../../test_pool/timer/t005.c
../../test_pool/timer/t006.c
../../test_pool/timer/t008.c
+ ../../test_pool/timer/t009.c
../../test_pool/watchdog/w001.c
../../test_pool/watchdog/w002.c
../../test_pool/peripherals/d001.c
diff --git a/apps/uefi/Sbsa.inf b/apps/uefi/Sbsa.inf
index d6acec0a..d8be7a8a 100644
--- a/apps/uefi/Sbsa.inf
+++ b/apps/uefi/Sbsa.inf
@@ -88,6 +88,7 @@
../../test_pool/timer/t005.c
../../test_pool/timer/t006.c
../../test_pool/timer/t008.c
+ ../../test_pool/timer/t009.c
../../test_pool/watchdog/w001.c
../../test_pool/watchdog/w002.c
../../test_pool/peripherals/d001.c
diff --git a/apps/uefi/Vbsa.inf b/apps/uefi/Vbsa.inf
index 4b152964..509b1d5a 100644
--- a/apps/uefi/Vbsa.inf
+++ b/apps/uefi/Vbsa.inf
@@ -87,6 +87,7 @@
../../test_pool/timer/t005.c
../../test_pool/timer/t006.c
../../test_pool/timer/t008.c
+ ../../test_pool/timer/t009.c
../../test_pool/watchdog/w001.c
../../test_pool/watchdog/w002.c
../../test_pool/peripherals/d001.c
diff --git a/apps/uefi/pc_bsa.inf b/apps/uefi/pc_bsa.inf
index 4b48389d..d003f3b4 100644
--- a/apps/uefi/pc_bsa.inf
+++ b/apps/uefi/pc_bsa.inf
@@ -88,6 +88,7 @@
../../test_pool/timer/t005.c
../../test_pool/timer/t006.c
../../test_pool/timer/t008.c
+ ../../test_pool/timer/t009.c
../../test_pool/watchdog/w001.c
../../test_pool/watchdog/w002.c
../../test_pool/peripherals/d001.c
diff --git a/apps/uefi/xbsa_acpi.inf b/apps/uefi/xbsa_acpi.inf
index a339354d..33b6363f 100644
--- a/apps/uefi/xbsa_acpi.inf
+++ b/apps/uefi/xbsa_acpi.inf
@@ -195,6 +195,7 @@
../../test_pool/gic/g016.c
../../test_pool/timer/t006.c
../../test_pool/timer/t008.c
+ ../../test_pool/timer/t009.c
../../test_pool/watchdog/w003.c
../../test_pool/smmu/i008.c
../../test_pool/smmu/i009.c
diff --git a/docs/bsa/arm_bsa_testcase_checklist.md b/docs/bsa/arm_bsa_testcase_checklist.md
index 1d0fd854..1f641898 100644
--- a/docs/bsa/arm_bsa_testcase_checklist.md
+++ b/docs/bsa/arm_bsa_testcase_checklist.md
@@ -663,11 +663,11 @@ The checklist provides information about:
L1 |
B_TIME_05 |
B_TIME_05 |
- Not Covered |
- |
- |
- |
- |
+ 501,502,503,504,505 |
+ Check sys cnt visible to PE timers |
+ Yes |
+ Yes |
+ No |
|
@@ -718,11 +718,11 @@ The checklist provides information about:
| L1 |
B_TIME_10 |
B_TIME_10 |
- Not Covered |
- |
- |
- |
- |
+ 410 |
+ Check Non-secure timer frame access |
+ Yes |
+ Yes |
+ No |
|
diff --git a/test_pool/timer/t009.c b/test_pool/timer/t009.c
new file mode 100644
index 00000000..2decb853
--- /dev/null
+++ b/test_pool/timer/t009.c
@@ -0,0 +1,165 @@
+/** @file
+ * Copyright (c) 2026, Arm Limited or its affiliates. All rights reserved.
+ * SPDX-License-Identifier : Apache-2.0
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ **/
+
+#include "acs_val.h"
+#include "val_interface.h"
+#include "acs_pe.h"
+#include "acs_timer.h"
+
+#define TEST_NUM (ACS_TIMER_TEST_NUM_BASE + 10)
+#define TEST_RULE "B_TIME_10"
+#define TEST_DESC "Check Non-secure timer frame access "
+
+#define ACCESS_CNTTIDR 1
+#define ACCESS_CNTPCT 2
+
+static void *branch_to_test;
+static uint64_t fault_timer_index;
+static uint32_t fault_access_type;
+
+static
+void
+esr(uint64_t interrupt_type, void *context)
+{
+ uint32_t index = val_pe_get_index_mpid(val_pe_get_mpid());
+
+ val_pe_update_elr(context, (uint64_t)branch_to_test);
+
+ val_print(ERROR, "\n Received exception type %d", interrupt_type);
+ if (fault_access_type == ACCESS_CNTTIDR)
+ val_print(ERROR, " during CNTCTLBase.CNTTIDR read for timer %llx", fault_timer_index);
+ else if (fault_access_type == ACCESS_CNTPCT)
+ val_print(ERROR, " during CNTBaseN.CNTPCT read for timer %llx", fault_timer_index);
+ val_set_status(index, RESULT_FAIL(1));
+}
+
+static
+void
+payload()
+{
+ uint32_t index = val_pe_get_index_mpid(val_pe_get_mpid());
+ uint32_t status;
+ uint32_t ns_timer = 0;
+ uint32_t readable_timer = 0;
+ uint32_t cnttidr;
+ uint64_t cnt_ctl_base;
+ uint64_t cnt_base_n;
+ uint64_t cntpct;
+ uint64_t timer_num = val_timer_get_info(TIMER_INFO_NUM_PLATFORM_TIMERS, 0);
+
+ branch_to_test = &&exception_taken;
+ status = val_pe_install_esr(EXCEPT_AARCH64_SYNCHRONOUS_EXCEPTIONS, esr);
+ status |= val_pe_install_esr(EXCEPT_AARCH64_SERROR, esr);
+ if (status) {
+ val_print(ERROR, "\n Failed to install exception handlers");
+ val_set_status(index, RESULT_FAIL(2));
+ return;
+ }
+
+ if (!timer_num) {
+ val_print(DEBUG, "\n No System timers are defined");
+ val_set_status(index, RESULT_SKIP(1));
+ return;
+ }
+
+ while (timer_num) {
+ --timer_num;
+
+ /* Skip secure timers */
+ if (val_timer_get_info(TIMER_INFO_IS_PLATFORM_TIMER_SECURE, timer_num))
+ continue;
+
+ ns_timer++;
+ cnt_ctl_base = val_timer_get_info(TIMER_INFO_SYS_CNTL_BASE, timer_num);
+ cnt_base_n = val_timer_get_info(TIMER_INFO_SYS_CNT_BASE_N, timer_num);
+
+ /*
+ * Firmware should expose non-zero base addresses for usable Non-secure
+ * timer frames. Keep scanning other frames so one bad descriptor does not
+ * hide a later usable frame.
+ */
+ if (cnt_ctl_base == 0)
+ val_print(WARN, "\n CNTCTL BASE is zero for timer %llx", timer_num);
+
+ if (cnt_base_n == 0)
+ val_print(WARN, "\n CNT_BASE_N is zero for timer %llx", timer_num);
+
+ if ((cnt_ctl_base == 0) || (cnt_base_n == 0))
+ continue;
+
+ /*
+ * The payload checks Non-secure memory access of CNTCTLBase and CNTBaseN,
+ * not register behavior. Do read probes only: no writes, no read-only/RW
+ * validation, and no CNTACR permission interpretation.
+ */
+ fault_timer_index = timer_num;
+ fault_access_type = ACCESS_CNTTIDR;
+ cnttidr = val_mmio_read(cnt_ctl_base + CNTTIDR);
+ val_print(DEBUG, "\n CNTTIDR Read value = 0x%x", cnttidr);
+
+ /*
+ * CNTPCT may legally read as RAZ/WI if CNTACR.RPCT is clear. That is
+ * acceptable here because CNTACR/CNTNSAR policy is Secure-state owned;
+ * this test only requires that the Non-secure read access itself does
+ * not fault.
+ */
+ fault_access_type = ACCESS_CNTPCT;
+ cntpct = val_mmio_read64(cnt_base_n + CNTPCT_LOWER);
+ val_print(DEBUG, "\n CNTPCT Read value = 0x%llx", cntpct);
+
+ readable_timer++;
+ }
+
+ /* Report fail if OS visible platform timers exists, but non of them are non-secure access */
+ if (!ns_timer) {
+ val_print(ERROR, "\n No non-secure systimer implemented");
+ val_set_status(index, RESULT_FAIL(3));
+ return;
+ }
+
+ if (!readable_timer) {
+ val_print(ERROR, "\n No usable non-secure systimer frame found");
+ val_set_status(index, RESULT_FAIL(4));
+ return;
+ }
+
+ val_set_status(index, RESULT_PASS);
+
+exception_taken:
+ return;
+}
+
+uint32_t
+t009_entry(uint32_t num_pe)
+{
+ uint32_t status = ACS_STATUS_FAIL;
+
+ num_pe = 1;
+
+ val_log_context((char8_t *)__FILE__, (char8_t *)__func__, __LINE__);
+ status = val_initialize_test(TEST_NUM, TEST_DESC, num_pe);
+
+ if (status != ACS_STATUS_SKIP)
+ val_run_test_payload(TEST_NUM, num_pe, payload, 0);
+
+ /* get the result from all PE and check for failure */
+ status = val_check_for_error(TEST_NUM, num_pe, TEST_RULE);
+
+ val_report_status(0, ACS_END(TEST_NUM), NULL);
+
+ return status;
+}
diff --git a/tools/cmake/infra/bsa_test.txt b/tools/cmake/infra/bsa_test.txt
index f65fbd7f..6fca881b 100644
--- a/tools/cmake/infra/bsa_test.txt
+++ b/tools/cmake/infra/bsa_test.txt
@@ -49,6 +49,7 @@ t002.c
t003.c
t004.c
t005.c
+t009.c
w001.c
w002.c
d001.c
diff --git a/tools/cmake/infra/pc_bsa_test.txt b/tools/cmake/infra/pc_bsa_test.txt
index 527afa0b..def7d689 100644
--- a/tools/cmake/infra/pc_bsa_test.txt
+++ b/tools/cmake/infra/pc_bsa_test.txt
@@ -100,6 +100,7 @@ t002.c
t003.c
t004.c
t005.c
+t009.c
tpm001.c
tpm002.c
e001.c
diff --git a/val/include/acs_timer.h b/val/include/acs_timer.h
index d4f41076..e0d05daf 100644
--- a/val/include/acs_timer.h
+++ b/val/include/acs_timer.h
@@ -49,4 +49,5 @@ uint32_t t004_entry(uint32_t num_pe);
uint32_t t005_entry(uint32_t num_pe);
uint32_t t006_entry(uint32_t num_pe);
uint32_t t008_entry(uint32_t num_pe);
+uint32_t t009_entry(uint32_t num_pe);
#endif // __ACS_TIMER_H__
diff --git a/val/include/rule_based_execution_enum.h b/val/include/rule_based_execution_enum.h
index 0a94d1eb..a2a614c7 100644
--- a/val/include/rule_based_execution_enum.h
+++ b/val/include/rule_based_execution_enum.h
@@ -765,6 +765,7 @@ typedef enum {
T005_ENTRY,
T006_ENTRY,
T008_ENTRY,
+ T009_ENTRY,
W001_ENTRY,
W002_ENTRY,
W003_ENTRY,
diff --git a/val/src/rule_metadata.c b/val/src/rule_metadata.c
index 4c1b7aef..04dbc52f 100644
--- a/val/src/rule_metadata.c
+++ b/val/src/rule_metadata.c
@@ -1404,6 +1404,13 @@ rule_test_map_t rule_test_map[RULE_ID_SENTINEL] = {
.flag = BASE_RULE,
.test_num = ACS_TIMER_TEST_NUM_BASE + 1,
},
+ [B_TIME_05] = {
+ .test_entry_id = B_WAK_03_07_ENTRY,
+ .module_id = TIMER,
+ .rule_desc = "Check sys cnt visible to PE timers",
+ .platform_bitmask = PLATFORM_BAREMETAL | PLATFORM_UEFI,
+ .flag = BASE_RULE,
+ },
[B_TIME_06] = {
.test_entry_id = T002_ENTRY,
.module_id = TIMER,
@@ -1436,6 +1443,14 @@ rule_test_map_t rule_test_map[RULE_ID_SENTINEL] = {
.flag = BASE_RULE,
.test_num = ACS_TIMER_TEST_NUM_BASE + 5,
},
+ [B_TIME_10] = {
+ .test_entry_id = T009_ENTRY,
+ .module_id = TIMER,
+ .rule_desc = "Check Non-secure timer frame access",
+ .platform_bitmask = PLATFORM_BAREMETAL | PLATFORM_UEFI,
+ .flag = BASE_RULE,
+ .test_num = ACS_TIMER_TEST_NUM_BASE + 10,
+ },
[S_L5TI_01] = {
.test_entry_id = T006_ENTRY,
.module_id = TIMER,
@@ -3125,12 +3140,6 @@ rule_test_map_t rule_test_map[RULE_ID_SENTINEL] = {
[B_TIME_04] = {
.module_id = TIMER,
},
- [B_TIME_05] = {
- .module_id = TIMER,
- },
- [B_TIME_10] = {
- .module_id = TIMER,
- },
[S_L8TI_01] = {
.module_id = TIMER,
},
@@ -3803,6 +3812,7 @@ test_entry_fn_t test_entry_func_table[TEST_ENTRY_SENTINEL] = {
[T004_ENTRY] = t004_entry,
[T005_ENTRY] = t005_entry,
[T008_ENTRY] = t008_entry,
+ [T009_ENTRY] = t009_entry,
[U001_ENTRY] = u001_entry, // used in wrapper.
[U002_ENTRY] = u002_entry, // used in wrapper.
[U003_ENTRY] = u003_entry, // used in wrapper.
@@ -3917,6 +3927,7 @@ test_entry_fn_t test_entry_func_table[TEST_ENTRY_SENTINEL] = {
[T003_ENTRY] = t003_entry,
[T004_ENTRY] = t004_entry,
[T005_ENTRY] = t005_entry,
+ [T009_ENTRY] = t009_entry,
[W001_ENTRY] = w001_entry,
[W002_ENTRY] = w002_entry,
[D001_ENTRY] = d001_entry,
@@ -4290,6 +4301,7 @@ test_entry_fn_t test_entry_func_table[TEST_ENTRY_SENTINEL] = {
[T004_ENTRY] = t004_entry,
[T001_ENTRY] = t001_entry,
[T002_ENTRY] = t002_entry,
+ [T009_ENTRY] = t009_entry,
[E039_ENTRY] = e039_entry, // used in wrapper.
[E035_ENTRY] = e035_entry,
[E013_ENTRY] = e013_entry,