diff mbox series

[v2,28/42] esp: use FIFO for PDMA transfers between initiator and device

Message ID 20210209193018.31339-29-mark.cave-ayland@ilande.co.uk
State New
Headers show
Series esp: consolidate PDMA transfer buffers and other fixes | expand

Commit Message

Mark Cave-Ayland Feb. 9, 2021, 7:30 p.m. UTC
PDMA as implemented on the Quadra 800 uses DREQ to load data into the FIFO
up to a maximum of 16 bytes at a time. The MacOS toolbox ROM requires this
because it mixes FIFO and PDMA transfers whilst checking the FIFO status
and counter registers to ensure success.

Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
---
 hw/scsi/esp.c | 109 ++++++++++++++++++++++++++++++++++----------------
 1 file changed, 75 insertions(+), 34 deletions(-)

Comments

Laurent Vivier March 2, 2021, 10:02 p.m. UTC | #1
Le 09/02/2021 à 20:30, Mark Cave-Ayland a écrit :
> PDMA as implemented on the Quadra 800 uses DREQ to load data into the FIFO
> up to a maximum of 16 bytes at a time. The MacOS toolbox ROM requires this
> because it mixes FIFO and PDMA transfers whilst checking the FIFO status
> and counter registers to ensure success.
> 
> Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
> ---
>  hw/scsi/esp.c | 109 ++++++++++++++++++++++++++++++++++----------------
>  1 file changed, 75 insertions(+), 34 deletions(-)
> 
> diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c
> index b7ab5a5592..5dcd4cd651 100644
> --- a/hw/scsi/esp.c
> +++ b/hw/scsi/esp.c
> @@ -134,13 +134,8 @@ static void set_pdma(ESPState *s, enum pdma_origin_id origin)
>  
>  static uint8_t esp_pdma_read(ESPState *s)
>  {
> -    uint32_t dmalen = esp_get_tc(s);
>      uint8_t val;
>  
> -    if (dmalen == 0) {
> -        return 0;
> -    }
> -
>      switch (s->pdma_origin) {
>      case TI:
>          if (s->do_cmd) {
> @@ -160,10 +155,6 @@ static uint8_t esp_pdma_read(ESPState *s)
>          g_assert_not_reached();
>      }
>  
> -    s->ti_size--;
> -    dmalen--;
> -    esp_set_tc(s, dmalen);
> -
>      return val;
>  }
>  
> @@ -194,7 +185,6 @@ static void esp_pdma_write(ESPState *s, uint8_t val)
>          g_assert_not_reached();
>      }
>  
> -    s->ti_size++;
>      dmalen--;
>      esp_set_tc(s, dmalen);
>  }
> @@ -290,6 +280,7 @@ static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid)
>      s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
>      s->rregs[ESP_RSEQ] = SEQ_CD;
>      esp_raise_irq(s);
> +    esp_lower_drq(s);
>  }
>  
>  static void do_cmd(ESPState *s)
> @@ -447,28 +438,71 @@ static void esp_dma_done(ESPState *s)
>  static void do_dma_pdma_cb(ESPState *s)
>  {
>      int to_device = ((s->rregs[ESP_RSTAT] & 7) == STAT_DO);
> +    int len;
>  
>      if (s->do_cmd) {
>          s->ti_size = 0;
>          s->cmdlen = 0;
>          s->do_cmd = 0;
>          do_cmd(s);
> +        esp_lower_drq(s);
>          return;
>      }
> -    if (s->async_len == 0) {
> -        scsi_req_continue(s->current_req);
> -        /*
> -         * If there is still data to be read from the device then
> -         * complete the DMA operation immediately.  Otherwise defer
> -         * until the scsi layer has completed.
> -         */
> -        if (to_device || esp_get_tc(s) != 0 || s->ti_size == 0) {
> +
> +    if (to_device) {
> +        /* Copy FIFO data to device */
> +        len = MIN(s->ti_wptr, TI_BUFSZ);
> +        memcpy(s->async_buf, s->ti_buf, len);
> +        s->ti_wptr = 0;
> +        s->ti_rptr = 0;
> +        s->async_buf += len;
> +        s->async_len -= len;
> +        s->ti_size += len;
> +        if (s->async_len == 0) {
> +            scsi_req_continue(s->current_req);
>              return;
>          }
> -    }
>  
> -    /* Partially filled a scsi buffer. Complete immediately.  */
> -    esp_dma_done(s);
> +        if (esp_get_tc(s) == 0) {
> +            esp_lower_drq(s);
> +            esp_dma_done(s);
> +        }
> +
> +        return;
> +    } else {
> +        if (s->async_len == 0) {
> +            if (s->current_req) {
> +                scsi_req_continue(s->current_req);
> +            }
> +
> +            /*
> +             * If there is still data to be read from the device then
> +             * complete the DMA operation immediately.  Otherwise defer
> +             * until the scsi layer has completed.
> +             */
> +            if (esp_get_tc(s) != 0 || s->ti_size == 0) {
> +                return;
> +            }
> +        }
> +
> +        if (esp_get_tc(s) != 0) {
> +            /* Copy device data to FIFO */
> +            s->ti_wptr = 0;
> +            s->ti_rptr = 0;
> +            len = MIN(s->async_len, TI_BUFSZ);
> +            memcpy(s->ti_buf, s->async_buf, len);
> +            s->ti_wptr += len;
> +            s->async_buf += len;
> +            s->async_len -= len;
> +            s->ti_size -= len;
> +            esp_set_tc(s, esp_get_tc(s) - len);
> +            return;
> +        }
> +
> +        /* Partially filled a scsi buffer. Complete immediately.  */
> +        esp_lower_drq(s);
> +        esp_dma_done(s);
> +    }
>  }
>  
>  static void esp_do_dma(ESPState *s)
> @@ -511,7 +545,7 @@ static void esp_do_dma(ESPState *s)
>          if (s->dma_memory_read) {
>              s->dma_memory_read(s->dma_opaque, s->async_buf, len);
>          } else {
> -            set_pdma(s, ASYNC);
> +            set_pdma(s, TI);
>              s->pdma_cb = do_dma_pdma_cb;
>              esp_raise_drq(s);
>              return;
> @@ -520,9 +554,20 @@ static void esp_do_dma(ESPState *s)
>          if (s->dma_memory_write) {
>              s->dma_memory_write(s->dma_opaque, s->async_buf, len);
>          } else {
> -            set_pdma(s, ASYNC);
> +            /* Copy device data to FIFO */
> +            len = MIN(len, TI_BUFSZ - s->ti_wptr);
> +            memcpy(&s->ti_buf[s->ti_wptr], s->async_buf, len);
> +            s->ti_wptr += len;
> +            s->async_buf += len;
> +            s->async_len -= len;
> +            s->ti_size -= len;
> +            esp_set_tc(s, esp_get_tc(s) - len);
> +            set_pdma(s, TI);
>              s->pdma_cb = do_dma_pdma_cb;
>              esp_raise_drq(s);
> +
> +            /* Indicate transfer to FIFO is complete */
> +            s->rregs[ESP_RSTAT] |= STAT_TC;
>              return;
>          }
>      }
> @@ -548,6 +593,7 @@ static void esp_do_dma(ESPState *s)
>  
>      /* Partially filled a scsi buffer. Complete immediately.  */
>      esp_dma_done(s);
> +    esp_lower_drq(s);
>  }
>  
>  static void esp_report_command_complete(ESPState *s, uint32_t status)
> @@ -564,6 +610,7 @@ static void esp_report_command_complete(ESPState *s, uint32_t status)
>      s->status = status;
>      s->rregs[ESP_RSTAT] = STAT_ST;
>      esp_dma_done(s);
> +    esp_lower_drq(s);
>      if (s->current_req) {
>          scsi_req_unref(s->current_req);
>          s->current_req = NULL;
> @@ -606,6 +653,7 @@ void esp_transfer_data(SCSIRequest *req, uint32_t len)
>           * completion interrupt is deferred to here.
>           */
>          esp_dma_done(s);
> +        esp_lower_drq(s);
>      }
>  }
>  
> @@ -977,10 +1025,8 @@ static void sysbus_esp_pdma_write(void *opaque, hwaddr addr,
>          break;
>      }
>      dmalen = esp_get_tc(s);
> -    if (dmalen == 0 && s->pdma_cb) {
> -        esp_lower_drq(s);
> +    if (dmalen == 0 || (s->ti_wptr == TI_BUFSZ)) {
>          s->pdma_cb(s);
> -        s->pdma_cb = NULL;
>      }
>  }
>  
> @@ -989,14 +1035,10 @@ static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
>  {
>      SysBusESPState *sysbus = opaque;
>      ESPState *s = ESP(&sysbus->esp);
> -    uint32_t dmalen = esp_get_tc(s);
>      uint64_t val = 0;
>  
>      trace_esp_pdma_read(size);
>  
> -    if (dmalen == 0) {
> -        return 0;
> -    }
>      switch (size) {
>      case 1:
>          val = esp_pdma_read(s);
> @@ -1006,11 +1048,10 @@ static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
>          val = (val << 8) | esp_pdma_read(s);
>          break;
>      }
> -    dmalen = esp_get_tc(s);
> -    if (dmalen == 0 && s->pdma_cb) {
> -        esp_lower_drq(s);
> +    if (s->ti_rptr == s->ti_wptr) {
> +        s->ti_wptr = 0;
> +        s->ti_rptr = 0;
>          s->pdma_cb(s);
> -        s->pdma_cb = NULL;
>      }
>      return val;
>  }
> 

Reviewed-by: Laurent Vivier <laurent@vivier.eu>
diff mbox series

Patch

diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c
index b7ab5a5592..5dcd4cd651 100644
--- a/hw/scsi/esp.c
+++ b/hw/scsi/esp.c
@@ -134,13 +134,8 @@  static void set_pdma(ESPState *s, enum pdma_origin_id origin)
 
 static uint8_t esp_pdma_read(ESPState *s)
 {
-    uint32_t dmalen = esp_get_tc(s);
     uint8_t val;
 
-    if (dmalen == 0) {
-        return 0;
-    }
-
     switch (s->pdma_origin) {
     case TI:
         if (s->do_cmd) {
@@ -160,10 +155,6 @@  static uint8_t esp_pdma_read(ESPState *s)
         g_assert_not_reached();
     }
 
-    s->ti_size--;
-    dmalen--;
-    esp_set_tc(s, dmalen);
-
     return val;
 }
 
@@ -194,7 +185,6 @@  static void esp_pdma_write(ESPState *s, uint8_t val)
         g_assert_not_reached();
     }
 
-    s->ti_size++;
     dmalen--;
     esp_set_tc(s, dmalen);
 }
@@ -290,6 +280,7 @@  static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid)
     s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
     s->rregs[ESP_RSEQ] = SEQ_CD;
     esp_raise_irq(s);
+    esp_lower_drq(s);
 }
 
 static void do_cmd(ESPState *s)
@@ -447,28 +438,71 @@  static void esp_dma_done(ESPState *s)
 static void do_dma_pdma_cb(ESPState *s)
 {
     int to_device = ((s->rregs[ESP_RSTAT] & 7) == STAT_DO);
+    int len;
 
     if (s->do_cmd) {
         s->ti_size = 0;
         s->cmdlen = 0;
         s->do_cmd = 0;
         do_cmd(s);
+        esp_lower_drq(s);
         return;
     }
-    if (s->async_len == 0) {
-        scsi_req_continue(s->current_req);
-        /*
-         * If there is still data to be read from the device then
-         * complete the DMA operation immediately.  Otherwise defer
-         * until the scsi layer has completed.
-         */
-        if (to_device || esp_get_tc(s) != 0 || s->ti_size == 0) {
+
+    if (to_device) {
+        /* Copy FIFO data to device */
+        len = MIN(s->ti_wptr, TI_BUFSZ);
+        memcpy(s->async_buf, s->ti_buf, len);
+        s->ti_wptr = 0;
+        s->ti_rptr = 0;
+        s->async_buf += len;
+        s->async_len -= len;
+        s->ti_size += len;
+        if (s->async_len == 0) {
+            scsi_req_continue(s->current_req);
             return;
         }
-    }
 
-    /* Partially filled a scsi buffer. Complete immediately.  */
-    esp_dma_done(s);
+        if (esp_get_tc(s) == 0) {
+            esp_lower_drq(s);
+            esp_dma_done(s);
+        }
+
+        return;
+    } else {
+        if (s->async_len == 0) {
+            if (s->current_req) {
+                scsi_req_continue(s->current_req);
+            }
+
+            /*
+             * If there is still data to be read from the device then
+             * complete the DMA operation immediately.  Otherwise defer
+             * until the scsi layer has completed.
+             */
+            if (esp_get_tc(s) != 0 || s->ti_size == 0) {
+                return;
+            }
+        }
+
+        if (esp_get_tc(s) != 0) {
+            /* Copy device data to FIFO */
+            s->ti_wptr = 0;
+            s->ti_rptr = 0;
+            len = MIN(s->async_len, TI_BUFSZ);
+            memcpy(s->ti_buf, s->async_buf, len);
+            s->ti_wptr += len;
+            s->async_buf += len;
+            s->async_len -= len;
+            s->ti_size -= len;
+            esp_set_tc(s, esp_get_tc(s) - len);
+            return;
+        }
+
+        /* Partially filled a scsi buffer. Complete immediately.  */
+        esp_lower_drq(s);
+        esp_dma_done(s);
+    }
 }
 
 static void esp_do_dma(ESPState *s)
@@ -511,7 +545,7 @@  static void esp_do_dma(ESPState *s)
         if (s->dma_memory_read) {
             s->dma_memory_read(s->dma_opaque, s->async_buf, len);
         } else {
-            set_pdma(s, ASYNC);
+            set_pdma(s, TI);
             s->pdma_cb = do_dma_pdma_cb;
             esp_raise_drq(s);
             return;
@@ -520,9 +554,20 @@  static void esp_do_dma(ESPState *s)
         if (s->dma_memory_write) {
             s->dma_memory_write(s->dma_opaque, s->async_buf, len);
         } else {
-            set_pdma(s, ASYNC);
+            /* Copy device data to FIFO */
+            len = MIN(len, TI_BUFSZ - s->ti_wptr);
+            memcpy(&s->ti_buf[s->ti_wptr], s->async_buf, len);
+            s->ti_wptr += len;
+            s->async_buf += len;
+            s->async_len -= len;
+            s->ti_size -= len;
+            esp_set_tc(s, esp_get_tc(s) - len);
+            set_pdma(s, TI);
             s->pdma_cb = do_dma_pdma_cb;
             esp_raise_drq(s);
+
+            /* Indicate transfer to FIFO is complete */
+            s->rregs[ESP_RSTAT] |= STAT_TC;
             return;
         }
     }
@@ -548,6 +593,7 @@  static void esp_do_dma(ESPState *s)
 
     /* Partially filled a scsi buffer. Complete immediately.  */
     esp_dma_done(s);
+    esp_lower_drq(s);
 }
 
 static void esp_report_command_complete(ESPState *s, uint32_t status)
@@ -564,6 +610,7 @@  static void esp_report_command_complete(ESPState *s, uint32_t status)
     s->status = status;
     s->rregs[ESP_RSTAT] = STAT_ST;
     esp_dma_done(s);
+    esp_lower_drq(s);
     if (s->current_req) {
         scsi_req_unref(s->current_req);
         s->current_req = NULL;
@@ -606,6 +653,7 @@  void esp_transfer_data(SCSIRequest *req, uint32_t len)
          * completion interrupt is deferred to here.
          */
         esp_dma_done(s);
+        esp_lower_drq(s);
     }
 }
 
@@ -977,10 +1025,8 @@  static void sysbus_esp_pdma_write(void *opaque, hwaddr addr,
         break;
     }
     dmalen = esp_get_tc(s);
-    if (dmalen == 0 && s->pdma_cb) {
-        esp_lower_drq(s);
+    if (dmalen == 0 || (s->ti_wptr == TI_BUFSZ)) {
         s->pdma_cb(s);
-        s->pdma_cb = NULL;
     }
 }
 
@@ -989,14 +1035,10 @@  static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
 {
     SysBusESPState *sysbus = opaque;
     ESPState *s = ESP(&sysbus->esp);
-    uint32_t dmalen = esp_get_tc(s);
     uint64_t val = 0;
 
     trace_esp_pdma_read(size);
 
-    if (dmalen == 0) {
-        return 0;
-    }
     switch (size) {
     case 1:
         val = esp_pdma_read(s);
@@ -1006,11 +1048,10 @@  static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
         val = (val << 8) | esp_pdma_read(s);
         break;
     }
-    dmalen = esp_get_tc(s);
-    if (dmalen == 0 && s->pdma_cb) {
-        esp_lower_drq(s);
+    if (s->ti_rptr == s->ti_wptr) {
+        s->ti_wptr = 0;
+        s->ti_rptr = 0;
         s->pdma_cb(s);
-        s->pdma_cb = NULL;
     }
     return val;
 }