aboutsummaryrefslogtreecommitdiff
path: root/tests/qtest/aspeed_scu-test.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/qtest/aspeed_scu-test.c')
-rw-r--r--tests/qtest/aspeed_scu-test.c231
1 files changed, 231 insertions, 0 deletions
diff --git a/tests/qtest/aspeed_scu-test.c b/tests/qtest/aspeed_scu-test.c
new file mode 100644
index 0000000..ca09f91
--- /dev/null
+++ b/tests/qtest/aspeed_scu-test.c
@@ -0,0 +1,231 @@
+/*
+ * QTest testcase for the ASPEED AST2500 and AST2600 SCU.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright (C) 2025 Tan Siewert
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+/*
+ * SCU base, as well as protection key are
+ * the same on AST2500 and 2600.
+ */
+#define AST_SCU_BASE 0x1E6E2000
+#define AST_SCU_PROT_LOCK_STATE 0x0
+#define AST_SCU_PROT_LOCK_VALUE 0x2
+#define AST_SCU_PROT_UNLOCK_STATE 0x1
+#define AST_SCU_PROT_UNLOCK_VALUE 0x1688A8A8
+
+#define AST2500_MACHINE "-machine ast2500-evb"
+#define AST2500_SCU_PROT_REG 0x00
+#define AST2500_SCU_MISC_2_CONTROL_REG 0x4C
+
+#define AST2600_MACHINE "-machine ast2600-evb"
+/* AST2600 has two protection registers */
+#define AST2600_SCU_PROT_REG 0x000
+#define AST2600_SCU_PROT_REG2 0x010
+#define AST2600_SCU_MISC_2_CONTROL_REG 0x0C4
+
+#define TEST_LOCK_ARBITRARY_VALUE 0xABCDEFAB
+
+/**
+ * Assert that a given register matches an expected value.
+ *
+ * Reads the register and checks if its value equals the expected value.
+ *
+ * @param *s - QTest machine state
+ * @param reg - Address of the register to be checked
+ * @param expected - Expected register value
+ */
+static inline void assert_register_eq(QTestState *s,
+ uint32_t reg,
+ uint32_t expected)
+{
+ uint32_t value = qtest_readl(s, reg);
+ g_assert_cmphex(value, ==, expected);
+}
+
+/**
+ * Assert that a given register does not match a specific value.
+ *
+ * Reads the register and checks that its value is not equal to the
+ * provided value.
+ *
+ * @param *s - QTest machine state
+ * @param reg - Address of the register to be checked
+ * @param not_expected - Value the register must not contain
+ */
+static inline void assert_register_neq(QTestState *s,
+ uint32_t reg,
+ uint32_t not_expected)
+{
+ uint32_t value = qtest_readl(s, reg);
+ g_assert_cmphex(value, !=, not_expected);
+}
+
+/**
+ * Test whether the SCU can be locked and unlocked correctly.
+ *
+ * When testing multiple registers, this function assumes that writing
+ * to the first register also affects the others. However, writing to
+ * any other register only affects itself.
+ *
+ * @param *machine - input machine configuration, passed directly
+ * to QTest
+ * @param regs[] - List of registers to be checked
+ * @param regc - amount of arguments for registers to be checked
+ */
+static void test_protection_register(const char *machine,
+ const uint32_t regs[],
+ const int regc)
+{
+ QTestState *s = qtest_init(machine);
+
+ for (int i = 0; i < regc; i++) {
+ uint32_t reg = regs[i];
+
+ qtest_writel(s, reg, AST_SCU_PROT_UNLOCK_VALUE);
+ assert_register_eq(s, reg, AST_SCU_PROT_UNLOCK_STATE);
+
+ /**
+ * Check that other registers are unlocked too, if more
+ * than one is available.
+ */
+ if (regc > 1 && i == 0) {
+ /* Initialise at 1 instead of 0 to skip first */
+ for (int j = 1; j < regc; j++) {
+ uint32_t add_reg = regs[j];
+ assert_register_eq(s, add_reg, AST_SCU_PROT_UNLOCK_STATE);
+ }
+ }
+
+ /* Lock the register again */
+ qtest_writel(s, reg, AST_SCU_PROT_LOCK_VALUE);
+ assert_register_eq(s, reg, AST_SCU_PROT_LOCK_STATE);
+
+ /* And the same for locked state */
+ if (regc > 1 && i == 0) {
+ /* Initialise at 1 instead of 0 to skip first */
+ for (int j = 1; j < regc; j++) {
+ uint32_t add_reg = regs[j];
+ assert_register_eq(s, add_reg, AST_SCU_PROT_LOCK_STATE);
+ }
+ }
+ }
+
+ qtest_quit(s);
+}
+
+static void test_2500_protection_register(void)
+{
+ uint32_t regs[] = { AST_SCU_BASE + AST2500_SCU_PROT_REG };
+
+ test_protection_register(AST2500_MACHINE,
+ regs,
+ ARRAY_SIZE(regs));
+}
+
+static void test_2600_protection_register(void)
+{
+ /**
+ * The AST2600 has two protection registers, both
+ * being required to be unlocked to do any operation.
+ *
+ * Modifying SCU000 also modifies SCU010, but modifying
+ * SCU010 only will keep SCU000 untouched.
+ */
+ uint32_t regs[] = { AST_SCU_BASE + AST2600_SCU_PROT_REG,
+ AST_SCU_BASE + AST2600_SCU_PROT_REG2 };
+
+ test_protection_register(AST2600_MACHINE,
+ regs,
+ ARRAY_SIZE(regs));
+}
+
+/**
+ * Test if SCU register writes are correctly allowed or blocked
+ * depending on the protection register state.
+ *
+ * The test first locks the protection register and verifies that
+ * writes to the target SCU register are rejected. It then unlocks
+ * the protection register and confirms that the written value is
+ * retained when unlocked.
+ *
+ * @param *machine - input machine configuration, passed directly
+ * to QTest
+ * @param protection_register - first SCU protection key register
+ * (only one for keeping it simple)
+ * @param test_register - Register to be used for writing arbitrary
+ * values
+ */
+static void test_write_permission_lock_state(const char *machine,
+ const uint32_t protection_register,
+ const uint32_t test_register)
+{
+ QTestState *s = qtest_init(machine);
+
+ /* Arbitrary value to lock provided SCU protection register */
+ qtest_writel(s, protection_register, AST_SCU_PROT_LOCK_VALUE);
+
+ /* Ensure that the SCU is really locked */
+ assert_register_eq(s, protection_register, AST_SCU_PROT_LOCK_STATE);
+
+ /* Write a known arbitrary value to test that the write is blocked */
+ qtest_writel(s, test_register, TEST_LOCK_ARBITRARY_VALUE);
+
+ /* We do not want to have the written value to be saved */
+ assert_register_neq(s, test_register, TEST_LOCK_ARBITRARY_VALUE);
+
+ /**
+ * Unlock the SCU and verify that it can be written to.
+ * Assumes that the first SCU protection register is sufficient to
+ * unlock all protection registers, if multiple are present.
+ */
+ qtest_writel(s, protection_register, AST_SCU_PROT_UNLOCK_VALUE);
+ assert_register_eq(s, protection_register, AST_SCU_PROT_UNLOCK_STATE);
+
+ /* Write a known arbitrary value to test that the write works */
+ qtest_writel(s, test_register, TEST_LOCK_ARBITRARY_VALUE);
+
+ /* Ensure that the written value is retained */
+ assert_register_eq(s, test_register, TEST_LOCK_ARBITRARY_VALUE);
+
+ qtest_quit(s);
+}
+
+static void test_2500_write_permission_lock_state(void)
+{
+ test_write_permission_lock_state(
+ AST2500_MACHINE,
+ AST_SCU_BASE + AST2500_SCU_PROT_REG,
+ AST_SCU_BASE + AST2500_SCU_MISC_2_CONTROL_REG
+ );
+}
+
+static void test_2600_write_permission_lock_state(void)
+{
+ test_write_permission_lock_state(
+ AST2600_MACHINE,
+ AST_SCU_BASE + AST2600_SCU_PROT_REG,
+ AST_SCU_BASE + AST2600_SCU_MISC_2_CONTROL_REG
+ );
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/ast2500/scu/protection_register",
+ test_2500_protection_register);
+ qtest_add_func("/ast2600/scu/protection_register",
+ test_2600_protection_register);
+
+ qtest_add_func("/ast2500/scu/write_permission_lock_state",
+ test_2500_write_permission_lock_state);
+ qtest_add_func("/ast2600/scu/write_permission_lock_state",
+ test_2600_write_permission_lock_state);
+
+ return g_test_run();
+}