static void sdhci_do_adma(SDHCIState *s) { unsigned int n, begin, length; const uint16_t block_size = s->blksize & 0x0fff; ADMADescr dscr; int i; for (i = 0; i < SDHC_ADMA_DESCS_PER_DELAY; ++i) { s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH; get_adma_description(s, &dscr); DPRINT_L2("ADMA loop: addr=" TARGET_FMT_plx ", len=%d, attr=%x\n", dscr.addr, dscr.length, dscr.attr); if ((dscr.attr & SDHC_ADMA_ATTR_VALID) == 0) { /* Indicate that error occurred in ST_FDS state */ s->admaerr &= ~SDHC_ADMAERR_STATE_MASK; s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS; /* Generate ADMA error interrupt */ if (s->errintstsen & SDHC_EISEN_ADMAERR) { s->errintsts |= SDHC_EIS_ADMAERR; s->norintsts |= SDHC_NIS_ERR; } sdhci_update_irq(s); return; } length = dscr.length ? dscr.length : 65536; switch (dscr.attr & SDHC_ADMA_ATTR_ACT_MASK) { case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */ if (s->trnmod & SDHC_TRNS_READ) { while (length) { if (s->data_count == 0) { for (n = 0; n < block_size; n++) { s->fifo_buffer[n] = sd_read_data(s->card); } } begin = s->data_count; if ((length + begin) < block_size) { s->data_count = length + begin; length = 0; } else { s->data_count = block_size; length -= block_size - begin; } dma_memory_write(&address_space_memory, dscr.addr, &s->fifo_buffer[begin], s->data_count - begin); dscr.addr += s->data_count - begin; if (s->data_count == block_size) { s->data_count = 0; if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { s->blkcnt--; if (s->blkcnt == 0) { break; } } } } } else { while (length) { begin = s->data_count; if ((length + begin) < block_size) { s->data_count = length + begin; length = 0; } else { s->data_count = block_size; length -= block_size - begin; } dma_memory_read(&address_space_memory, dscr.addr, &s->fifo_buffer[begin], s->data_count - begin); dscr.addr += s->data_count - begin; if (s->data_count == block_size) { for (n = 0; n < block_size; n++) { sd_write_data(s->card, s->fifo_buffer[n]); } s->data_count = 0; if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { s->blkcnt--; if (s->blkcnt == 0) { break; } } } } } s->admasysaddr += dscr.incr; break; case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */ s->admasysaddr = dscr.addr; DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", s->admasysaddr); break; default: s->admasysaddr += dscr.incr; break; } if (dscr.attr & SDHC_ADMA_ATTR_INT) { DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr); if (s->norintstsen & SDHC_NISEN_DMA) { s->norintsts |= SDHC_NIS_DMA; } sdhci_update_irq(s); } /* ADMA transfer terminates if blkcnt == 0 or by END attribute */ if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) || (dscr.attr & SDHC_ADMA_ATTR_END)) { DPRINT_L2("ADMA transfer completed\n"); if (length || ((dscr.attr & SDHC_ADMA_ATTR_END) && (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && s->blkcnt != 0)) { ERRPRINT("SD/MMC host ADMA length mismatch\n"); s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH | SDHC_ADMAERR_STATE_ST_TFR; if (s->errintstsen & SDHC_EISEN_ADMAERR) { ERRPRINT("Set ADMA error flag\n"); s->errintsts |= SDHC_EIS_ADMAERR; s->norintsts |= SDHC_NIS_ERR; } sdhci_update_irq(s); } SDHCI_GET_CLASS(s)->end_data_transfer(s); return; } } /* we have unfinished business - reschedule to continue ADMA */ timer_mod(s->transfer_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY); }