aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/scsi/esp.c223
1 files changed, 142 insertions, 81 deletions
diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c
index 590ff99744..5d9b52632e 100644
--- a/hw/scsi/esp.c
+++ b/hw/scsi/esp.c
@@ -79,6 +79,24 @@ static void esp_lower_drq(ESPState *s)
}
}
+static const char *esp_phase_names[8] = {
+ "DATA OUT", "DATA IN", "COMMAND", "STATUS",
+ "(reserved)", "(reserved)", "MESSAGE OUT", "MESSAGE IN"
+};
+
+static void esp_set_phase(ESPState *s, uint8_t phase)
+{
+ s->rregs[ESP_RSTAT] &= ~7;
+ s->rregs[ESP_RSTAT] |= phase;
+
+ trace_esp_set_phase(esp_phase_names[phase]);
+}
+
+static uint8_t esp_get_phase(ESPState *s)
+{
+ return s->rregs[ESP_RSTAT] & 7;
+}
+
void esp_dma_enable(ESPState *s, int irq, int level)
{
if (level) {
@@ -106,26 +124,80 @@ void esp_request_cancelled(SCSIRequest *req)
}
}
-static void esp_fifo_push(Fifo8 *fifo, uint8_t val)
+static void esp_update_drq(ESPState *s)
{
- if (fifo8_num_used(fifo) == fifo->capacity) {
- trace_esp_error_fifo_overrun();
+ bool to_device;
+
+ switch (esp_get_phase(s)) {
+ case STAT_MO:
+ case STAT_CD:
+ case STAT_DO:
+ to_device = true;
+ break;
+
+ case STAT_DI:
+ case STAT_ST:
+ case STAT_MI:
+ to_device = false;
+ break;
+
+ default:
return;
}
- fifo8_push(fifo, val);
+ if (s->dma) {
+ /* DMA request so update DRQ according to transfer direction */
+ if (to_device) {
+ if (fifo8_num_free(&s->fifo) < 2) {
+ esp_lower_drq(s);
+ } else {
+ esp_raise_drq(s);
+ }
+ } else {
+ if (fifo8_num_used(&s->fifo) < 2) {
+ esp_lower_drq(s);
+ } else {
+ esp_raise_drq(s);
+ }
+ }
+ } else {
+ /* Not a DMA request */
+ esp_lower_drq(s);
+ }
}
-static uint8_t esp_fifo_pop(Fifo8 *fifo)
+static void esp_fifo_push(ESPState *s, uint8_t val)
{
- if (fifo8_is_empty(fifo)) {
- return 0;
+ if (fifo8_num_used(&s->fifo) == s->fifo.capacity) {
+ trace_esp_error_fifo_overrun();
+ } else {
+ fifo8_push(&s->fifo, val);
}
- return fifo8_pop(fifo);
+ esp_update_drq(s);
}
-static uint32_t esp_fifo_pop_buf(Fifo8 *fifo, uint8_t *dest, int maxlen)
+static void esp_fifo_push_buf(ESPState *s, uint8_t *buf, int len)
+{
+ fifo8_push_all(&s->fifo, buf, len);
+ esp_update_drq(s);
+}
+
+static uint8_t esp_fifo_pop(ESPState *s)
+{
+ uint8_t val;
+
+ if (fifo8_is_empty(&s->fifo)) {
+ val = 0;
+ } else {
+ val = fifo8_pop(&s->fifo);
+ }
+
+ esp_update_drq(s);
+ return val;
+}
+
+static uint32_t esp_fifo8_pop_buf(Fifo8 *fifo, uint8_t *dest, int maxlen)
{
const uint8_t *buf;
uint32_t n, n2;
@@ -155,6 +227,14 @@ static uint32_t esp_fifo_pop_buf(Fifo8 *fifo, uint8_t *dest, int maxlen)
return n;
}
+static uint32_t esp_fifo_pop_buf(ESPState *s, uint8_t *dest, int maxlen)
+{
+ uint32_t len = esp_fifo8_pop_buf(&s->fifo, dest, maxlen);
+
+ esp_update_drq(s);
+ return len;
+}
+
static uint32_t esp_get_tc(ESPState *s)
{
uint32_t dmalen;
@@ -190,29 +270,11 @@ static uint32_t esp_get_stc(ESPState *s)
return dmalen;
}
-static const char *esp_phase_names[8] = {
- "DATA OUT", "DATA IN", "COMMAND", "STATUS",
- "(reserved)", "(reserved)", "MESSAGE OUT", "MESSAGE IN"
-};
-
-static void esp_set_phase(ESPState *s, uint8_t phase)
-{
- s->rregs[ESP_RSTAT] &= ~7;
- s->rregs[ESP_RSTAT] |= phase;
-
- trace_esp_set_phase(esp_phase_names[phase]);
-}
-
-static uint8_t esp_get_phase(ESPState *s)
-{
- return s->rregs[ESP_RSTAT] & 7;
-}
-
static uint8_t esp_pdma_read(ESPState *s)
{
uint8_t val;
- val = esp_fifo_pop(&s->fifo);
+ val = esp_fifo_pop(s);
return val;
}
@@ -220,14 +282,12 @@ static void esp_pdma_write(ESPState *s, uint8_t val)
{
uint32_t dmalen = esp_get_tc(s);
- if (dmalen == 0) {
- return;
- }
-
- esp_fifo_push(&s->fifo, val);
+ esp_fifo_push(s, val);
- dmalen--;
- esp_set_tc(s, dmalen);
+ if (dmalen && s->drq_state) {
+ dmalen--;
+ esp_set_tc(s, dmalen);
+ }
}
static int esp_select(ESPState *s)
@@ -275,7 +335,7 @@ static void do_command_phase(ESPState *s)
if (!cmdlen || !s->current_dev) {
return;
}
- esp_fifo_pop_buf(&s->cmdfifo, buf, cmdlen);
+ esp_fifo8_pop_buf(&s->cmdfifo, buf, cmdlen);
current_lun = scsi_device_find(&s->bus, 0, s->current_dev->id, s->lun);
if (!current_lun) {
@@ -310,7 +370,8 @@ static void do_command_phase(ESPState *s)
static void do_message_phase(ESPState *s)
{
if (s->cmdfifo_cdb_offset) {
- uint8_t message = esp_fifo_pop(&s->cmdfifo);
+ uint8_t message = fifo8_is_empty(&s->cmdfifo) ? 0 :
+ fifo8_pop(&s->cmdfifo);
trace_esp_do_identify(message);
s->lun = message & 7;
@@ -320,7 +381,7 @@ static void do_message_phase(ESPState *s)
/* Ignore extended messages for now */
if (s->cmdfifo_cdb_offset) {
int len = MIN(s->cmdfifo_cdb_offset, fifo8_num_used(&s->cmdfifo));
- esp_fifo_pop_buf(&s->cmdfifo, NULL, len);
+ esp_fifo8_pop_buf(&s->cmdfifo, NULL, len);
s->cmdfifo_cdb_offset = 0;
}
}
@@ -414,20 +475,30 @@ static void write_response(ESPState *s)
}
}
-static int esp_cdb_length(ESPState *s)
+static bool esp_cdb_ready(ESPState *s)
{
+ int len = fifo8_num_used(&s->cmdfifo) - s->cmdfifo_cdb_offset;
const uint8_t *pbuf;
- int cmdlen, len;
+ uint32_t n;
+ int cdblen;
- cmdlen = fifo8_num_used(&s->cmdfifo);
- if (cmdlen < s->cmdfifo_cdb_offset) {
- return 0;
+ if (len <= 0) {
+ return false;
}
- pbuf = fifo8_peek_buf(&s->cmdfifo, cmdlen, NULL);
- len = scsi_cdb_length((uint8_t *)&pbuf[s->cmdfifo_cdb_offset]);
+ pbuf = fifo8_peek_buf(&s->cmdfifo, len, &n);
+ if (n < len) {
+ /*
+ * In normal use the cmdfifo should never wrap, but include this check
+ * to prevent a malicious guest from reading past the end of the
+ * cmdfifo data buffer below
+ */
+ return false;
+ }
- return len;
+ cdblen = scsi_cdb_length((uint8_t *)&pbuf[s->cmdfifo_cdb_offset]);
+
+ return cdblen < 0 ? false : (len >= cdblen);
}
static void esp_dma_ti_check(ESPState *s)
@@ -435,7 +506,6 @@ static void esp_dma_ti_check(ESPState *s)
if (esp_get_tc(s) == 0 && fifo8_num_used(&s->fifo) < 2) {
s->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(s);
- esp_lower_drq(s);
}
}
@@ -453,9 +523,8 @@ static void esp_do_dma(ESPState *s)
s->dma_memory_read(s->dma_opaque, buf, len);
esp_set_tc(s, esp_get_tc(s) - len);
} else {
- len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
+ len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
- esp_raise_drq(s);
}
fifo8_push_all(&s->cmdfifo, buf, len);
@@ -509,10 +578,9 @@ static void esp_do_dma(ESPState *s)
fifo8_push_all(&s->cmdfifo, buf, len);
esp_set_tc(s, esp_get_tc(s) - len);
} else {
- len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
+ len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
fifo8_push_all(&s->cmdfifo, buf, len);
- esp_raise_drq(s);
}
trace_esp_handle_ti_cmd(cmdlen);
s->ti_size = 0;
@@ -543,8 +611,7 @@ static void esp_do_dma(ESPState *s)
/* Copy FIFO data to device */
len = MIN(s->async_len, ESP_FIFO_SZ);
len = MIN(len, fifo8_num_used(&s->fifo));
- len = esp_fifo_pop_buf(&s->fifo, s->async_buf, len);
- esp_raise_drq(s);
+ len = esp_fifo_pop_buf(s, s->async_buf, len);
}
s->async_buf += len;
@@ -595,8 +662,7 @@ static void esp_do_dma(ESPState *s)
} else {
/* Copy device data to FIFO */
len = MIN(len, fifo8_num_free(&s->fifo));
- fifo8_push_all(&s->fifo, s->async_buf, len);
- esp_raise_drq(s);
+ esp_fifo_push_buf(s, s->async_buf, len);
}
s->async_buf += len;
@@ -644,7 +710,7 @@ static void esp_do_dma(ESPState *s)
if (s->dma_memory_write) {
s->dma_memory_write(s->dma_opaque, buf, len);
} else {
- fifo8_push_all(&s->fifo, buf, len);
+ esp_fifo_push_buf(s, buf, len);
}
esp_set_tc(s, esp_get_tc(s) - len);
@@ -662,7 +728,6 @@ static void esp_do_dma(ESPState *s)
if (fifo8_num_used(&s->fifo) < 2) {
s->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(s);
- esp_lower_drq(s);
}
break;
}
@@ -679,7 +744,7 @@ static void esp_do_dma(ESPState *s)
if (s->dma_memory_write) {
s->dma_memory_write(s->dma_opaque, buf, len);
} else {
- fifo8_push_all(&s->fifo, buf, len);
+ esp_fifo_push_buf(s, buf, len);
}
esp_set_tc(s, esp_get_tc(s) - len);
@@ -707,7 +772,7 @@ static void esp_nodma_ti_dataout(ESPState *s)
}
len = MIN(s->async_len, ESP_FIFO_SZ);
len = MIN(len, fifo8_num_used(&s->fifo));
- esp_fifo_pop_buf(&s->fifo, s->async_buf, len);
+ esp_fifo_pop_buf(s, s->async_buf, len);
s->async_buf += len;
s->async_len -= len;
s->ti_size += len;
@@ -732,7 +797,7 @@ static void esp_do_nodma(ESPState *s)
switch (s->rregs[ESP_CMD]) {
case CMD_SELATN:
/* Copy FIFO into cmdfifo */
- len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
+ len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
fifo8_push_all(&s->cmdfifo, buf, len);
@@ -751,7 +816,8 @@ static void esp_do_nodma(ESPState *s)
case CMD_SELATNS:
/* Copy one byte from FIFO into cmdfifo */
- len = esp_fifo_pop_buf(&s->fifo, buf, 1);
+ len = esp_fifo_pop_buf(s, buf,
+ MIN(fifo8_num_used(&s->fifo), 1));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
fifo8_push_all(&s->cmdfifo, buf, len);
@@ -768,7 +834,7 @@ static void esp_do_nodma(ESPState *s)
case CMD_TI:
/* Copy FIFO into cmdfifo */
- len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
+ len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
fifo8_push_all(&s->cmdfifo, buf, len);
@@ -786,7 +852,7 @@ static void esp_do_nodma(ESPState *s)
switch (s->rregs[ESP_CMD]) {
case CMD_TI:
/* Copy FIFO into cmdfifo */
- len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
+ len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
fifo8_push_all(&s->cmdfifo, buf, len);
@@ -794,10 +860,9 @@ static void esp_do_nodma(ESPState *s)
trace_esp_handle_ti_cmd(cmdlen);
/* CDB may be transferred in one or more TI commands */
- if (esp_cdb_length(s) && esp_cdb_length(s) ==
- fifo8_num_used(&s->cmdfifo) - s->cmdfifo_cdb_offset) {
- /* Command has been received */
- do_cmd(s);
+ if (esp_cdb_ready(s)) {
+ /* Command has been received */
+ do_cmd(s);
} else {
/*
* If data was transferred from the FIFO then raise bus
@@ -815,22 +880,21 @@ static void esp_do_nodma(ESPState *s)
case CMD_SEL | CMD_DMA:
case CMD_SELATN | CMD_DMA:
/* Copy FIFO into cmdfifo */
- len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
+ len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
fifo8_push_all(&s->cmdfifo, buf, len);
/* Handle when DMA transfer is terminated by non-DMA FIFO write */
- if (esp_cdb_length(s) && esp_cdb_length(s) ==
- fifo8_num_used(&s->cmdfifo) - s->cmdfifo_cdb_offset) {
- /* Command has been received */
- do_cmd(s);
+ if (esp_cdb_ready(s)) {
+ /* Command has been received */
+ do_cmd(s);
}
break;
case CMD_SEL:
case CMD_SELATN:
/* FIFO already contain entire CDB: copy to cmdfifo and execute */
- len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
+ len = esp_fifo_pop_buf(s, buf, fifo8_num_used(&s->fifo));
len = MIN(fifo8_num_free(&s->cmdfifo), len);
fifo8_push_all(&s->cmdfifo, buf, len);
@@ -852,7 +916,7 @@ static void esp_do_nodma(ESPState *s)
return;
}
if (fifo8_is_empty(&s->fifo)) {
- fifo8_push(&s->fifo, s->async_buf[0]);
+ esp_fifo_push(s, s->async_buf[0]);
s->async_buf++;
s->async_len--;
s->ti_size--;
@@ -875,7 +939,7 @@ static void esp_do_nodma(ESPState *s)
case STAT_ST:
switch (s->rregs[ESP_CMD]) {
case CMD_ICCS:
- fifo8_push(&s->fifo, s->status);
+ esp_fifo_push(s, s->status);
esp_set_phase(s, STAT_MI);
/* Process any message in phase data */
@@ -887,7 +951,7 @@ static void esp_do_nodma(ESPState *s)
case STAT_MI:
switch (s->rregs[ESP_CMD]) {
case CMD_ICCS:
- fifo8_push(&s->fifo, 0);
+ esp_fifo_push(s, 0);
/* Raise end of command interrupt */
s->rregs[ESP_RINTR] |= INTR_FC;
@@ -951,9 +1015,6 @@ void esp_command_complete(SCSIRequest *req, size_t resid)
s->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(s);
- /* Ensure DRQ is set correctly for TC underflow or normal completion */
- esp_dma_ti_check(s);
-
if (s->current_req) {
scsi_req_unref(s->current_req);
s->current_req = NULL;
@@ -1178,7 +1239,7 @@ uint64_t esp_reg_read(ESPState *s, uint32_t saddr)
switch (saddr) {
case ESP_FIFO:
- s->rregs[ESP_FIFO] = esp_fifo_pop(&s->fifo);
+ s->rregs[ESP_FIFO] = esp_fifo_pop(s);
val = s->rregs[ESP_FIFO];
break;
case ESP_RINTR:
@@ -1234,7 +1295,7 @@ void esp_reg_write(ESPState *s, uint32_t saddr, uint64_t val)
break;
case ESP_FIFO:
if (!fifo8_is_full(&s->fifo)) {
- esp_fifo_push(&s->fifo, val);
+ esp_fifo_push(s, val);
}
esp_do_nodma(s);
break;