aboutsummaryrefslogtreecommitdiff
path: root/hw/scsi/esp.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/scsi/esp.c')
-rw-r--r--hw/scsi/esp.c336
1 files changed, 308 insertions, 28 deletions
diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c
index 841d79b..f8fc30c 100644
--- a/hw/scsi/esp.c
+++ b/hw/scsi/esp.c
@@ -38,6 +38,8 @@
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
* and
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR53C9X.txt
+ *
+ * On Macintosh Quadra it is a NCR53C96.
*/
static void esp_raise_irq(ESPState *s)
@@ -58,6 +60,16 @@ static void esp_lower_irq(ESPState *s)
}
}
+static void esp_raise_drq(ESPState *s)
+{
+ qemu_irq_raise(s->irq_data);
+}
+
+static void esp_lower_drq(ESPState *s)
+{
+ qemu_irq_lower(s->irq_data);
+}
+
void esp_dma_enable(ESPState *s, int irq, int level)
{
if (level) {
@@ -84,6 +96,58 @@ void esp_request_cancelled(SCSIRequest *req)
}
}
+static void set_pdma(ESPState *s, enum pdma_origin_id origin,
+ uint32_t index, uint32_t len)
+{
+ s->pdma_origin = origin;
+ s->pdma_start = index;
+ s->pdma_cur = index;
+ s->pdma_len = len;
+}
+
+static uint8_t *get_pdma_buf(ESPState *s)
+{
+ switch (s->pdma_origin) {
+ case PDMA:
+ return s->pdma_buf;
+ case TI:
+ return s->ti_buf;
+ case CMD:
+ return s->cmdbuf;
+ case ASYNC:
+ return s->async_buf;
+ }
+ return NULL;
+}
+
+static int get_cmd_cb(ESPState *s)
+{
+ int target;
+
+ target = s->wregs[ESP_WBUSID] & BUSID_DID;
+
+ s->ti_size = 0;
+ s->ti_rptr = 0;
+ s->ti_wptr = 0;
+
+ if (s->current_req) {
+ /* Started a new command before the old one finished. Cancel it. */
+ scsi_req_cancel(s->current_req);
+ s->async_len = 0;
+ }
+
+ s->current_dev = scsi_device_find(&s->bus, 0, target, 0);
+ if (!s->current_dev) {
+ /* No such drive */
+ s->rregs[ESP_RSTAT] = 0;
+ s->rregs[ESP_RINTR] = INTR_DC;
+ s->rregs[ESP_RSEQ] = SEQ_0;
+ esp_raise_irq(s);
+ return -1;
+ }
+ return 0;
+}
+
static uint32_t get_cmd(ESPState *s, uint8_t *buf, uint8_t buflen)
{
uint32_t dmalen;
@@ -97,7 +161,14 @@ static uint32_t get_cmd(ESPState *s, uint8_t *buf, uint8_t buflen)
if (dmalen > buflen) {
return 0;
}
- s->dma_memory_read(s->dma_opaque, buf, dmalen);
+ if (s->dma_memory_read) {
+ s->dma_memory_read(s->dma_opaque, buf, dmalen);
+ } else {
+ memcpy(s->pdma_buf, buf, dmalen);
+ set_pdma(s, PDMA, 0, dmalen);
+ esp_raise_drq(s);
+ return 0;
+ }
} else {
dmalen = s->ti_size;
if (dmalen > TI_BUFSZ) {
@@ -108,23 +179,7 @@ static uint32_t get_cmd(ESPState *s, uint8_t *buf, uint8_t buflen)
}
trace_esp_get_cmd(dmalen, target);
- s->ti_size = 0;
- s->ti_rptr = 0;
- s->ti_wptr = 0;
-
- if (s->current_req) {
- /* Started a new command before the old one finished. Cancel it. */
- scsi_req_cancel(s->current_req);
- s->async_len = 0;
- }
-
- s->current_dev = scsi_device_find(&s->bus, 0, target, 0);
- if (!s->current_dev) {
- // No such drive
- s->rregs[ESP_RSTAT] = 0;
- s->rregs[ESP_RINTR] = INTR_DC;
- s->rregs[ESP_RSEQ] = SEQ_0;
- esp_raise_irq(s);
+ if (get_cmd_cb(s) < 0) {
return 0;
}
return dmalen;
@@ -165,6 +220,16 @@ static void do_cmd(ESPState *s, uint8_t *buf)
do_busid_cmd(s, &buf[1], busid);
}
+static void satn_pdma_cb(ESPState *s)
+{
+ if (get_cmd_cb(s) < 0) {
+ return;
+ }
+ if (s->pdma_cur != s->pdma_start) {
+ do_cmd(s, get_pdma_buf(s) + s->pdma_start);
+ }
+}
+
static void handle_satn(ESPState *s)
{
uint8_t buf[32];
@@ -174,11 +239,22 @@ static void handle_satn(ESPState *s)
s->dma_cb = handle_satn;
return;
}
+ s->pdma_cb = satn_pdma_cb;
len = get_cmd(s, buf, sizeof(buf));
if (len)
do_cmd(s, buf);
}
+static void s_without_satn_pdma_cb(ESPState *s)
+{
+ if (get_cmd_cb(s) < 0) {
+ return;
+ }
+ if (s->pdma_cur != s->pdma_start) {
+ do_busid_cmd(s, get_pdma_buf(s) + s->pdma_start, 0);
+ }
+}
+
static void handle_s_without_atn(ESPState *s)
{
uint8_t buf[32];
@@ -188,18 +264,36 @@ static void handle_s_without_atn(ESPState *s)
s->dma_cb = handle_s_without_atn;
return;
}
+ s->pdma_cb = s_without_satn_pdma_cb;
len = get_cmd(s, buf, sizeof(buf));
if (len) {
do_busid_cmd(s, buf, 0);
}
}
+static void satn_stop_pdma_cb(ESPState *s)
+{
+ if (get_cmd_cb(s) < 0) {
+ return;
+ }
+ s->cmdlen = s->pdma_cur - s->pdma_start;
+ if (s->cmdlen) {
+ trace_esp_handle_satn_stop(s->cmdlen);
+ s->do_cmd = 1;
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_CD;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ esp_raise_irq(s);
+ }
+}
+
static void handle_satn_stop(ESPState *s)
{
if (s->dma && !s->dma_enabled) {
s->dma_cb = handle_satn_stop;
return;
}
+ s->pdma_cb = satn_stop_pdma_cb;;
s->cmdlen = get_cmd(s, s->cmdbuf, sizeof(s->cmdbuf));
if (s->cmdlen) {
trace_esp_handle_satn_stop(s->cmdlen);
@@ -211,16 +305,31 @@ static void handle_satn_stop(ESPState *s)
}
}
+static void write_response_pdma_cb(ESPState *s)
+{
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ esp_raise_irq(s);
+}
+
static void write_response(ESPState *s)
{
trace_esp_write_response(s->status);
s->ti_buf[0] = s->status;
s->ti_buf[1] = 0;
if (s->dma) {
- s->dma_memory_write(s->dma_opaque, s->ti_buf, 2);
- s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
- s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
- s->rregs[ESP_RSEQ] = SEQ_CD;
+ if (s->dma_memory_write) {
+ s->dma_memory_write(s->dma_opaque, s->ti_buf, 2);
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
+ s->rregs[ESP_RINTR] = INTR_BS | INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ } else {
+ set_pdma(s, TI, 0, 2);
+ s->pdma_cb = write_response_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
} else {
s->ti_size = 2;
s->ti_rptr = 0;
@@ -242,6 +351,41 @@ static void esp_dma_done(ESPState *s)
esp_raise_irq(s);
}
+static void do_dma_pdma_cb(ESPState *s)
+{
+ int to_device = (s->ti_size < 0);
+ int len = s->pdma_cur - s->pdma_start;
+ if (s->do_cmd) {
+ s->ti_size = 0;
+ s->cmdlen = 0;
+ s->do_cmd = 0;
+ do_cmd(s, s->cmdbuf);
+ return;
+ }
+ s->dma_left -= len;
+ s->async_buf += len;
+ s->async_len -= len;
+ if (to_device) {
+ s->ti_size += len;
+ } else {
+ s->ti_size -= len;
+ }
+ 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 || s->dma_left != 0 || s->ti_size == 0) {
+ return;
+ }
+ }
+
+ /* Partially filled a scsi buffer. Complete immediately. */
+ esp_dma_done(s);
+}
+
static void esp_do_dma(ESPState *s)
{
uint32_t len;
@@ -249,10 +393,26 @@ static void esp_do_dma(ESPState *s)
len = s->dma_left;
if (s->do_cmd) {
+ /*
+ * handle_ti_cmd() case: esp_do_dma() is called only from
+ * handle_ti_cmd() with do_cmd != NULL (see the assert())
+ */
trace_esp_do_dma(s->cmdlen, len);
assert (s->cmdlen <= sizeof(s->cmdbuf) &&
len <= sizeof(s->cmdbuf) - s->cmdlen);
- s->dma_memory_read(s->dma_opaque, &s->cmdbuf[s->cmdlen], len);
+ if (s->dma_memory_read) {
+ s->dma_memory_read(s->dma_opaque, &s->cmdbuf[s->cmdlen], len);
+ } else {
+ set_pdma(s, CMD, s->cmdlen, len);
+ s->pdma_cb = do_dma_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
+ trace_esp_handle_ti_cmd(s->cmdlen);
+ s->ti_size = 0;
+ s->cmdlen = 0;
+ s->do_cmd = 0;
+ do_cmd(s, s->cmdbuf);
return;
}
if (s->async_len == 0) {
@@ -264,9 +424,23 @@ static void esp_do_dma(ESPState *s)
}
to_device = (s->ti_size < 0);
if (to_device) {
- s->dma_memory_read(s->dma_opaque, s->async_buf, len);
+ if (s->dma_memory_read) {
+ s->dma_memory_read(s->dma_opaque, s->async_buf, len);
+ } else {
+ set_pdma(s, ASYNC, 0, len);
+ s->pdma_cb = do_dma_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
} else {
- s->dma_memory_write(s->dma_opaque, s->async_buf, len);
+ if (s->dma_memory_write) {
+ s->dma_memory_write(s->dma_opaque, s->async_buf, len);
+ } else {
+ set_pdma(s, ASYNC, 0, len);
+ s->pdma_cb = do_dma_pdma_cb;
+ esp_raise_drq(s);
+ return;
+ }
}
s->dma_left -= len;
s->async_buf += len;
@@ -373,8 +547,7 @@ static void handle_ti(ESPState *s)
s->dma_left = minlen;
s->rregs[ESP_RSTAT] &= ~STAT_TC;
esp_do_dma(s);
- }
- if (s->do_cmd) {
+ } else if (s->do_cmd) {
trace_esp_handle_ti_cmd(s->cmdlen);
s->ti_size = 0;
s->cmdlen = 0;
@@ -401,6 +574,7 @@ void esp_hard_reset(ESPState *s)
static void esp_soft_reset(ESPState *s)
{
qemu_irq_lower(s->irq);
+ qemu_irq_lower(s->irq_data);
esp_hard_reset(s);
}
@@ -590,6 +764,28 @@ static bool esp_mem_accepts(void *opaque, hwaddr addr,
return (size == 1) || (is_write && size == 4);
}
+static bool esp_pdma_needed(void *opaque)
+{
+ ESPState *s = opaque;
+ return s->dma_memory_read == NULL && s->dma_memory_write == NULL &&
+ s->dma_enabled;
+}
+
+static const VMStateDescription vmstate_esp_pdma = {
+ .name = "esp/pdma",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = esp_pdma_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(pdma_buf, ESPState),
+ VMSTATE_INT32(pdma_origin, ESPState),
+ VMSTATE_UINT32(pdma_len, ESPState),
+ VMSTATE_UINT32(pdma_start, ESPState),
+ VMSTATE_UINT32(pdma_cur, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
const VMStateDescription vmstate_esp = {
.name ="esp",
.version_id = 4,
@@ -611,6 +807,10 @@ const VMStateDescription vmstate_esp = {
VMSTATE_UINT32(do_cmd, ESPState),
VMSTATE_UINT32(dma_left, ESPState),
VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription * []) {
+ &vmstate_esp_pdma,
+ NULL
}
};
@@ -641,6 +841,82 @@ static const MemoryRegionOps sysbus_esp_mem_ops = {
.valid.accepts = esp_mem_accepts,
};
+static void sysbus_esp_pdma_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = &sysbus->esp;
+ uint32_t dmalen;
+ uint8_t *buf = get_pdma_buf(s);
+
+ dmalen = s->rregs[ESP_TCLO];
+ dmalen |= s->rregs[ESP_TCMID] << 8;
+ dmalen |= s->rregs[ESP_TCHI] << 16;
+ if (dmalen == 0 || s->pdma_len == 0) {
+ return;
+ }
+ switch (size) {
+ case 1:
+ buf[s->pdma_cur++] = val;
+ s->pdma_len--;
+ dmalen--;
+ break;
+ case 2:
+ buf[s->pdma_cur++] = val >> 8;
+ buf[s->pdma_cur++] = val;
+ s->pdma_len -= 2;
+ dmalen -= 2;
+ break;
+ }
+ s->rregs[ESP_TCLO] = dmalen & 0xff;
+ s->rregs[ESP_TCMID] = dmalen >> 8;
+ s->rregs[ESP_TCHI] = dmalen >> 16;
+ if (s->pdma_len == 0 && s->pdma_cb) {
+ esp_lower_drq(s);
+ s->pdma_cb(s);
+ s->pdma_cb = NULL;
+ }
+}
+
+static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = &sysbus->esp;
+ uint8_t *buf = get_pdma_buf(s);
+ uint64_t val = 0;
+
+ if (s->pdma_len == 0) {
+ return 0;
+ }
+ switch (size) {
+ case 1:
+ val = buf[s->pdma_cur++];
+ s->pdma_len--;
+ break;
+ case 2:
+ val = buf[s->pdma_cur++];
+ val = (val << 8) | buf[s->pdma_cur++];
+ s->pdma_len -= 2;
+ break;
+ }
+
+ if (s->pdma_len == 0 && s->pdma_cb) {
+ esp_lower_drq(s);
+ s->pdma_cb(s);
+ s->pdma_cb = NULL;
+ }
+ return val;
+}
+
+static const MemoryRegionOps sysbus_esp_pdma_ops = {
+ .read = sysbus_esp_pdma_read,
+ .write = sysbus_esp_pdma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 2,
+};
+
static const struct SCSIBusInfo esp_scsi_info = {
.tcq = false,
.max_target = ESP_MAX_DEVS,
@@ -673,12 +949,16 @@ static void sysbus_esp_realize(DeviceState *dev, Error **errp)
ESPState *s = &sysbus->esp;
sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->irq_data);
assert(sysbus->it_shift != -1);
s->chip_id = TCHI_FAS100A;
memory_region_init_io(&sysbus->iomem, OBJECT(sysbus), &sysbus_esp_mem_ops,
- sysbus, "esp", ESP_REGS << sysbus->it_shift);
+ sysbus, "esp-regs", ESP_REGS << sysbus->it_shift);
sysbus_init_mmio(sbd, &sysbus->iomem);
+ memory_region_init_io(&sysbus->pdma, OBJECT(sysbus), &sysbus_esp_pdma_ops,
+ sysbus, "esp-pdma", 2);
+ sysbus_init_mmio(sbd, &sysbus->pdma);
qdev_init_gpio_in(dev, sysbus_esp_gpio_demux, 2);