Cover the AST1040 Caliptra mailbox execute flow through the external peer protocol and verify that the SCU CPTRA page register remaps the MCI aperture between CSR and SRAM pages.
Signed-off-by: Steven Lee <steven_lee@aspeedtech.com>
---
tests/qtest/aspeed_cptra_mbox-test.c | 223 +++++++++++++++++++++++++++
tests/qtest/meson.build | 3 +-
2 files changed, 225 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/aspeed_cptra_mbox-test.c
diff --git a/tests/qtest/aspeed_cptra_mbox-test.c b/tests/qtest/aspeed_cptra_mbox-test.c
new file mode 100644
index 0000000000..4d0e1682da
--- /dev/null
+++ b/tests/qtest/aspeed_cptra_mbox-test.c
@@ -0,0 +1,223 @@
+/*
+ * QTest for the ASPEED Caliptra mailbox on the ast1040-evb machine.
+ *
+ * Copyright (C) 2026 ASPEED Technology Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "qemu/sockets.h"
+
+/* AST1040 guest-visible mailbox bases. */
+#define SRAM_BASE 0x21400000ULL
+#define CSR_BASE 0x21600000ULL
+
+#define CSR_LOCK 0x000
+#define CSR_USER 0x004
+#define CSR_CMD 0x010
+#define CSR_DLEN 0x014
+#define CSR_EXECUTE 0x018
+#define CSR_CMD_STATUS 0x020
+
+#define STATUS_BUSY 0
+#define STATUS_COMPLETE 2
+#define STATUS_CMD_FAILURE 3
+
+/* SCU CPTRA page-select register (SCU base 0x74C02000 + 0x120). */
+#define SCU_CPTRA_PAGE_REG0 0x74C02120ULL
+/* MCI remap aperture. */
+#define MCI_WINDOW 0x74200000ULL
+
+/* Wire protocol. */
+#define PROTO_MAGIC 0x4D424F58u
+#define PROTO_VERSION 1u
+#define CMD_EXECUTE 1u
+#define CMD_RESPONSE 2u
+
+static int peer_lfd = -1;
+static int peer_fd = -1;
+
+static void peer_read(void *buf, size_t len)
+{
+ size_t off = 0;
+
+ while (off < len) {
+ ssize_t r = read(peer_fd, (uint8_t *)buf + off, len - off);
+ g_assert_cmpint(r, >, 0);
+ off += r;
+ }
+}
+
+static void peer_write(const void *buf, size_t len)
+{
+ size_t off = 0;
+
+ while (off < len) {
+ ssize_t w = write(peer_fd, (const uint8_t *)buf + off, len - off);
+ g_assert_cmpint(w, >, 0);
+ off += w;
+ }
+}
+
+static uint32_t ld32(const uint8_t *p)
+{
+ return p[0] | (p[1] << 8) | (p[2] << 16) | ((uint32_t)p[3] << 24);
+}
+
+static void st32(uint8_t *p, uint32_t v)
+{
+ p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24;
+}
+
+/*
+ * Service one MBOX_EXECUTE: read the request, optionally check it, then reply
+ * with MBOX_RESPONSE carrying @rsp_status and @rsp_data.
+ */
+static void peer_serve_execute(uint32_t expect_cmd, uint32_t expect_dlen,
+ uint32_t rsp_status,
+ const uint8_t *rsp_data, uint32_t rsp_dlen)
+{
+ uint8_t hdr[12];
+ uint32_t plen, cmd, dlen;
+ g_autofree uint8_t *payload = NULL;
+ uint32_t rsp_padded = (rsp_dlen + 3) & ~3u;
+ uint32_t rsp_plen = 8 + rsp_padded;
+ g_autofree uint8_t *rsp = g_malloc0(12 + rsp_plen);
+
+ peer_read(hdr, sizeof(hdr));
+ g_assert_cmpuint(ld32(hdr), ==, PROTO_MAGIC);
+ g_assert_cmpuint(hdr[4] | (hdr[5] << 8), ==, PROTO_VERSION);
+ g_assert_cmpuint(hdr[6] | (hdr[7] << 8), ==, CMD_EXECUTE);
+ plen = ld32(hdr + 8);
+ g_assert_cmpuint(plen, >=, 8);
+
+ payload = g_malloc(plen);
+ peer_read(payload, plen);
+ cmd = ld32(payload);
+ dlen = ld32(payload + 4);
+ g_assert_cmpuint(cmd, ==, expect_cmd);
+ g_assert_cmpuint(dlen, ==, expect_dlen);
+
+ st32(rsp, PROTO_MAGIC);
+ rsp[4] = PROTO_VERSION; rsp[5] = 0;
+ rsp[6] = CMD_RESPONSE; rsp[7] = 0;
+ st32(rsp + 8, rsp_plen);
+ st32(rsp + 12, rsp_status);
+ st32(rsp + 16, rsp_dlen);
+ if (rsp_dlen) {
+ memcpy(rsp + 20, rsp_data, rsp_dlen);
+ }
+ peer_write(rsp, 12 + rsp_plen);
+}
+
+static uint32_t poll_cmd_status(void)
+{
+ uint32_t s = STATUS_BUSY;
+ int tries = 1000;
+
+ while (tries--) {
+ s = qtest_readl(global_qtest, CSR_BASE + CSR_CMD_STATUS);
+ if (s != STATUS_BUSY) {
+ break;
+ }
+ }
+ return s;
+}
+
+/*
+ * Realistic mailbox transaction: acquire the lock, run an EXECUTE roundtrip
+ * against the peer, verify the response lands in SRAM, then release.
+ */
+static void test_execute(void)
+{
+ static const uint8_t resp[] = {
+ 0x11, 0xf9, 0xff, 0xff, /* checksum */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x00, 0x00, /* inner dlen = 20 */
+ 'C', 'a', 'l', 'i', 'p', 't', 'r', 'a',
+ '_', 'C', 'o', 'r', 'e', '_', 'v', '2', '.', '0', '.', '0',
+ };
+ uint32_t status;
+
+ /*
+ * Acquire: first read returns 0, USER reflects the SoC agent, busy after.
+ */
+ g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 0);
+ g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_USER), ==, 1);
+ g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 1);
+
+ qtest_writel(global_qtest, SRAM_BASE + 0, 0xfffffec0);
+ qtest_writel(global_qtest, CSR_BASE + CSR_CMD, 0x4d465756);
+ qtest_writel(global_qtest, CSR_BASE + CSR_DLEN, 8);
+ qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 1);
+
+ peer_serve_execute(0x4d465756, 8, STATUS_COMPLETE, resp, sizeof(resp));
+
+ status = poll_cmd_status();
+ g_assert_cmpuint(status, ==, STATUS_COMPLETE);
+ g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_DLEN),
+ ==, sizeof(resp));
+ g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 0), ==, 0xfffff911);
+ g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 8), ==, 0x14);
+ g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 12), ==, 0x696c6143);
+
+ /* Completing the transaction (EXECUTE 1->0) releases the lock. */
+ qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 0);
+ g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 0);
+ qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 0);
+}
+
+/* SCU CPTRA_PAGE_REG0 selects the target page seen through the MCI window. */
+static void test_scu_remap(void)
+{
+ /* Window pointed at the CSR page exposes the CMD register. */
+ qtest_writel(global_qtest, CSR_BASE + CSR_CMD, 0xdeadbeef);
+ qtest_writel(global_qtest, SCU_CPTRA_PAGE_REG0, CSR_BASE);
+ g_assert_cmpuint(qtest_readl(global_qtest, MCI_WINDOW + CSR_CMD),
+ ==, 0xdeadbeef);
+
+ /* Re-point at the SRAM page. */
+ qtest_writel(global_qtest, SRAM_BASE + 0, 0x12345678);
+ qtest_writel(global_qtest, SCU_CPTRA_PAGE_REG0, SRAM_BASE);
+ g_assert_cmpuint(qtest_readl(global_qtest, MCI_WINDOW + 0),
+ ==, 0x12345678);
+}
+
+int main(int argc, char **argv)
+{
+ g_autofree char *sock_path = NULL;
+ g_autofree char *cmdline = NULL;
+ int ret;
+
+ g_test_init(&argc, &argv, NULL);
+
+ sock_path = g_strdup_printf("%s/cptra-mbox-%u.sock",
+ g_get_tmp_dir(), getpid());
+ unlink(sock_path);
+ peer_lfd = qtest_socket_server(sock_path);
+
+ cmdline = g_strdup_printf(
+ "-machine ast1040-evb,cptra-peer=peer0 "
+ "-chardev socket,id=cptra0,path=%s "
+ "-device cptra-mbox-peer-extern,id=peer0,chardev=cptra0", sock_path);
+ qtest_start(cmdline);
+
+ peer_fd = accept(peer_lfd, NULL, NULL);
+ g_assert_cmpint(peer_fd, >=, 0);
+
+ qtest_add_func("/cptra-mbox/execute", test_execute);
+ qtest_add_func("/cptra-mbox/scu-remap", test_scu_remap);
+
+ ret = g_test_run();
+
+ qtest_end();
+ if (peer_fd >= 0) {
+ close(peer_fd);
+ }
+ close(peer_lfd);
+ unlink(sock_path);
+
+ return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 728dde54b3..c50da850f7 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -219,7 +219,8 @@ qtests_aspeed = \
['aspeed_gpio-test',
'aspeed_hace-test',
'aspeed_scu-test',
- 'aspeed_smc-test']
+ 'aspeed_smc-test',
+ 'aspeed_cptra_mbox-test']
qtests_aspeed64 = \
['ast2700-gpio-test',
'ast2700-hace-test',
--
2.43.0
© 2016 - 2026 Red Hat, Inc.