Add a standalone qtest for the SDHCI controller on the xilinx-zynq-a9 machine.
The test covers the SDMA boundary continuation case for a multi-block read. After the controller stops at a boundary, software updates SYSAD to continue the transfer. This verifies the generic SDHCI behavior that previously failed when the continuation SYSAD write was ignored and the transfer stalled. Reviewed-by: Bin Meng <[email protected]> Signed-off-by: Tao Ding <[email protected]> --- Build: mkdir build cd build ../configure --target-list="arm-softmmu" --disable-slirp --enable-debug --disable-werror make -j4 Run: QTEST_QEMU_BINARY=./qemu-system-arm \ tests/qtest/xilinx-zynq-sdhci-test Before the fix, the test fails with a BLKCNT assertion: ERROR:../tests/qtest/xilinx-zynq-sdhci-test.c:90:wait_for_block_count: assertion failed (qtest_readw(qts, XILINX_ZYNQ_MMC_BA + SDHC_BLKCNT) == expected): (1 == 0) After applying the fix, the test passes: ok 1 /arm/xilinx-zynq-sdhci/sdma/read-boundary-continue tests/qtest/meson.build | 1 + tests/qtest/xilinx-zynq-sdhci-test.c | 178 +++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 tests/qtest/xilinx-zynq-sdhci-test.c diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 2ae720156f..c12a35721d 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -244,6 +244,7 @@ qtests_arm = \ config_all_devices.has_key('CONFIG_MUSICPAL') ? ['pflash-cfi02-test'] : []) + \ (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed : []) + \ (config_all_devices.has_key('CONFIG_NPCM7XX') ? qtests_npcm7xx : []) + \ + (config_all_devices.has_key('CONFIG_ZYNQ') ? ['xilinx-zynq-sdhci-test'] : []) + \ (config_all_devices.has_key('CONFIG_GENERIC_LOADER') ? ['hexloader-test'] : []) + \ (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 'tpm-tis-i2c-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \ diff --git a/tests/qtest/xilinx-zynq-sdhci-test.c b/tests/qtest/xilinx-zynq-sdhci-test.c new file mode 100644 index 0000000000..1be9bdabf7 --- /dev/null +++ b/tests/qtest/xilinx-zynq-sdhci-test.c @@ -0,0 +1,178 @@ +/* +* QTests for the Xilinx Zynq SDHCI controller. +* +* Copyright (c) 2026 Tao Ding <[email protected]> +* +* SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "qemu/osdep.h" +#include "hw/sd/sdhci.h" +#include "hw/sd/sdhci-internal.h" + +#include "libqtest.h" +#include "libqtest-single.h" +#include "libqos/sdhci-cmd.h" + +#define XILINX_ZYNQ_MMC_BA 0xE0100000 +#define XILINX_ZYNQ_BLK_SIZE 512 +#define XILINX_ZYNQ_TEST_IMAGE_SIZE (1 << 20) +#define XILINX_ZYNQ_SDMA_BOUNDARY_ARG 7 +#define XILINX_ZYNQ_SDMA_BOUNDARY_SIZE (512 * 1024) +#define XILINX_ZYNQ_SDMA_BLOCK_COUNT \ + ((XILINX_ZYNQ_SDMA_BOUNDARY_SIZE / XILINX_ZYNQ_BLK_SIZE) + 1) +#define XILINX_ZYNQ_SDMA_TRANSFER_SIZE \ + (XILINX_ZYNQ_SDMA_BLOCK_COUNT * XILINX_ZYNQ_BLK_SIZE) +#define XILINX_ZYNQ_DMA_ADDR 0x00100000 + +#define SDHC_MAKE_BLKSZ(boundary, size) (((boundary) << 12) | (size)) + +static char *sd_path; + +static QTestState *setup_sd_card(void) +{ + QTestState *qts; + uint16_t rca; + + qts = qtest_initf( + "-machine xilinx-zynq-a9 " + "-drive if=sd,index=0,file=%s,format=raw,auto-read-only=off", + sd_path); + + qtest_writew(qts, XILINX_ZYNQ_MMC_BA + SDHC_SWRST, SDHC_RESET_ALL); + qtest_writew(qts, XILINX_ZYNQ_MMC_BA + SDHC_CLKCON, + SDHC_CLOCK_SDCLK_EN | SDHC_CLOCK_INT_STABLE | + SDHC_CLOCK_INT_EN); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0, 0, SDHC_APP_CMD); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0x41200000, 0, + (41 << 8)); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0, 0, SDHC_ALL_SEND_CID); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0, 0, + SDHC_SEND_RELATIVE_ADDR | SDHC_CMD_RESPONSE); + rca = qtest_readl(qts, XILINX_ZYNQ_MMC_BA + SDHC_RSPREG0) >> 16; + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, rca << 16, 0, + SDHC_SELECT_DESELECT_CARD); + + return qts; +} + +static void write_pattern_to_image(size_t offset, size_t len) +{ + int fd; + int ret; + uint8_t *buf = g_malloc(len); + + for (size_t i = 0; i < len; i++) { + buf[i] = (i * 13 + 7) & 0xff; + } + + fd = open(sd_path, O_WRONLY); + g_assert_cmpint(fd, >=, 0); + ret = pwrite(fd, buf, len, offset); + close(fd); + g_assert_cmpint(ret, ==, len); + + g_free(buf); +} + +static void wait_for_block_count(QTestState *qts, uint16_t expected) +{ + int i; + + for (i = 0; i < 1000; i++) { + if (qtest_readw(qts, XILINX_ZYNQ_MMC_BA + SDHC_BLKCNT) == expected) { + return; + } + g_usleep(100); + } + + g_assert_cmpuint(qtest_readw(qts, XILINX_ZYNQ_MMC_BA + SDHC_BLKCNT), + ==, expected); +} + +/* + * SDMA stops at each boundary and requires software to update SYSAD + * to continue. The bug this test covers leaves DOING_READ/WRITE asserted, + * so the continuation SYSAD write is ignored and the transfer stalls. + */ +static void test_read_sdma_boundary_continue(void) +{ + QTestState *qts = setup_sd_card(); + uint8_t *actual = g_malloc0(XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + uint8_t *expected = g_malloc(XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + + write_pattern_to_image(0, XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + + for (size_t i = 0; i < XILINX_ZYNQ_SDMA_TRANSFER_SIZE; i++) { + expected[i] = (i * 13 + 7) & 0xff; + } + + qtest_writel(qts, XILINX_ZYNQ_DMA_ADDR, 0xdeadbeef); + qtest_writel(qts, XILINX_ZYNQ_DMA_ADDR + XILINX_ZYNQ_SDMA_BOUNDARY_SIZE, + 0xcafef00d); + + qtest_writel(qts, XILINX_ZYNQ_MMC_BA + SDHC_SYSAD, XILINX_ZYNQ_DMA_ADDR); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, + SDHC_MAKE_BLKSZ(XILINX_ZYNQ_SDMA_BOUNDARY_ARG, + XILINX_ZYNQ_BLK_SIZE), + XILINX_ZYNQ_SDMA_BLOCK_COUNT, 0, + SDHC_TRNS_DMA | SDHC_TRNS_MULTI | SDHC_TRNS_READ | + SDHC_TRNS_BLK_CNT_EN, + SDHC_READ_MULTIPLE_BLOCK | SDHC_CMD_DATA_PRESENT); + + wait_for_block_count(qts, 1); + + qtest_writew(qts, XILINX_ZYNQ_MMC_BA + SDHC_NORINTSTS, 0xffff); + qtest_writel(qts, XILINX_ZYNQ_MMC_BA + SDHC_SYSAD, + XILINX_ZYNQ_DMA_ADDR + XILINX_ZYNQ_SDMA_BOUNDARY_SIZE); + + wait_for_block_count(qts, 0); + + qtest_memread(qts, XILINX_ZYNQ_DMA_ADDR, actual, + XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + g_assert_cmpmem(actual, XILINX_ZYNQ_SDMA_TRANSFER_SIZE, + expected, XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + + g_free(expected); + g_free(actual); + qtest_quit(qts); +} + +static void drive_destroy(void) +{ + unlink(sd_path); + g_free(sd_path); +} + +static void drive_create(void) +{ + int fd; + int ret; + GError *error = NULL; + + fd = g_file_open_tmp("xilinx-zynq-sdhci.XXXXXX", &sd_path, &error); + if (fd == -1) { + fprintf(stderr, "unable to create sdhci file: %s\n", error->message); + g_error_free(error); + } + g_assert_nonnull(sd_path); + + ret = ftruncate(fd, XILINX_ZYNQ_TEST_IMAGE_SIZE); + g_assert_cmpint(ret, ==, 0); + close(fd); +} + +int main(int argc, char **argv) +{ + int ret; + + drive_create(); + + g_test_init(&argc, &argv, NULL); + qtest_add_func("xilinx-zynq-sdhci/sdma/read-boundary-continue", + test_read_sdma_boundary_continue); + + ret = g_test_run(); + drive_destroy(); + return ret; +} -- 2.43.0
