ESP: fix ESP DMA access when DMA is not enabled

Sending ESP a command caused it to trigger DMA immediately
even if DMA was not enabled at the DMA controller.

Add a signal from DMA controller to ESP to tell ESP about changes in
DMA enable bit. Also use the correct function for setting up GPIO outputs.

This fixes NetBSD 1.6.1 through 3.0 boot.

Thanks to Artyom Tarasenko for extensive debugging of the problem.

Signed-off-by: Blue Swirl <blauwirbel@gmail.com>
diff --git a/hw/esp.c b/hw/esp.c
index 349052a..910fd31 100644
--- a/hw/esp.c
+++ b/hw/esp.c
@@ -80,6 +80,8 @@
     ESPDMAMemoryReadWriteFunc dma_memory_read;
     ESPDMAMemoryReadWriteFunc dma_memory_write;
     void *dma_opaque;
+    int dma_enabled;
+    void (*dma_cb)(ESPState *s);
 };
 
 #define ESP_TCLO   0x0
@@ -167,6 +169,24 @@
     }
 }
 
+static void esp_dma_enable(void *opaque, int irq, int level)
+{
+    DeviceState *d = opaque;
+    ESPState *s = container_of(d, ESPState, busdev.qdev);
+
+    if (level) {
+        s->dma_enabled = 1;
+        DPRINTF("Raise enable\n");
+        if (s->dma_cb) {
+            s->dma_cb(s);
+            s->dma_cb = NULL;
+        }
+    } else {
+        DPRINTF("Lower enable\n");
+        s->dma_enabled = 0;
+    }
+}
+
 static uint32_t get_cmd(ESPState *s, uint8_t *buf)
 {
     uint32_t dmalen;
@@ -243,6 +263,10 @@
     uint8_t buf[32];
     int len;
 
+    if (!s->dma_enabled) {
+        s->dma_cb = handle_satn;
+        return;
+    }
     len = get_cmd(s, buf);
     if (len)
         do_cmd(s, buf);
@@ -253,6 +277,10 @@
     uint8_t buf[32];
     int len;
 
+    if (!s->dma_enabled) {
+        s->dma_cb = handle_s_without_atn;
+        return;
+    }
     len = get_cmd(s, buf);
     if (len) {
         do_busid_cmd(s, buf, 0);
@@ -261,6 +289,10 @@
 
 static void handle_satn_stop(ESPState *s)
 {
+    if (!s->dma_enabled) {
+        s->dma_cb = handle_satn_stop;
+        return;
+    }
     s->cmdlen = get_cmd(s, s->cmdbuf);
     if (s->cmdlen) {
         DPRINTF("Set ATN & Stop: cmdlen %d\n", s->cmdlen);
@@ -431,6 +463,7 @@
     s->ti_wptr = 0;
     s->dma = 0;
     s->do_cmd = 0;
+    s->dma_cb = NULL;
 
     s->rregs[ESP_CFG1] = 7;
 }
@@ -450,6 +483,18 @@
     }
 }
 
+static void esp_gpio_demux(void *opaque, int irq, int level)
+{
+    switch (irq) {
+    case 0:
+        parent_esp_reset(opaque, irq, level);
+        break;
+    case 1:
+        esp_dma_enable(opaque, irq, level);
+        break;
+    }
+}
+
 static uint32_t esp_mem_readb(void *opaque, target_phys_addr_t addr)
 {
     ESPState *s = opaque;
@@ -646,7 +691,8 @@
 void esp_init(target_phys_addr_t espaddr, int it_shift,
               ESPDMAMemoryReadWriteFunc dma_memory_read,
               ESPDMAMemoryReadWriteFunc dma_memory_write,
-              void *dma_opaque, qemu_irq irq, qemu_irq *reset)
+              void *dma_opaque, qemu_irq irq, qemu_irq *reset,
+              qemu_irq *dma_enable)
 {
     DeviceState *dev;
     SysBusDevice *s;
@@ -658,11 +704,14 @@
     esp->dma_memory_write = dma_memory_write;
     esp->dma_opaque = dma_opaque;
     esp->it_shift = it_shift;
+    /* XXX for now until rc4030 has been changed to use DMA enable signal */
+    esp->dma_enabled = 1;
     qdev_init_nofail(dev);
     s = sysbus_from_qdev(dev);
     sysbus_connect_irq(s, 0, irq);
     sysbus_mmio_map(s, 0, espaddr);
     *reset = qdev_get_gpio_in(dev, 0);
+    *dma_enable = qdev_get_gpio_in(dev, 1);
 }
 
 static int esp_init1(SysBusDevice *dev)
@@ -676,7 +725,7 @@
     esp_io_memory = cpu_register_io_memory(esp_mem_read, esp_mem_write, s);
     sysbus_init_mmio(dev, ESP_REGS << s->it_shift, esp_io_memory);
 
-    qdev_init_gpio_in(&dev->qdev, parent_esp_reset, 1);
+    qdev_init_gpio_in(&dev->qdev, esp_gpio_demux, 2);
 
     scsi_bus_new(&s->bus, &dev->qdev, 0, ESP_MAX_DEVS, esp_command_complete);
     return scsi_bus_legacy_handle_cmdline(&s->bus);
diff --git a/hw/esp.h b/hw/esp.h
index 605f953..62bfd4d 100644
--- a/hw/esp.h
+++ b/hw/esp.h
@@ -7,6 +7,7 @@
 void esp_init(target_phys_addr_t espaddr, int it_shift,
               ESPDMAMemoryReadWriteFunc dma_memory_read,
               ESPDMAMemoryReadWriteFunc dma_memory_write,
-              void *dma_opaque, qemu_irq irq, qemu_irq *reset);
+              void *dma_opaque, qemu_irq irq, qemu_irq *reset,
+              qemu_irq *dma_enable);
 
 #endif
diff --git a/hw/mips_jazz.c b/hw/mips_jazz.c
index 5d5305a..66397c0 100644
--- a/hw/mips_jazz.c
+++ b/hw/mips_jazz.c
@@ -137,7 +137,7 @@
     NICInfo *nd;
     PITState *pit;
     DriveInfo *fds[MAX_FD];
-    qemu_irq esp_reset;
+    qemu_irq esp_reset, dma_enable;
     qemu_irq *cpu_exit_irq;
     ram_addr_t ram_offset;
     ram_addr_t bios_offset;
@@ -245,7 +245,7 @@
     /* SCSI adapter */
     esp_init(0x80002000, 0,
              rc4030_dma_read, rc4030_dma_write, dmas[0],
-             rc4030[5], &esp_reset);
+             rc4030[5], &esp_reset, &dma_enable);
 
     /* Floppy */
     if (drive_get_max_bus(IF_FLOPPY) >= MAX_FD) {
diff --git a/hw/sparc32_dma.c b/hw/sparc32_dma.c
index b521707..984ffc3 100644
--- a/hw/sparc32_dma.c
+++ b/hw/sparc32_dma.c
@@ -58,6 +58,7 @@
 #define DMA_INTR 1
 #define DMA_INTREN 0x10
 #define DMA_WRITE_MEM 0x100
+#define DMA_EN 0x200
 #define DMA_LOADED 0x04000000
 #define DMA_DRAIN_FIFO 0x40
 #define DMA_RESET 0x80
@@ -72,7 +73,12 @@
     uint32_t dmaregs[DMA_REGS];
     qemu_irq irq;
     void *iommu;
-    qemu_irq dev_reset;
+    qemu_irq gpio[2];
+};
+
+enum {
+    GPIO_RESET = 0,
+    GPIO_DMA,
 };
 
 /* Note: on sparc, the lance 16 bit bus is swapped */
@@ -201,12 +207,21 @@
             }
         }
         if (val & DMA_RESET) {
-            qemu_irq_raise(s->dev_reset);
-            qemu_irq_lower(s->dev_reset);
+            qemu_irq_raise(s->gpio[GPIO_RESET]);
+            qemu_irq_lower(s->gpio[GPIO_RESET]);
         } else if (val & DMA_DRAIN_FIFO) {
             val &= ~DMA_DRAIN_FIFO;
         } else if (val == 0)
             val = DMA_DRAIN_FIFO;
+
+        if (val & DMA_EN && !(s->dmaregs[0] & DMA_EN)) {
+            DPRINTF("Raise DMA enable\n");
+            qemu_irq_raise(s->gpio[GPIO_DMA]);
+        } else if (!(val & DMA_EN) && !!(s->dmaregs[0] & DMA_EN)) {
+            DPRINTF("Lower DMA enable\n");
+            qemu_irq_lower(s->gpio[GPIO_DMA]);
+        }
+
         val &= ~DMA_CSR_RO_MASK;
         val |= DMA_VER;
         s->dmaregs[0] = (s->dmaregs[0] & DMA_CSR_RO_MASK) | val;
@@ -262,7 +277,7 @@
     sysbus_init_mmio(dev, DMA_SIZE, dma_io_memory);
 
     qdev_init_gpio_in(&dev->qdev, dma_set_irq, 1);
-    qdev_init_gpio_out(&dev->qdev, &s->dev_reset, 1);
+    qdev_init_gpio_out(&dev->qdev, s->gpio, 2);
 
     return 0;
 }
diff --git a/hw/sun4m.c b/hw/sun4m.c
index 7d7a7df..0392109 100644
--- a/hw/sun4m.c
+++ b/hw/sun4m.c
@@ -810,7 +810,7 @@
     void *iommu, *espdma, *ledma, *nvram;
     qemu_irq *cpu_irqs[MAX_CPUS], slavio_irq[32], slavio_cpu_irq[MAX_CPUS],
         espdma_irq, ledma_irq;
-    qemu_irq esp_reset;
+    qemu_irq esp_reset, dma_enable;
     qemu_irq fdc_tc;
     qemu_irq *cpu_halt;
     unsigned long kernel_size;
@@ -930,11 +930,12 @@
         exit(1);
     }
 
-    esp_reset = qdev_get_gpio_in(espdma, 0);
     esp_init(hwdef->esp_base, 2,
              espdma_memory_read, espdma_memory_write,
-             espdma, espdma_irq, &esp_reset);
+             espdma, espdma_irq, &esp_reset, &dma_enable);
 
+    qdev_connect_gpio_out(espdma, 0, esp_reset);
+    qdev_connect_gpio_out(espdma, 1, dma_enable);
 
     if (hwdef->cs_base) {
         sysbus_create_simple("SUNW,CS4231", hwdef->cs_base,
@@ -1494,7 +1495,7 @@
     void *iounits[MAX_IOUNITS], *espdma, *ledma, *nvram;
     qemu_irq *cpu_irqs[MAX_CPUS], sbi_irq[32], sbi_cpu_irq[MAX_CPUS],
         espdma_irq, ledma_irq;
-    qemu_irq esp_reset;
+    qemu_irq esp_reset, dma_enable;
     unsigned long kernel_size;
     void *fw_cfg;
     DeviceState *dev;
@@ -1561,10 +1562,12 @@
         exit(1);
     }
 
-    esp_reset = qdev_get_gpio_in(espdma, 0);
     esp_init(hwdef->esp_base, 2,
              espdma_memory_read, espdma_memory_write,
-             espdma, espdma_irq, &esp_reset);
+             espdma, espdma_irq, &esp_reset, &dma_enable);
+
+    qdev_connect_gpio_out(espdma, 0, esp_reset);
+    qdev_connect_gpio_out(espdma, 1, dma_enable);
 
     kernel_size = sun4m_load_kernel(kernel_filename, initrd_filename,
                                     RAM_size);
@@ -1683,7 +1686,7 @@
 {
     void *iommu, *espdma, *ledma, *nvram;
     qemu_irq *cpu_irqs, slavio_irq[8], espdma_irq, ledma_irq;
-    qemu_irq esp_reset;
+    qemu_irq esp_reset, dma_enable;
     qemu_irq fdc_tc;
     unsigned long kernel_size;
     DriveInfo *fd[MAX_FD];
@@ -1751,10 +1754,12 @@
         exit(1);
     }
 
-    esp_reset = qdev_get_gpio_in(espdma, 0);
     esp_init(hwdef->esp_base, 2,
              espdma_memory_read, espdma_memory_write,
-             espdma, espdma_irq, &esp_reset);
+             espdma, espdma_irq, &esp_reset, &dma_enable);
+
+    qdev_connect_gpio_out(espdma, 0, esp_reset);
+    qdev_connect_gpio_out(espdma, 1, dma_enable);
 
     kernel_size = sun4m_load_kernel(kernel_filename, initrd_filename,
                                     RAM_size);