From: Tao Ding <[email protected]>

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]>
Reviewed-by: Bin Meng <[email protected]>
Message-ID: <[email protected]>
Signed-off-by: Philippe Mathieu-Daudé <[email protected]>
---
 include/hw/sd/sdhci.h |  2 ++
 hw/sd/sdhci.c         | 54 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 55 insertions(+), 1 deletion(-)

diff --git a/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h
index a9da6203fcb..c485277744e 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;
 
diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
index e58a6103970..02f09187272 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);
     }
 }
@@ -717,6 +720,15 @@ static void sdhci_sdma_transfer(SDHCIState *s)
     }
 }
 
+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;
+}
+
 typedef struct ADMADescr {
     hwaddr addr;
     uint16_t length;
@@ -1164,6 +1176,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 +1198,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 +1477,31 @@ 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 int sdhci_pre_load(void *opaque)
+{
+    SDHCIState *s = opaque;
+
+    s->sdma_boundary_paused = false;
+    return 0;
+}
+
+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;
+}
+
 static const VMStateDescription sdhci_pending_insert_vmstate = {
     .name = "sdhci/pending-insert",
     .version_id = 1,
@@ -1475,10 +1513,23 @@ 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()
+    },
+};
+
 const VMStateDescription sdhci_vmstate = {
     .name = "sdhci",
     .version_id = 1,
     .minimum_version_id = 1,
+    .pre_load = sdhci_pre_load,
+    .post_load = sdhci_post_load,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32(sdmasysad, SDHCIState),
         VMSTATE_UINT16(blksize, SDHCIState),
@@ -1512,6 +1563,7 @@ const VMStateDescription sdhci_vmstate = {
     },
     .subsections = (const VMStateDescription * const []) {
         &sdhci_pending_insert_vmstate,
+        &sdhci_sdma_boundary_paused_vmstate,
         NULL
     },
 };
-- 
2.53.0


Reply via email to