[PATCH v1 3/3] tests/qtest: add K230 SDHCI tests
Xin Xie <[email protected]> Wed, 29 Jul 2026 18:59:03 +0800
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
Add qtests covering the K230-specific SDHCI register layout, capability masking, preset values, extension pointers, PHY power status and per-controller vendor register storage. Verify card detection and interrupt delivery through the K230 PLIC. Exercise basic PIO reads and writes with an attached SD card image and check that written data reaches the backing file. Signed-off-by: Xin Xie <[email protected]> --- MAINTAINERS | 1 + tests/qtest/k230-sdhci-test.c | 214 ++++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 5 +- 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/k230-sdhci-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 06285352c4..99c4c3294b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1831,6 +1831,7 @@ F: include/hw/riscv/k230.h F: include/hw/watchdog/k230_wdt.h F: include/hw/sd/k230_sdhci.h F: tests/qtest/k230-wdt-test.c +F: tests/qtest/k230-sdhci-test.c RX Machines ----------- diff --git a/tests/qtest/k230-sdhci-test.c b/tests/qtest/k230-sdhci-test.c new file mode 100644 index 0000000000..5eedb130c3 --- /dev/null +++ b/tests/qtest/k230-sdhci-test.c @@ -0,0 +1,214 @@ +/* + * QTest testcase for the Kendryte K230 SDHCI controllers + * + * Copyright (c) 2026 Xin Xie <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "qemu/units.h" +#include "libqtest.h" +#include "libqos/sdhci-cmd.h" +#include "hw/sd/k230_sdhci.h" +#include "hw/sd/sdhci-internal.h" + +#define K230_SD0_BASE 0x91580000 +#define K230_SD1_BASE 0x91581000 +#define K230_PLIC_PENDING 0xf00001000ULL +#define K230_SD1_IRQ 144 + +#define K230_SD_TEST_IMAGE_SIZE (2 * MiB) +#define K230_SD_TEST_BLOCK_SIZE 512 + +static void test_registers(void) +{ + QTestState *qts = qtest_init("-machine k230"); + uint64_t capabilities; + uint64_t expected = K230_SDHCI_CAPAREG_RESET; + + /* Only capabilities unsupported by the QEMU model are removed. */ + expected = FIELD_DP64(expected, SDHC_CAPAB, ASYNC_INT, 0); + expected = FIELD_DP64(expected, SDHC_CAPAB, TIMER_RETUNING, 0); + expected = FIELD_DP64(expected, SDHC_CAPAB, RETUNING_MODE, 0); + expected = FIELD_DP64(expected, SDHC_CAPAB, ADMA3, 0); + capabilities = qtest_readq(qts, K230_SD0_BASE + SDHC_CAPAB); + g_assert_cmphex(capabilities, ==, expected); + + /* Preset Value registers use the TRM reset values and are read-only. */ + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_PRESET_INIT) == + K230_SDHCI_PRESET_INIT_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + + K230_SDHCI_PRESET_DEFAULT_SPEED) == + K230_SDHCI_PRESET_DEFAULT_SPEED_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + + K230_SDHCI_PRESET_HIGH_SPEED) == + K230_SDHCI_PRESET_HIGH_SPEED_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_PRESET_SDR12) == + K230_SDHCI_PRESET_SDR12_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_PRESET_SDR25) == + K230_SDHCI_PRESET_SDR25_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_PRESET_SDR50) == + K230_SDHCI_PRESET_SDR50_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_PRESET_SDR104) == + K230_SDHCI_PRESET_SDR104_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_PRESET_DDR50) == + K230_SDHCI_PRESET_DDR50_RESET); + + qtest_writew(qts, K230_SD0_BASE + K230_SDHCI_PRESET_SDR50, 0xffff); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_PRESET_SDR50) == + K230_SDHCI_PRESET_SDR50_RESET); + + /* Extension-area pointers use their documented read-only reset values. */ + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_P_UHS2_SETTINGS) == + K230_SDHCI_P_UHS2_SETTINGS_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_P_UHS2_HOST_CAPAB) == + K230_SDHCI_P_UHS2_HOST_CAPAB_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_P_UHS2_TEST) == + K230_SDHCI_P_UHS2_TEST_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_P_EMBEDDED_CNTRL) == + K230_SDHCI_P_EMBEDDED_CNTRL_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_P_VENDOR_AREA1) == + K230_SDHCI_P_VENDOR_AREA1_RESET); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_P_VENDOR_AREA2) == + K230_SDHCI_P_VENDOR_AREA2_RESET); + + qtest_writew(qts, K230_SD0_BASE + K230_SDHCI_P_VENDOR_AREA1, 0xffff); + g_assert(qtest_readw(qts, K230_SD0_BASE + K230_SDHCI_P_VENDOR_AREA1) == + K230_SDHCI_P_VENDOR_AREA1_RESET); + + /* The virtual PHY is always ready and software cannot clear PWRGOOD. */ + g_assert((qtest_readl(qts, K230_SD0_BASE + K230_SDHCI_PHY_CNFG) & + K230_SDHCI_PHY_CNFG_PWRGOOD) == K230_SDHCI_PHY_CNFG_PWRGOOD); + qtest_writel(qts, K230_SD0_BASE + K230_SDHCI_PHY_CNFG, 0); + g_assert(qtest_readl(qts, K230_SD0_BASE + K230_SDHCI_PHY_CNFG) == + K230_SDHCI_PHY_CNFG_PWRGOOD); + + /* Vendor storage is writable and private to each controller instance. */ + qtest_writel(qts, K230_SD0_BASE + K230_SDHCI_P_VENDOR_AREA1_RESET, + 0x89abcdef); + g_assert(qtest_readl(qts, K230_SD0_BASE + + K230_SDHCI_P_VENDOR_AREA1_RESET) == + 0x89abcdef); + g_assert(qtest_readl(qts, K230_SD1_BASE + + K230_SDHCI_P_VENDOR_AREA1_RESET) == 0); + + qtest_quit(qts); +} + +static void test_card_detect(void) +{ + g_autofree char *path = NULL; + int fd = g_file_open_tmp("k230-sd-XXXXXX.img", &path, NULL); + QTestState *qts; + + g_assert(fd >= 0); + g_assert(ftruncate(fd, K230_SD_TEST_IMAGE_SIZE) == 0); + close(fd); + + qts = qtest_initf("-machine k230 " + "-drive if=sd,index=1,format=raw,file=%s", path); + + /* Card insertion becomes visible after the generic insertion delay. */ + qtest_clock_step(qts, NANOSECONDS_PER_SECOND); + g_assert((qtest_readl(qts, K230_SD1_BASE + SDHC_PRNSTS) & + SDHC_CARD_PRESENT) == SDHC_CARD_PRESENT); + g_assert((qtest_readl(qts, K230_SD0_BASE + SDHC_PRNSTS) & + SDHC_CARD_PRESENT) == 0); + + /* Route a command-complete event through SDHCI to the K230 PLIC. */ + qtest_writew(qts, K230_SD1_BASE + SDHC_NORINTSTSEN, + SDHC_NISEN_CMDCMP); + qtest_writew(qts, K230_SD1_BASE + SDHC_NORINTSIGEN, + SDHC_NISEN_CMDCMP); + g_assert((qtest_readw(qts, K230_SD1_BASE + SDHC_NORINTSTSEN) & + SDHC_NISEN_CMDCMP) != 0); + g_assert((qtest_readw(qts, K230_SD1_BASE + SDHC_NORINTSIGEN) & + SDHC_NISEN_CMDCMP) != 0); + qtest_writew(qts, K230_SD1_BASE + SDHC_CLKCON, + SDHC_CLOCK_INT_EN | SDHC_CLOCK_SDCLK_EN); + qtest_writew(qts, K230_SD1_BASE + SDHC_CMDREG, + SDHC_APP_CMD | SDHC_CMD_RESPONSE); + g_assert((qtest_readw(qts, K230_SD1_BASE + SDHC_NORINTSTS) & + SDHC_NIS_CMDCMP) != 0); + g_assert((qtest_readl(qts, K230_PLIC_PENDING + + K230_SD1_IRQ / 32 * sizeof(uint32_t)) & + BIT(K230_SD1_IRQ % 32)) != 0); + + qtest_quit(qts); + unlink(path); +} + +static void k230_sdhci_select_card(QTestState *qts) +{ + uint16_t rca; + + /* Reset the controller and enable its internal and SD clocks. */ + qtest_writeb(qts, K230_SD1_BASE + SDHC_SWRST, SDHC_RESET_ALL); + qtest_writew(qts, K230_SD1_BASE + SDHC_CLKCON, + SDHC_CLOCK_INT_EN | SDHC_CLOCK_INT_STABLE | + SDHC_CLOCK_SDCLK_EN); + + /* Run the SD identification sequence and select the assigned RCA. */ + sdhci_cmd_regs(qts, K230_SD1_BASE, 0, 0, 0, 0, SDHC_APP_CMD); + sdhci_cmd_regs(qts, K230_SD1_BASE, 0, 0, 0x41200000, 0, 41 << 8); + sdhci_cmd_regs(qts, K230_SD1_BASE, 0, 0, 0, 0, SDHC_ALL_SEND_CID); + sdhci_cmd_regs(qts, K230_SD1_BASE, 0, 0, 0, 0, + SDHC_SEND_RELATIVE_ADDR | SDHC_CMD_RESPONSE); + rca = qtest_readl(qts, K230_SD1_BASE + SDHC_RSPREG0) >> 16; + sdhci_cmd_regs(qts, K230_SD1_BASE, 0, 0, rca << 16, 0, + SDHC_SELECT_DESELECT_CARD); +} + +static void test_card_io(void) +{ + static const char read_data[] = "K230 SDHCI read"; + static const char write_data[] = "K230 SDHCI write"; + g_autofree char *path = NULL; + char buffer[K230_SD_TEST_BLOCK_SIZE]; + int fd = g_file_open_tmp("k230-sd-XXXXXX.img", &path, NULL); + QTestState *qts; + + /* Seed block zero so the controller read can be checked independently. */ + g_assert(fd >= 0); + g_assert(ftruncate(fd, K230_SD_TEST_IMAGE_SIZE) == 0); + g_assert(pwrite(fd, read_data, sizeof(read_data), 0) == sizeof(read_data)); + close(fd); + + qts = qtest_initf("-machine k230 " + "-drive if=sd,index=1,format=raw,auto-read-only=off," + "file=%s", path); + k230_sdhci_select_card(qts); + + /* Read block zero through the SDHCI PIO data port. */ + memset(buffer, 0, sizeof(buffer)); + g_assert(sdhci_read_cmd(qts, K230_SD1_BASE, buffer, + sizeof(read_data)) == sizeof(read_data)); + g_assert(memcmp(buffer, read_data, sizeof(read_data)) == 0); + + /* Write through SDHCI, then verify the data reached the backing image. */ + sdhci_write_cmd(qts, K230_SD1_BASE, write_data, sizeof(write_data), + K230_SD_TEST_BLOCK_SIZE); + qtest_quit(qts); + + fd = open(path, O_RDONLY); + g_assert(fd >= 0); + memset(buffer, 0, sizeof(buffer)); + g_assert(pread(fd, buffer, sizeof(write_data), 0) == sizeof(write_data)); + close(fd); + g_assert(memcmp(buffer, write_data, sizeof(write_data)) == 0); + + unlink(path); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-sdhci/registers", test_registers); + qtest_add_func("/k230-sdhci/card-detect", test_card_detect); + qtest_add_func("/k230-sdhci/card-io", test_card_io); + + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 56ff860e21..0600b1e807 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -297,7 +297,10 @@ qtests_riscv64 = ['riscv-csr-test'] + \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? ['iommu-riscv-test'] : []) + \ - (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : []) + (config_all_devices.has_key('CONFIG_K230') ? [ + 'k230-wdt-test', + 'k230-sdhci-test', + ] : []) qtests_hexagon = ['boot-serial-test'] -- 2.43.0