Am 5. Oktober 2026 10:46:08 UTC schrieb Bernhard Beschow <[email protected]>:
>
>
>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.
I've performed a dist upgrade inside a guest with ~600MB of download and ~2GB
of changed files with this patch cherry-picked on top of
https://lore.kernel.org/qemu-devel/[email protected]/ .
The guest rebooted successfully into the new environment. So:
Tested-by: Bernhard Beschow<[email protected]>
>
>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;
>> }