Am 3. Oktober 2026 17:01:59 UTC schrieb Bin Meng <[email protected]>:
>The SDHCI model processes the first ADMA descriptor batch inside the
>command MMIO write, then resumes pending batches before every later
>SDHCI MMIO read or write. This makes register access perform bulk DMA
>work and can delay a status read even when completion bits are set.
>
>On the Icicle Kit, U-Boot uses ADMA2-64 and CMD18 to load a roughly
>19 MiB FIT image from SD. It can report:
>
>  Timeout for status update: 00000001 00000001
>  Timeout for status update: 00000003 00000001
>
>The status already contains the requested Command Complete bit, and may
>also contain Transfer Complete. However, reading the interrupt status
>first executes a pending ADMA batch. The MMIO read can therefore return
>after U-Boot has reached its one-second polling deadline.
>
>Turn the existing transfer timer into an ADMA-only engine. Starting an
>ADMA command arms the engine directly, and each timer callback runs one
>bounded batch before yielding. MMIO reads and writes no longer perform
>or schedule ADMA work. Keep PIO and SDMA synchronous.
>
>Cancel the engine and clear ADMA activity and error state on DATA or ALL
>reset.
>
>Signed-off-by: Bin Meng <[email protected]>
>---
>
>(no changes since v1)

Hi Bin,

I've accidentally left some comments on v2 after you sent v3: 
https://lore.kernel.org/qemu-devel/[email protected]/
 . Generally patch LGTM.

Best regards,
Bernhard

>
> hw/sd/sdhci.c | 143 ++++++++++++++++++++++++++++++--------------------
> 1 file changed, 87 insertions(+), 56 deletions(-)
>
>diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
>index bd74c60f72..14c312a691 100644
>--- a/hw/sd/sdhci.c
>+++ b/hw/sd/sdhci.c
>@@ -277,6 +277,7 @@ static void sdhci_set_inserted(DeviceState *dev, bool 
>level)
>             }
>         } else {
>             s->prnsts = 0x1fa0000;
>+            timer_del(s->transfer_timer);
>             s->pwrcon &= ~SDHC_POWER_ON;
>             s->clkcon &= ~SDHC_CLOCK_SDCLK_EN;
>             if (s->norintstsen & SDHC_NISEN_REMOVE) {
>@@ -348,7 +349,34 @@ static void sdhci_poweron_reset(DeviceState *dev)
>     }
> }
> 
>-static void sdhci_data_transfer(void *opaque);
>+static void sdhci_data_transfer(SDHCIState *s);
>+static void sdhci_adma_engine(void *opaque);
>+
>+static void sdhci_set_transfer_active(SDHCIState *s)
>+{
>+    s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
>+    if (s->trnmod & SDHC_TRNS_READ) {
>+        s->prnsts |= SDHC_DOING_READ;
>+    } else {
>+        s->prnsts |= SDHC_DOING_WRITE;
>+    }
>+}
>+
>+static void sdhci_schedule_adma(SDHCIState *s)
>+{
>+    timer_mod(s->transfer_timer,
>+              qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY);
>+}
>+
>+static bool sdhci_adma_transfer_active(SDHCIState *s)
>+{
>+    return TRANSFERRING_DATA(s->prnsts) &&
>+           (s->trnmod & SDHC_TRNS_DMA) &&
>+           (s->cmdreg & SDHC_CMD_DATA_PRESENT) &&
>+           sdhci_get_dma_type(s) != SDHC_CTRL_SDMA &&
>+           !(s->admaerr & SDHC_ADMAERR_STATE_MASK) &&
>+           !(s->errintsts & SDHC_EIS_ADMAERR);
>+}
> 
> #define BLOCK_SIZE_MASK (4 * KiB - 1)
> 
>@@ -405,7 +433,13 @@ static void sdhci_send_command(SDHCIState *s)
>     if (!timeout && (s->blksize & BLOCK_SIZE_MASK) &&
>         (s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
>         s->data_count = 0;
>-        sdhci_data_transfer(s);
>+        if ((s->trnmod & SDHC_TRNS_DMA) &&
>+            sdhci_get_dma_type(s) != SDHC_CTRL_SDMA) {
>+            sdhci_set_transfer_active(s);
>+            sdhci_schedule_adma(s);
>+        } else {
>+            sdhci_data_transfer(s);
>+        }
>     }
> }
> 
>@@ -850,7 +884,7 @@ static void get_adma_description(SDHCIState *s, ADMADescr 
>*dscr)
> 
> /* Advanced DMA data transfer */
> 
>-static void sdhci_do_adma(SDHCIState *s)
>+static void sdhci_adma_run_batch(SDHCIState *s)
> {
>     unsigned int begin, length;
>     const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
>@@ -956,15 +990,17 @@ static void sdhci_do_adma(SDHCIState *s)
>             }
>             if (res != MEMTX_OK) {
>                 s->data_count = 0;
>+                s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
>+                s->admaerr |= SDHC_ADMAERR_STATE_ST_TFR;
>                 if (s->errintstsen & SDHC_EISEN_ADMAERR) {
>                     trace_sdhci_error("Set ADMA error flag");
>                     s->errintsts |= SDHC_EIS_ADMAERR;
>                     s->norintsts |= SDHC_NIS_ERR;
>                 }
>                 sdhci_update_irq(s);
>-            } else {
>-                s->admasysaddr += dscr.incr;
>+                return;
>             }
>+            s->admasysaddr += dscr.incr;
>             break;
>         case SDHC_ADMA_ATTR_ACT_LINK:   /* link to next descriptor table */
>             s->admasysaddr = dscr.addr;
>@@ -1012,50 +1048,52 @@ static void sdhci_do_adma(SDHCIState *s)
>     }
> 
>     /* we have unfinished business - reschedule to continue ADMA */
>-    timer_mod(s->transfer_timer,
>-                   qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 
>SDHC_TRANSFER_DELAY);
>+    sdhci_schedule_adma(s);
> }
> 
>-/* Perform data transfer according to controller configuration */
>-
>-static void sdhci_data_transfer(void *opaque)
>+/* Run one independently scheduled, quota-bounded ADMA batch */
>+static void sdhci_adma_engine(void *opaque)
> {
>     SDHCIState *s = (SDHCIState *)opaque;
> 
>-    if (s->trnmod & SDHC_TRNS_DMA) {
>-        switch (sdhci_get_dma_type(s)) {
>-        case SDHC_CTRL_SDMA:
>-            sdhci_sdma_transfer(s);
>-            break;
>-        case SDHC_CTRL_ADMA1_32:
>-            if (!(s->capareg & R_SDHC_CAPAB_ADMA1_MASK)) {
>-                trace_sdhci_error("ADMA1 not supported");
>-                break;
>-            }
>+    if (!sdhci_adma_transfer_active(s)) {
>+        return;
>+    }
> 
>-            sdhci_do_adma(s);
>-            break;
>-        case SDHC_CTRL_ADMA2_32:
>-            if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) {
>-                trace_sdhci_error("ADMA2 not supported");
>-                break;
>-            }
>+    switch (sdhci_get_dma_type(s)) {
>+    case SDHC_CTRL_ADMA1_32:
>+        if (!(s->capareg & R_SDHC_CAPAB_ADMA1_MASK)) {
>+            trace_sdhci_error("ADMA1 not supported");
>+            return;
>+        }
>+        break;
>+    case SDHC_CTRL_ADMA2_32:
>+        if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) {
>+            trace_sdhci_error("ADMA2 not supported");
>+            return;
>+        }
>+        break;
>+    case SDHC_CTRL_ADMA2_64:
>+        if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) ||
>+            !(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) {
>+            trace_sdhci_error("64 bit ADMA not supported");
>+            return;
>+        }
>+        break;
>+    default:
>+        trace_sdhci_error("Unsupported ADMA type");
>+        return;
>+    }
> 
>-            sdhci_do_adma(s);
>-            break;
>-        case SDHC_CTRL_ADMA2_64:
>-            if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) ||
>-                    !(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) {
>-                trace_sdhci_error("64 bit ADMA not supported");
>-                break;
>-            }
>+    sdhci_adma_run_batch(s);
>+}
> 
>-            sdhci_do_adma(s);
>-            break;
>-        default:
>-            trace_sdhci_error("Unsupported DMA type");
>-            break;
>-        }
>+/* Perform synchronous PIO or SDMA data transfer */
>+static void sdhci_data_transfer(SDHCIState *s)
>+{
>+    if (s->trnmod & SDHC_TRNS_DMA) {
>+        assert(sdhci_get_dma_type(s) == SDHC_CTRL_SDMA);
>+        sdhci_sdma_transfer(s);
>     } else {
>         if ((s->trnmod & SDHC_TRNS_READ) && sdbus_data_ready(&s->sdbus)) {
>             s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
>@@ -1098,21 +1136,11 @@ sdhci_buff_access_is_sequential(SDHCIState *s, 
>unsigned byte_num)
>     return true;
> }
> 
>-static void sdhci_resume_pending_transfer(SDHCIState *s)
>-{
>-    timer_del(s->transfer_timer);
>-    sdhci_data_transfer(s);
>-}
>-
> static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size)
> {
>     SDHCIState *s = (SDHCIState *)opaque;
>     uint32_t ret = 0;
> 
>-    if (timer_pending(s->transfer_timer)) {
>-        sdhci_resume_pending_transfer(s);
>-    }
>-
>     switch (offset & ~0x3) {
>     case SDHC_SYSAD:
>         ret = s->sdmasysad;
>@@ -1235,7 +1263,10 @@ static inline void sdhci_reset_write(SDHCIState *s, 
>uint8_t value)
>         s->norintsts &= ~SDHC_NIS_CMDCMP;
>         break;
>     case SDHC_RESET_DATA:
>+        timer_del(s->transfer_timer);
>         s->data_count = 0;
>+        s->admaerr = 0;
>+        s->errintsts &= ~SDHC_EIS_ADMAERR;
>         s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE |
>                 SDHC_DOING_READ | SDHC_DOING_WRITE |
>                 SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE);
>@@ -1244,6 +1275,10 @@ static inline void sdhci_reset_write(SDHCIState *s, 
>uint8_t value)
>         s->sdma_boundary_paused = false;
>         s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
>                 SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
>+        if (!s->errintsts) {
>+            s->norintsts &= ~SDHC_NIS_ERR;
>+        }
>+        sdhci_update_irq(s);
>         break;
>     }
> }
>@@ -1257,10 +1292,6 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, 
>unsigned size)
>     uint32_t value = val;
>     value <<= shift;
> 
>-    if (timer_pending(s->transfer_timer)) {
>-        sdhci_resume_pending_transfer(s);
>-    }
>-
>     switch (offset & ~0x3) {
>     case SDHC_SYSAD:
>         if (!TRANSFERRING_DATA(s->prnsts) ||
>@@ -1521,7 +1552,7 @@ void sdhci_initfn(SDHCIState *s)
>     s->insert_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
>                                    sdhci_raise_insertion_irq, s);
>     s->transfer_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
>-                                     sdhci_data_transfer, s);
>+                                     sdhci_adma_engine, s);
> 
>     s->io_ops = &sdhci_mmio_ops;
> }

Reply via email to