Re: [PATCH v1 3/3] tests/qtest: add K230 SDHCI tests
Daniel Henrique Barboza <[email protected]>
| Newsgroups | org.nongnu.qemu-devel,org.nongnu.qemu-riscv |
|---|---|
| Message-ID | <[email protected]> |
On 7/29/2026 7:59 AM, Xin Xie wrote: > 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]> > --- Reviewed-by: Daniel Henrique Barboza <[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'] >