On Tue, Jul 14, 2026 at 12:19 AM Tao Ding <[email protected]> wrote:
>
> 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 | 25 ++++++++++++++++++++++++-
> include/hw/sd/sdhci.h | 2 ++
> 2 files changed, 26 insertions(+), 1 deletion(-)
>
> diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
> index c86dfa281f..e614afa346 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);
> }
> }
> @@ -1164,6 +1167,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 +1189,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 +1468,13 @@ 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 const VMStateDescription sdhci_pending_insert_vmstate = {
> .name = "sdhci/pending-insert",
> .version_id = 1,
> @@ -1475,6 +1486,17 @@ 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()
> + },
> +};
Need to add a pre_load/post_load pair to ensure
s->sdma_boundary_paused can be correctly migrated.
The pre_load() should set the state to false and the post_load()
should reconstruct the state based on the migrated register values
when the migrated stream does not contain the state.
static int sdhci_pre_load(void *opaque)
{
SDHCIState *s = opaque;
s->sdma_boundary_paused = false;
return 0;
}
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;
}
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;
}
> +
> const VMStateDescription sdhci_vmstate = {
> .name = "sdhci",
> .version_id = 1,
> @@ -1512,6 +1534,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;
>
Regards,
Bin