On Wed, Jul 8, 2026 at 1:05 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.
>
> Version_id change to 2 for migration.
>
> Suggested-by: Bin Meng <[email protected]>
> Signed-off-by: Tao Ding <[email protected]>
> ---
> The reviewer pointed out an issue in patch v3. When s->blkcnt decreases to 1 
> the logic goes to
> sdhci_sdma_transfer_single_block() where s->sdma_boundary_paused should be 
> cleared too.
>
> 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         | 14 +++++++++++---
>  include/hw/sd/sdhci.h |  2 ++
>  2 files changed, 13 insertions(+), 3 deletions(-)
>
> diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
> index c86dfa281f..dc774fdf21 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;
> @@ -620,6 +621,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState 
> *s)
>      }
>
>      s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
> +    s->sdma_boundary_paused = false;

I believe this line is not necessary too.

>      if (s->trnmod & SDHC_TRNS_READ) {
>          s->prnsts |= SDHC_DOING_READ;
>          while (s->blkcnt) {
> @@ -644,6 +646,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState 
> *s)
>                  s->data_count = 0;
>              }
>              if (page_aligned && boundary_count == 0) {
> +                s->sdma_boundary_paused = true;
>                  break;
>              }
>          }
> @@ -669,6 +672,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState 
> *s)
>                  }
>              }
>              if (page_aligned && boundary_count == 0) {
> +                s->sdma_boundary_paused = true;
>                  break;
>              }
>          }
> @@ -679,6 +683,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState 
> *s)
>      }
>
>      if (s->blkcnt == 0) {
> +        s->sdma_boundary_paused = false;
>          sdhci_end_transfer(s);
>      } else {
>          sdhci_update_irq(s);
> @@ -690,6 +695,7 @@ static void sdhci_sdma_transfer_single_block(SDHCIState 
> *s)
>  {
>      uint32_t datacnt = s->blksize & BLOCK_SIZE_MASK;
>
> +    s->sdma_boundary_paused = false;
>      if (s->trnmod & SDHC_TRNS_READ) {
>          sdbus_read_data(&s->sdbus, s->fifo_buffer, datacnt);
>          dma_memory_write(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt,
> @@ -1164,6 +1170,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 +1192,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 */
> @@ -1477,8 +1484,8 @@ static const VMStateDescription 
> sdhci_pending_insert_vmstate = {
>
>  const VMStateDescription sdhci_vmstate = {
>      .name = "sdhci",
> -    .version_id = 1,
> -    .minimum_version_id = 1,
> +    .version_id = 2,
> +    .minimum_version_id = 2,
>      .fields = (const VMStateField[]) {
>          VMSTATE_UINT32(sdmasysad, SDHCIState),
>          VMSTATE_UINT16(blksize, SDHCIState),
> @@ -1508,6 +1515,7 @@ const VMStateDescription sdhci_vmstate = {
>          VMSTATE_VBUFFER_UINT32(fifo_buffer, SDHCIState, 1, NULL, buf_maxsz),
>          VMSTATE_TIMER_PTR(insert_timer, SDHCIState),
>          VMSTATE_TIMER_PTR(transfer_timer, SDHCIState),
> +        VMSTATE_BOOL(sdma_boundary_paused, SDHCIState),
>          VMSTATE_END_OF_LIST()
>      },
>      .subsections = (const VMStateDescription * const []) {
> 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

Reply via email to