During SDMA transfers, the controller raises an interrupt at buffer boundaries to request a system address update. The host driver will rewrite the SDHC_SYSAD register. (according to PartA2_SD_Host_Controller_Simplified_Specification_Ver2.00.pdf section 2.2.1) However, the current code will ignore write operations to SDHC_SYSAD.
To fix this bug, when SDMA encounters a boundary, a state is set to record it. In this state, SDHC_SYSAD can be written. In other cases, it is still protected by TRANSFERRING_DATA. Meanwhile, a subsection has been added for migration. Suggested-by: Bin Meng <[email protected]> Signed-off-by: Tao Ding <[email protected]> --- This bug may be encountered when using uboot with SDMA functionality, and can be reproduced by following the steps below. Uboot test: 1. Prepare zImage, uboot and rootfs.cpio.gz, make sure uboot config (MMC_SDHCI_SDMA=y). for more detail, can obtained "https://lore.kernel.org/qemu-devel/[email protected]/T/#mcdf231e6bd762903f8393bf93253cbcb12ececcf" 2. ./qemu-system-aarch64 -M xilinx-zynq-a9 -machine boot-mode=sd -m 1024 -display none -serial null -serial stdio -monitor none \ -device loader,file=u-boot-dtb.bin,addr=0x04000000,cpu-num=0 \ -drive file=zynq-sd.img,format=raw,if=sd -D qemu.log After applying the fix, success load sd card: U-Boot 2026.07-rc4-g1e80ee41441c (Jun 15 2026 - 19:05:05 +0800) Model: Xilinx ZC702 board DRAM: ECC disabled 1 GiB Core: 33 devices, 21 uclasses, devicetree: board Flash: 0 Bytes NAND: 0 MiB MMC: mmc@e0100000: 0 Loading Environment from FAT... *** Error - No Valid Environment Area found *** Warning - bad env area, using default environment In: serial@e0001000 Out: serial@e0001000 Err: serial@e0001000 Net: ZYNQ GEM: e000b000, mdio bus e000b000, phyaddr 7, interface rgmii-id Warning: ethernet@e000b000 (eth0) using random MAC address - 4a:63:5a:a4:d3:6b eth0: ethernet@e000b000 Hit any key to stop autoboot: 0 Zynq> fatload mmc 0 ${kernel_addr_r} zImage 10838528 bytes read in 4316 ms (2.4 MiB/s) Zynq> fatload mmc 0 ${ramdisk_addr_r} rootfs-arm32.cpio.gz 406531 bytes read in 199 ms (1.9 MiB/s) Zynq> setenv ramdisk_size ${filesize} Zynq> fatload mmc 0 ${fdt_addr_r} zynq-zc702.dtb 15754 bytes read in 21 ms (732.4 KiB/s) Zynq> setenv bootargs console=ttyPS0,115200 earlycon ignore_loglevel rdinit=/init Zynq> bootz ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r} Kernel image @ 0x2000000 [ 0x000000 - 0xa56200 ] ## Flattened Device Tree blob at 01f00000 Booting using the fdt blob at 0x1f00000 Working FDT set to 1f00000 Loading Ramdisk to 2ff9c000, end 2ffff403 ... OK Loading Device Tree to 2ff95000, end 2ff9bd89 ... OK Working FDT set to 2ff95000 Starting kernel ... hw/sd/sdhci.c | 54 ++++++++++++++++++++++++++++++++++++++++++- include/hw/sd/sdhci.h | 2 ++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index c86dfa281f..55a4abdd72 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -307,6 +307,7 @@ static void sdhci_reset(SDHCIState *s) s->data_count = 0; s->stopped_state = sdhc_not_stopped; s->pending_insert_state = false; + s->sdma_boundary_paused = false; if (object_dynamic_cast(OBJECT(s), TYPE_FSL_ESDHC_BE) || object_dynamic_cast(OBJECT(s), TYPE_FSL_ESDHC_LE)) { s->norintstsen = 0x013f; @@ -414,6 +415,7 @@ static void sdhci_end_transfer(SDHCIState *s) s->norintsts |= SDHC_NIS_TRSCMP; } + s->sdma_boundary_paused = false; sdhci_update_irq(s); } @@ -681,6 +683,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) if (s->blkcnt == 0) { sdhci_end_transfer(s); } else { + s->sdma_boundary_paused = true; sdhci_update_irq(s); } } @@ -717,6 +720,15 @@ static void sdhci_sdma_transfer(SDHCIState *s) } } +static bool sdhci_sdma_transfer_active(SDHCIState *s) +{ + return TRANSFERRING_DATA(s->prnsts) && + (s->trnmod & SDHC_TRNS_DMA) && + s->blkcnt && + (s->blksize & BLOCK_SIZE_MASK) && + SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA; +} + typedef struct ADMADescr { hwaddr addr; uint16_t length; @@ -1164,6 +1176,7 @@ static inline void sdhci_reset_write(SDHCIState *s, uint8_t value) SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE); s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ); s->stopped_state = sdhc_not_stopped; + s->sdma_boundary_paused = false; s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY | SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP); break; @@ -1185,7 +1198,7 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) switch (offset & ~0x3) { case SDHC_SYSAD: - if (!TRANSFERRING_DATA(s->prnsts)) { + if (!TRANSFERRING_DATA(s->prnsts) || s->sdma_boundary_paused) { s->sdmasysad = (s->sdmasysad & mask) | value; MASKED_WRITE(s->sdmasysad, mask, value); /* Writing to last byte of sdmasysad might trigger transfer */ @@ -1464,6 +1477,31 @@ static bool sdhci_pending_insert_vmstate_needed(void *opaque) return s->pending_insert_state; } +static bool sdhci_sdma_boundary_paused_vmstate_needed(void *opaque) +{ + SDHCIState *s = opaque; + + return s->sdma_boundary_paused; +} + +static int sdhci_pre_load(void *opaque) +{ + SDHCIState *s = opaque; + + s->sdma_boundary_paused = false; + return 0; +} + +static int sdhci_post_load(void *opaque, int version_id) +{ + SDHCIState *s = opaque; + + if (!s->sdma_boundary_paused) { + s->sdma_boundary_paused = sdhci_sdma_transfer_active(s); + } + return 0; +} + static const VMStateDescription sdhci_pending_insert_vmstate = { .name = "sdhci/pending-insert", .version_id = 1, @@ -1475,10 +1513,23 @@ static const VMStateDescription sdhci_pending_insert_vmstate = { }, }; +static const VMStateDescription sdhci_sdma_boundary_paused_vmstate = { + .name = "sdhci/sdma_boundary_paused", + .version_id = 1, + .minimum_version_id = 1, + .needed = sdhci_sdma_boundary_paused_vmstate_needed, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(sdma_boundary_paused, SDHCIState), + VMSTATE_END_OF_LIST() + }, +}; + const VMStateDescription sdhci_vmstate = { .name = "sdhci", .version_id = 1, .minimum_version_id = 1, + .pre_load = sdhci_pre_load, + .post_load = sdhci_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT32(sdmasysad, SDHCIState), VMSTATE_UINT16(blksize, SDHCIState), @@ -1512,6 +1563,7 @@ const VMStateDescription sdhci_vmstate = { }, .subsections = (const VMStateDescription * const []) { &sdhci_pending_insert_vmstate, + &sdhci_sdma_boundary_paused_vmstate, NULL }, }; diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index a9da6203fc..c485277744 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -103,6 +103,8 @@ struct SDHCIState { * to be protected. Set wp_inverted to invert the signal. */ bool wp_inverted; + /* Indicate that SDMA transfer is paused due to hitting the boundary */ + bool sdma_boundary_paused; }; typedef struct SDHCIState SDHCIState; -- 2.43.0
