* [PATCH] tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer
@ 2026-08-13 12:32 Wadim Mueller
0 siblings, 0 replies; only message in thread
From: Wadim Mueller @ 2026-08-13 12:32 UTC (permalink / raw)
To: qemu-devel; +Cc: bmeng.cn, qemu-block, philmd
Add an image-free qtest for the microchip-icicle-kit machine that drives
the generic SDHCI model (hw/sd/sdhci.c) through the Cadence wrapper
(hw/sd/cadence_sdhci.c, SRS window at 0x20008200) directly over MMIO
under "-accel qtest", where QEMU_CLOCK_VIRTUAL only advances when the
test steps it.
It reproduces two aspects of the ADMA2 engine objectively, without any
guest firmware:
1. Pacing: a 12-descriptor (> SDHC_ADMA_DESCS_PER_DELAY) non-interrupt
ADMA2 CMD18 read completes after a model-dependent number of
SDHC_TRANSFER_DELAY virtual-clock steps, which the test reports.
2. MMIO coupling: a chain carrying a DMA-boundary interrupt descriptor
yields mid-chain and reschedules on the virtual clock. With the
clock frozen, a bare interrupt-status read must not itself resume or
complete the transfer. This asserts the behaviour intended by "Run
ADMA independently of MMIO": it passes on the fixed model and fails
on a model that resumes ADMA from an MMIO read.
The SD bring-up (CMD0/CMD8/ACMD41/CMD2/CMD3/CMD7/CMD16) and the ADMA2
descriptor-table layout mirror the pattern already used by the SDHCI
device model; SDHCI register offsets and ADMA attribute bits are
reproduced locally because sdhci-internal.h is not standalone-includable
from a qtest.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
MAINTAINERS | 1 +
tests/qtest/cadence-sdhci-test.c | 268 +++++++++++++++++++++++++++++++
tests/qtest/meson.build | 1 +
3 files changed, 270 insertions(+)
create mode 100644 tests/qtest/cadence-sdhci-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 63e9ba521b..12c657d000 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2277,6 +2277,7 @@ F: include/hw/sd/sd*
F: hw/sd/core.c
F: hw/sd/sd*
F: hw/sd/ssi-sd.c
+F: tests/qtest/cadence-sdhci-test.c
F: tests/qtest/fuzz-sdcard-test.c
F: tests/qtest/sdhci-test.c
diff --git a/tests/qtest/cadence-sdhci-test.c b/tests/qtest/cadence-sdhci-test.c
new file mode 100644
index 0000000000..f2f06fdcaf
--- /dev/null
+++ b/tests/qtest/cadence-sdhci-test.c
@@ -0,0 +1,268 @@
+/*
+ * QTest for Cadence SDHCI ADMA2 pacing / MMIO coupling on the Microchip
+ * PolarFire SoC Icicle Kit (microchip-icicle-kit).
+ *
+ * Drives the generic SDHCI model (hw/sd/sdhci.c) through the Cadence wrapper
+ * directly over MMIO under "-accel qtest", where QEMU_CLOCK_VIRTUAL only
+ * advances when the test steps it; no guest firmware runs.
+ *
+ * Copyright (c) 2026 Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+/*
+ * SDHCI register offsets and bits, mirrored from hw/sd/sdhci-internal.h,
+ * which is not standalone-includable from a qtest.
+ */
+#define SDHC_BLKSIZE 0x04
+#define SDHC_ARGUMENT 0x08
+#define SDHC_TRNMOD 0x0C
+#define SDHC_TRNS_DMA 0x0001
+#define SDHC_TRNS_BLK_CNT_EN 0x0002
+#define SDHC_TRNS_READ 0x0010
+#define SDHC_TRNS_MULTI 0x0020
+#define SDHC_CMDREG 0x0E
+#define SDHC_CMD_DATA_PRESENT (1 << 5)
+#define SDHC_RSPREG0 0x10
+#define SDHC_PRNSTS 0x24
+#define SDHC_CARD_PRESENT 0x00010000
+#define SDHC_HOSTCTL 0x28
+#define SDHC_CTRL_ADMA2_32 0x10
+#define SDHC_CLKCON 0x2C
+#define SDHC_CLOCK_INT_EN 0x0001
+#define SDHC_CLOCK_SDCLK_EN (1 << 2)
+#define SDHC_SWRST 0x2F
+#define SDHC_RESET_ALL 0x01
+#define SDHC_NORINTSTS 0x30
+#define SDHC_NIS_ERR 0x8000
+#define SDHC_NIS_CMDCMP 0x0001
+#define SDHC_NIS_TRSCMP 0x0002
+#define SDHC_NIS_DMA 0x0008
+#define SDHC_NORINTSTSEN 0x34
+#define SDHC_ERRINTSTSEN 0x36
+#define SDHC_NORINTSIGEN 0x38
+#define SDHC_ADMASYSADDR 0x58
+#define SDHC_ADMA_ATTR_ACT_TRAN (1 << 5)
+#define SDHC_ADMA_ATTR_INT (1 << 2)
+#define SDHC_ADMA_ATTR_END (1 << 1)
+#define SDHC_ADMA_ATTR_VALID (1 << 0)
+#define SDHC_TRANSFER_DELAY 100 /* ns between ADMA batches */
+
+/* Cadence maps the generic SDHCI (SRS) register window at base + 0x200. */
+#define SDHCI_BASE (0x20008000ULL + 0x200)
+
+/* Scratch in PolarFire DRAM (base 0x80000000): descriptor table + target. */
+#define ADMA_TABLE_ADDR 0x82000000ULL
+#define ADMA_BUF_ADDR 0x82100000ULL
+
+#define BLK_LEN 512
+#define NDESC 12 /* > SDHC_ADMA_DESCS_PER_DELAY (5) */
+#define INT_DESC 5 /* DMA-boundary interrupt descriptor */
+
+/* CMDREG response-type encodings (bits [1:0]). */
+#define RESP_NONE 0x0000
+#define RESP_R2 0x0001 /* 136-bit */
+#define RESP_R48 0x0002 /* 48-bit */
+#define RESP_R1B 0x0003 /* 48-bit with busy */
+
+#define ACMD41_ARG 0x40FF8000u /* HCS + 3.3-3.6V window */
+#define CMD8_ARG 0x000001AAu /* VHS 2.7-3.6V + check pattern */
+
+/* Issue an SD command and wait (bounded) for Command Complete. */
+static uint16_t sd_cmd(QTestState *qts, uint8_t cmd, uint32_t arg,
+ uint16_t flags)
+{
+ uint16_t sts = 0;
+ int i;
+
+ qtest_writel(qts, SDHCI_BASE + SDHC_ARGUMENT, arg);
+ qtest_writew(qts, SDHCI_BASE + SDHC_CMDREG, ((uint16_t)cmd << 8) | flags);
+
+ for (i = 0; i < 1000; i++) {
+ sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS);
+ if (sts & SDHC_NIS_CMDCMP) {
+ break;
+ }
+ }
+ g_assert_cmphex(sts & SDHC_NIS_CMDCMP, ==, SDHC_NIS_CMDCMP);
+
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSTS, 0xffff); /* w1c */
+ return sts;
+}
+
+/* Bring the SD card from idle to transfer state. */
+static void sd_bring_up_card(QTestState *qts)
+{
+ uint32_t rca;
+
+ g_assert_cmphex(qtest_readl(qts, SDHCI_BASE + SDHC_PRNSTS) &
+ SDHC_CARD_PRESENT, ==, SDHC_CARD_PRESENT);
+
+ qtest_writeb(qts, SDHCI_BASE + SDHC_SWRST, SDHC_RESET_ALL);
+ qtest_writew(qts, SDHCI_BASE + SDHC_CLKCON,
+ SDHC_CLOCK_INT_EN | SDHC_CLOCK_SDCLK_EN);
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSTSEN, 0xffff);
+ qtest_writew(qts, SDHCI_BASE + SDHC_ERRINTSTSEN, 0xffff);
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSIGEN, 0xffff);
+
+ sd_cmd(qts, 0, 0x00000000, RESP_NONE); /* GO_IDLE_STATE */
+ sd_cmd(qts, 8, CMD8_ARG, RESP_R48); /* SEND_IF_COND */
+ sd_cmd(qts, 55, 0x00000000, RESP_R48); /* APP_CMD */
+ sd_cmd(qts, 41, ACMD41_ARG, RESP_R48); /* SD_SEND_OP_COND */
+ sd_cmd(qts, 2, 0x00000000, RESP_R2); /* ALL_SEND_CID */
+ sd_cmd(qts, 3, 0x00000000, RESP_R48); /* SEND_RELATIVE_ADDR */
+
+ rca = qtest_readl(qts, SDHCI_BASE + SDHC_RSPREG0) >> 16; /* R6 */
+
+ sd_cmd(qts, 7, rca << 16, RESP_R1B); /* SELECT_CARD */
+ sd_cmd(qts, 16, BLK_LEN, RESP_R48); /* SET_BLOCKLEN */
+}
+
+/*
+ * Build a 32-bit ADMA2 table: per 8-byte entry word0 = (len << 16) | attr,
+ * word1 = target address. When int_desc >= 0 that descriptor also carries
+ * the DMA-boundary interrupt attribute, so the model yields mid-chain
+ * (rescheduling on QEMU_CLOCK_VIRTUAL) instead of running to completion.
+ */
+static void build_adma_table(QTestState *qts, int int_desc)
+{
+ for (int i = 0; i < NDESC; i++) {
+ uint8_t attr = SDHC_ADMA_ATTR_VALID | SDHC_ADMA_ATTR_ACT_TRAN;
+ uint32_t addr = (uint32_t)(ADMA_BUF_ADDR + (uint64_t)i * BLK_LEN);
+
+ if (i == int_desc) {
+ attr |= SDHC_ADMA_ATTR_INT;
+ }
+ if (i == NDESC - 1) {
+ attr |= SDHC_ADMA_ATTR_END;
+ }
+ qtest_writel(qts, ADMA_TABLE_ADDR + (uint64_t)i * 8,
+ ((uint32_t)BLK_LEN << 16) | attr);
+ qtest_writel(qts, ADMA_TABLE_ADDR + (uint64_t)i * 8 + 4, addr);
+ }
+}
+
+/* Program the ADMA2 engine, descriptor pointer and transfer geometry. */
+static void program_adma_read(QTestState *qts)
+{
+ qtest_writeb(qts, SDHCI_BASE + SDHC_HOSTCTL, SDHC_CTRL_ADMA2_32);
+ qtest_writel(qts, SDHCI_BASE + SDHC_ADMASYSADDR, (uint32_t)ADMA_TABLE_ADDR);
+ qtest_writel(qts, SDHCI_BASE + SDHC_ADMASYSADDR + 4,
+ (uint32_t)(ADMA_TABLE_ADDR >> 32));
+ qtest_writel(qts, SDHCI_BASE + SDHC_BLKSIZE, BLK_LEN | (NDESC << 16));
+ qtest_writew(qts, SDHCI_BASE + SDHC_TRNMOD,
+ SDHC_TRNS_DMA | SDHC_TRNS_BLK_CNT_EN |
+ SDHC_TRNS_READ | SDHC_TRNS_MULTI);
+ qtest_writel(qts, SDHCI_BASE + SDHC_ARGUMENT, 0); /* start block 0 */
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSTS, 0xffff);
+}
+
+/* CMD18 READ_MULTIPLE_BLOCK with data present -> kicks off ADMA. */
+static void kick_cmd18(QTestState *qts)
+{
+ qtest_writew(qts, SDHCI_BASE + SDHC_CMDREG,
+ (18 << 8) | SDHC_CMD_DATA_PRESENT | RESP_R48);
+}
+
+/* Start a card-carrying icicle machine and bring the card to "tran". */
+static QTestState *icicle_start(char **tmp)
+{
+ int fd;
+ GError *err = NULL;
+ QTestState *qts;
+
+ fd = g_file_open_tmp("icicle-sdhci-XXXXXX.raw", tmp, &err);
+ g_assert_no_error(err);
+ g_assert_cmpint(fd, >=, 0);
+ g_assert_cmpint(ftruncate(fd, 1 * 1024 * 1024), ==, 0);
+ close(fd);
+
+ qts = qtest_initf("-machine microchip-icicle-kit -accel qtest "
+ "-display none -drive if=sd,file=%s,format=raw", *tmp);
+ sd_bring_up_card(qts);
+ return qts;
+}
+
+/*
+ * Facet 1 -- pacing. A non-interrupt chain completes after some number of
+ * SDHC_TRANSFER_DELAY steps (several for a batched model, none for one that
+ * runs a no-interrupt chain to completion); the count is reported, not
+ * asserted -- only a clean Transfer Complete is required.
+ */
+static void test_adma_pacing(void)
+{
+ char *tmp = NULL;
+ QTestState *qts = icicle_start(&tmp);
+ uint16_t sts;
+ int steps = 0;
+
+ build_adma_table(qts, -1);
+ program_adma_read(qts);
+ kick_cmd18(qts);
+
+ while (!((sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS)) &
+ SDHC_NIS_TRSCMP)) {
+ qtest_clock_step(qts, SDHC_TRANSFER_DELAY);
+ steps++;
+ g_assert_cmpint(steps, <, 1000);
+ }
+
+ g_assert_cmphex(sts & SDHC_NIS_TRSCMP, ==, SDHC_NIS_TRSCMP);
+ g_assert_cmphex(sts & SDHC_NIS_ERR, ==, 0);
+
+ g_test_message("ADMA %d descriptors: %d clock steps to Transfer Complete",
+ NDESC, steps);
+
+ qtest_quit(qts);
+ unlink(tmp);
+ g_free(tmp);
+}
+
+/*
+ * Facet 2 -- MMIO coupling (regression for "Run ADMA independently of MMIO").
+ * With the clock frozen, a bare interrupt-status read must not resume the
+ * transfer left pending by the mid-chain interrupt descriptor: Transfer
+ * Complete must still be clear. A model that resumes ADMA from an MMIO read
+ * completes it on that bare read and fails the assertion.
+ */
+static void test_adma_mmio_coupling(void)
+{
+ char *tmp = NULL;
+ QTestState *qts = icicle_start(&tmp);
+ uint16_t sts;
+ int steps = 0;
+
+ build_adma_table(qts, INT_DESC);
+ program_adma_read(qts);
+ kick_cmd18(qts);
+
+ sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS);
+ g_assert_cmphex(sts & SDHC_NIS_TRSCMP, ==, 0);
+
+ while (!((sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS)) &
+ SDHC_NIS_TRSCMP)) {
+ qtest_clock_step(qts, SDHC_TRANSFER_DELAY);
+ steps++;
+ g_assert_cmpint(steps, <, 1000);
+ }
+ g_assert_cmphex(sts & SDHC_NIS_ERR, ==, 0);
+
+ g_test_message("ADMA coupling: %d clock steps to complete after bare read",
+ steps);
+
+ qtest_quit(qts);
+ unlink(tmp);
+ g_free(tmp);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/microchip/icicle/sdhci/adma-pacing", test_adma_pacing);
+ qtest_add_func("/microchip/icicle/sdhci/adma-mmio-coupling",
+ test_adma_mmio_coupling);
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 54e2cb0461..78e47ee024 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -283,6 +283,7 @@ qtests_riscv32 = \
(config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : [])
qtests_riscv64 = ['riscv-csr-test'] + \
+ (config_all_devices.has_key('CONFIG_MICROCHIP_PFSOC') ? ['cadence-sdhci-test'] : []) + \
(unpack_edk2_blobs ? ['bios-tables-test'] : [])
qos_test_ss = ss.source_set()
--
2.43.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-08-13 12:32 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 12:32 [PATCH] tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer Wadim Mueller
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.