a6cad7cd39
The Transfer Pad command is used to either drop incoming FIFO data during the DATA IN phase or generate a series of zero bytes in the FIFO during the DATA OUT phase. Implement the DMA Transfer Pad command for the DATA phases which is used by the NeXTCube firmware in the DATA IN phase to ignore part of the incoming SCSI data as it is copied into memory. Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> Tested-by: Helge Deller <deller@gmx.de> Tested-by: Thomas Huth <thuth@redhat.com> Message-Id: <20240112125420.514425-85-mark.cave-ayland@ilande.co.uk> Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
1594 lines
41 KiB
C
1594 lines
41 KiB
C
/*
|
|
* QEMU ESP/NCR53C9x emulation
|
|
*
|
|
* Copyright (c) 2005-2006 Fabrice Bellard
|
|
* Copyright (c) 2012 Herve Poussineau
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "hw/sysbus.h"
|
|
#include "migration/vmstate.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/scsi/esp.h"
|
|
#include "trace.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/module.h"
|
|
|
|
/*
|
|
* On Sparc32, this is the ESP (NCR53C90) part of chip STP2000 (Master I/O),
|
|
* also produced as NCR89C100. See
|
|
* 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)
|
|
{
|
|
if (!(s->rregs[ESP_RSTAT] & STAT_INT)) {
|
|
s->rregs[ESP_RSTAT] |= STAT_INT;
|
|
qemu_irq_raise(s->irq);
|
|
trace_esp_raise_irq();
|
|
}
|
|
}
|
|
|
|
static void esp_lower_irq(ESPState *s)
|
|
{
|
|
if (s->rregs[ESP_RSTAT] & STAT_INT) {
|
|
s->rregs[ESP_RSTAT] &= ~STAT_INT;
|
|
qemu_irq_lower(s->irq);
|
|
trace_esp_lower_irq();
|
|
}
|
|
}
|
|
|
|
static void esp_raise_drq(ESPState *s)
|
|
{
|
|
qemu_irq_raise(s->irq_data);
|
|
trace_esp_raise_drq();
|
|
}
|
|
|
|
static void esp_lower_drq(ESPState *s)
|
|
{
|
|
qemu_irq_lower(s->irq_data);
|
|
trace_esp_lower_drq();
|
|
}
|
|
|
|
void esp_dma_enable(ESPState *s, int irq, int level)
|
|
{
|
|
if (level) {
|
|
s->dma_enabled = 1;
|
|
trace_esp_dma_enable();
|
|
if (s->dma_cb) {
|
|
s->dma_cb(s);
|
|
s->dma_cb = NULL;
|
|
}
|
|
} else {
|
|
trace_esp_dma_disable();
|
|
s->dma_enabled = 0;
|
|
}
|
|
}
|
|
|
|
void esp_request_cancelled(SCSIRequest *req)
|
|
{
|
|
ESPState *s = req->hba_private;
|
|
|
|
if (req == s->current_req) {
|
|
scsi_req_unref(s->current_req);
|
|
s->current_req = NULL;
|
|
s->current_dev = NULL;
|
|
s->async_len = 0;
|
|
}
|
|
}
|
|
|
|
static void esp_fifo_push(Fifo8 *fifo, uint8_t val)
|
|
{
|
|
if (fifo8_num_used(fifo) == fifo->capacity) {
|
|
trace_esp_error_fifo_overrun();
|
|
return;
|
|
}
|
|
|
|
fifo8_push(fifo, val);
|
|
}
|
|
|
|
static uint8_t esp_fifo_pop(Fifo8 *fifo)
|
|
{
|
|
if (fifo8_is_empty(fifo)) {
|
|
return 0;
|
|
}
|
|
|
|
return fifo8_pop(fifo);
|
|
}
|
|
|
|
static uint32_t esp_fifo_pop_buf(Fifo8 *fifo, uint8_t *dest, int maxlen)
|
|
{
|
|
const uint8_t *buf;
|
|
uint32_t n, n2;
|
|
int len;
|
|
|
|
if (maxlen == 0) {
|
|
return 0;
|
|
}
|
|
|
|
len = maxlen;
|
|
buf = fifo8_pop_buf(fifo, len, &n);
|
|
if (dest) {
|
|
memcpy(dest, buf, n);
|
|
}
|
|
|
|
/* Add FIFO wraparound if needed */
|
|
len -= n;
|
|
len = MIN(len, fifo8_num_used(fifo));
|
|
if (len) {
|
|
buf = fifo8_pop_buf(fifo, len, &n2);
|
|
if (dest) {
|
|
memcpy(&dest[n], buf, n2);
|
|
}
|
|
n += n2;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
static uint32_t esp_get_tc(ESPState *s)
|
|
{
|
|
uint32_t dmalen;
|
|
|
|
dmalen = s->rregs[ESP_TCLO];
|
|
dmalen |= s->rregs[ESP_TCMID] << 8;
|
|
dmalen |= s->rregs[ESP_TCHI] << 16;
|
|
|
|
return dmalen;
|
|
}
|
|
|
|
static void esp_set_tc(ESPState *s, uint32_t dmalen)
|
|
{
|
|
uint32_t old_tc = esp_get_tc(s);
|
|
|
|
s->rregs[ESP_TCLO] = dmalen;
|
|
s->rregs[ESP_TCMID] = dmalen >> 8;
|
|
s->rregs[ESP_TCHI] = dmalen >> 16;
|
|
|
|
if (old_tc && dmalen == 0) {
|
|
s->rregs[ESP_RSTAT] |= STAT_TC;
|
|
}
|
|
}
|
|
|
|
static uint32_t esp_get_stc(ESPState *s)
|
|
{
|
|
uint32_t dmalen;
|
|
|
|
dmalen = s->wregs[ESP_TCLO];
|
|
dmalen |= s->wregs[ESP_TCMID] << 8;
|
|
dmalen |= s->wregs[ESP_TCHI] << 16;
|
|
|
|
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);
|
|
return val;
|
|
}
|
|
|
|
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);
|
|
|
|
dmalen--;
|
|
esp_set_tc(s, dmalen);
|
|
}
|
|
|
|
static int esp_select(ESPState *s)
|
|
{
|
|
int target;
|
|
|
|
target = s->wregs[ESP_WBUSID] & BUSID_DID;
|
|
|
|
s->ti_size = 0;
|
|
s->rregs[ESP_RSEQ] = SEQ_0;
|
|
|
|
if (s->current_req) {
|
|
/* Started a new command before the old one finished. Cancel it. */
|
|
scsi_req_cancel(s->current_req);
|
|
}
|
|
|
|
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;
|
|
esp_raise_irq(s);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Note that we deliberately don't raise the IRQ here: this will be done
|
|
* either in esp_transfer_data() or esp_command_complete()
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
static void esp_do_dma(ESPState *s);
|
|
static void esp_do_nodma(ESPState *s);
|
|
|
|
static void do_command_phase(ESPState *s)
|
|
{
|
|
uint32_t cmdlen;
|
|
int32_t datalen;
|
|
SCSIDevice *current_lun;
|
|
uint8_t buf[ESP_CMDFIFO_SZ];
|
|
|
|
trace_esp_do_command_phase(s->lun);
|
|
cmdlen = fifo8_num_used(&s->cmdfifo);
|
|
if (!cmdlen || !s->current_dev) {
|
|
return;
|
|
}
|
|
esp_fifo_pop_buf(&s->cmdfifo, buf, cmdlen);
|
|
|
|
current_lun = scsi_device_find(&s->bus, 0, s->current_dev->id, s->lun);
|
|
if (!current_lun) {
|
|
/* 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;
|
|
}
|
|
|
|
s->current_req = scsi_req_new(current_lun, 0, s->lun, buf, cmdlen, s);
|
|
datalen = scsi_req_enqueue(s->current_req);
|
|
s->ti_size = datalen;
|
|
fifo8_reset(&s->cmdfifo);
|
|
s->data_ready = false;
|
|
if (datalen != 0) {
|
|
/*
|
|
* Switch to DATA phase but wait until initial data xfer is
|
|
* complete before raising the command completion interrupt
|
|
*/
|
|
if (datalen > 0) {
|
|
esp_set_phase(s, STAT_DI);
|
|
} else {
|
|
esp_set_phase(s, STAT_DO);
|
|
}
|
|
scsi_req_continue(s->current_req);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void do_message_phase(ESPState *s)
|
|
{
|
|
if (s->cmdfifo_cdb_offset) {
|
|
uint8_t message = esp_fifo_pop(&s->cmdfifo);
|
|
|
|
trace_esp_do_identify(message);
|
|
s->lun = message & 7;
|
|
s->cmdfifo_cdb_offset--;
|
|
}
|
|
|
|
/* 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);
|
|
s->cmdfifo_cdb_offset = 0;
|
|
}
|
|
}
|
|
|
|
static void do_cmd(ESPState *s)
|
|
{
|
|
do_message_phase(s);
|
|
assert(s->cmdfifo_cdb_offset == 0);
|
|
do_command_phase(s);
|
|
}
|
|
|
|
static void handle_satn(ESPState *s)
|
|
{
|
|
if (s->dma && !s->dma_enabled) {
|
|
s->dma_cb = handle_satn;
|
|
return;
|
|
}
|
|
|
|
if (esp_select(s) < 0) {
|
|
return;
|
|
}
|
|
|
|
esp_set_phase(s, STAT_MO);
|
|
|
|
if (s->dma) {
|
|
esp_do_dma(s);
|
|
} else {
|
|
esp_do_nodma(s);
|
|
}
|
|
}
|
|
|
|
static void handle_s_without_atn(ESPState *s)
|
|
{
|
|
if (s->dma && !s->dma_enabled) {
|
|
s->dma_cb = handle_s_without_atn;
|
|
return;
|
|
}
|
|
|
|
if (esp_select(s) < 0) {
|
|
return;
|
|
}
|
|
|
|
esp_set_phase(s, STAT_CD);
|
|
s->cmdfifo_cdb_offset = 0;
|
|
|
|
if (s->dma) {
|
|
esp_do_dma(s);
|
|
} else {
|
|
esp_do_nodma(s);
|
|
}
|
|
}
|
|
|
|
static void handle_satn_stop(ESPState *s)
|
|
{
|
|
if (s->dma && !s->dma_enabled) {
|
|
s->dma_cb = handle_satn_stop;
|
|
return;
|
|
}
|
|
|
|
if (esp_select(s) < 0) {
|
|
return;
|
|
}
|
|
|
|
esp_set_phase(s, STAT_MO);
|
|
s->cmdfifo_cdb_offset = 0;
|
|
|
|
if (s->dma) {
|
|
esp_do_dma(s);
|
|
} else {
|
|
esp_do_nodma(s);
|
|
}
|
|
}
|
|
|
|
static void handle_pad(ESPState *s)
|
|
{
|
|
if (s->dma) {
|
|
esp_do_dma(s);
|
|
} else {
|
|
esp_do_nodma(s);
|
|
}
|
|
}
|
|
|
|
static void write_response(ESPState *s)
|
|
{
|
|
trace_esp_write_response(s->status);
|
|
|
|
if (s->dma) {
|
|
esp_do_dma(s);
|
|
} else {
|
|
esp_do_nodma(s);
|
|
}
|
|
}
|
|
|
|
static int esp_cdb_length(ESPState *s)
|
|
{
|
|
const uint8_t *pbuf;
|
|
int cmdlen, len;
|
|
|
|
cmdlen = fifo8_num_used(&s->cmdfifo);
|
|
if (cmdlen < s->cmdfifo_cdb_offset) {
|
|
return 0;
|
|
}
|
|
|
|
pbuf = fifo8_peek_buf(&s->cmdfifo, cmdlen, NULL);
|
|
len = scsi_cdb_length((uint8_t *)&pbuf[s->cmdfifo_cdb_offset]);
|
|
|
|
return len;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void esp_do_dma(ESPState *s)
|
|
{
|
|
uint32_t len, cmdlen;
|
|
uint8_t buf[ESP_CMDFIFO_SZ];
|
|
|
|
len = esp_get_tc(s);
|
|
|
|
switch (esp_get_phase(s)) {
|
|
case STAT_MO:
|
|
if (s->dma_memory_read) {
|
|
len = MIN(len, fifo8_num_free(&s->cmdfifo));
|
|
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 = MIN(fifo8_num_free(&s->cmdfifo), len);
|
|
esp_raise_drq(s);
|
|
}
|
|
|
|
fifo8_push_all(&s->cmdfifo, buf, len);
|
|
s->cmdfifo_cdb_offset += len;
|
|
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_SELATN | CMD_DMA:
|
|
if (fifo8_num_used(&s->cmdfifo) >= 1) {
|
|
/* First byte received, switch to command phase */
|
|
esp_set_phase(s, STAT_CD);
|
|
s->rregs[ESP_RSEQ] = SEQ_CD;
|
|
s->cmdfifo_cdb_offset = 1;
|
|
|
|
if (fifo8_num_used(&s->cmdfifo) > 1) {
|
|
/* Process any additional command phase data */
|
|
esp_do_dma(s);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_SELATNS | CMD_DMA:
|
|
if (fifo8_num_used(&s->cmdfifo) == 1) {
|
|
/* First byte received, stop in message out phase */
|
|
s->rregs[ESP_RSEQ] = SEQ_MO;
|
|
s->cmdfifo_cdb_offset = 1;
|
|
|
|
/* Raise command completion interrupt */
|
|
s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
|
|
esp_raise_irq(s);
|
|
}
|
|
break;
|
|
|
|
case CMD_TI | CMD_DMA:
|
|
/* ATN remains asserted until TC == 0 */
|
|
if (esp_get_tc(s) == 0) {
|
|
esp_set_phase(s, STAT_CD);
|
|
s->rregs[ESP_CMD] = 0;
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
esp_raise_irq(s);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case STAT_CD:
|
|
cmdlen = fifo8_num_used(&s->cmdfifo);
|
|
trace_esp_do_dma(cmdlen, len);
|
|
if (s->dma_memory_read) {
|
|
len = MIN(len, fifo8_num_free(&s->cmdfifo));
|
|
s->dma_memory_read(s->dma_opaque, buf, len);
|
|
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 = 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;
|
|
if (esp_get_tc(s) == 0) {
|
|
/* Command has been received */
|
|
do_cmd(s);
|
|
}
|
|
break;
|
|
|
|
case STAT_DO:
|
|
if (!s->current_req) {
|
|
return;
|
|
}
|
|
if (s->async_len == 0 && esp_get_tc(s) && s->ti_size) {
|
|
/* Defer until data is available. */
|
|
return;
|
|
}
|
|
if (len > s->async_len) {
|
|
len = s->async_len;
|
|
}
|
|
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_TI | CMD_DMA:
|
|
if (s->dma_memory_read) {
|
|
s->dma_memory_read(s->dma_opaque, s->async_buf, len);
|
|
esp_set_tc(s, esp_get_tc(s) - len);
|
|
} else {
|
|
/* 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);
|
|
}
|
|
|
|
s->async_buf += len;
|
|
s->async_len -= len;
|
|
s->ti_size += len;
|
|
break;
|
|
|
|
case CMD_PAD | CMD_DMA:
|
|
/* Copy TC zero bytes into the incoming stream */
|
|
if (!s->dma_memory_read) {
|
|
len = MIN(s->async_len, ESP_FIFO_SZ);
|
|
len = MIN(len, fifo8_num_free(&s->fifo));
|
|
}
|
|
|
|
memset(s->async_buf, 0, len);
|
|
|
|
s->async_buf += len;
|
|
s->async_len -= len;
|
|
s->ti_size += len;
|
|
break;
|
|
}
|
|
|
|
if (s->async_len == 0 && fifo8_num_used(&s->fifo) < 2) {
|
|
/* Defer until the scsi layer has completed */
|
|
scsi_req_continue(s->current_req);
|
|
return;
|
|
}
|
|
|
|
esp_dma_ti_check(s);
|
|
break;
|
|
|
|
case STAT_DI:
|
|
if (!s->current_req) {
|
|
return;
|
|
}
|
|
if (s->async_len == 0 && esp_get_tc(s) && s->ti_size) {
|
|
/* Defer until data is available. */
|
|
return;
|
|
}
|
|
if (len > s->async_len) {
|
|
len = s->async_len;
|
|
}
|
|
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_TI | CMD_DMA:
|
|
if (s->dma_memory_write) {
|
|
s->dma_memory_write(s->dma_opaque, s->async_buf, len);
|
|
} 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);
|
|
}
|
|
|
|
s->async_buf += len;
|
|
s->async_len -= len;
|
|
s->ti_size -= len;
|
|
esp_set_tc(s, esp_get_tc(s) - len);
|
|
break;
|
|
|
|
case CMD_PAD | CMD_DMA:
|
|
/* Drop TC bytes from the incoming stream */
|
|
if (!s->dma_memory_write) {
|
|
len = MIN(len, fifo8_num_free(&s->fifo));
|
|
}
|
|
|
|
s->async_buf += len;
|
|
s->async_len -= len;
|
|
s->ti_size -= len;
|
|
esp_set_tc(s, esp_get_tc(s) - len);
|
|
break;
|
|
}
|
|
|
|
if (s->async_len == 0 && s->ti_size == 0 && esp_get_tc(s)) {
|
|
/* If the guest underflows TC then terminate SCSI request */
|
|
scsi_req_continue(s->current_req);
|
|
return;
|
|
}
|
|
|
|
if (s->async_len == 0 && fifo8_num_used(&s->fifo) < 2) {
|
|
/* Defer until the scsi layer has completed */
|
|
scsi_req_continue(s->current_req);
|
|
return;
|
|
}
|
|
|
|
esp_dma_ti_check(s);
|
|
break;
|
|
|
|
case STAT_ST:
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_ICCS | CMD_DMA:
|
|
len = MIN(len, 1);
|
|
|
|
if (len) {
|
|
buf[0] = s->status;
|
|
|
|
if (s->dma_memory_write) {
|
|
s->dma_memory_write(s->dma_opaque, buf, len);
|
|
} else {
|
|
fifo8_push_all(&s->fifo, buf, len);
|
|
}
|
|
|
|
esp_set_tc(s, esp_get_tc(s) - len);
|
|
esp_set_phase(s, STAT_MI);
|
|
|
|
if (esp_get_tc(s) > 0) {
|
|
/* Process any message in phase data */
|
|
esp_do_dma(s);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Consume remaining data if the guest underflows TC */
|
|
if (fifo8_num_used(&s->fifo) < 2) {
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
esp_raise_irq(s);
|
|
esp_lower_drq(s);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case STAT_MI:
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_ICCS | CMD_DMA:
|
|
len = MIN(len, 1);
|
|
|
|
if (len) {
|
|
buf[0] = 0;
|
|
|
|
if (s->dma_memory_write) {
|
|
s->dma_memory_write(s->dma_opaque, buf, len);
|
|
} else {
|
|
fifo8_push_all(&s->fifo, buf, len);
|
|
}
|
|
|
|
esp_set_tc(s, esp_get_tc(s) - len);
|
|
|
|
/* Raise end of command interrupt */
|
|
s->rregs[ESP_RINTR] |= INTR_FC;
|
|
esp_raise_irq(s);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void esp_nodma_ti_dataout(ESPState *s)
|
|
{
|
|
int len;
|
|
|
|
if (!s->current_req) {
|
|
return;
|
|
}
|
|
if (s->async_len == 0) {
|
|
/* Defer until data is available. */
|
|
return;
|
|
}
|
|
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);
|
|
s->async_buf += len;
|
|
s->async_len -= len;
|
|
s->ti_size += len;
|
|
|
|
if (s->async_len == 0) {
|
|
scsi_req_continue(s->current_req);
|
|
return;
|
|
}
|
|
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
esp_raise_irq(s);
|
|
}
|
|
|
|
static void esp_do_nodma(ESPState *s)
|
|
{
|
|
uint8_t buf[ESP_FIFO_SZ];
|
|
uint32_t cmdlen;
|
|
int len;
|
|
|
|
switch (esp_get_phase(s)) {
|
|
case STAT_MO:
|
|
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 = MIN(fifo8_num_free(&s->cmdfifo), len);
|
|
fifo8_push_all(&s->cmdfifo, buf, len);
|
|
|
|
if (fifo8_num_used(&s->cmdfifo) >= 1) {
|
|
/* First byte received, switch to command phase */
|
|
esp_set_phase(s, STAT_CD);
|
|
s->rregs[ESP_RSEQ] = SEQ_CD;
|
|
s->cmdfifo_cdb_offset = 1;
|
|
|
|
if (fifo8_num_used(&s->cmdfifo) > 1) {
|
|
/* Process any additional command phase data */
|
|
esp_do_nodma(s);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_SELATNS:
|
|
/* Copy one byte from FIFO into cmdfifo */
|
|
len = esp_fifo_pop_buf(&s->fifo, buf, 1);
|
|
len = MIN(fifo8_num_free(&s->cmdfifo), len);
|
|
fifo8_push_all(&s->cmdfifo, buf, len);
|
|
|
|
if (fifo8_num_used(&s->cmdfifo) >= 1) {
|
|
/* First byte received, stop in message out phase */
|
|
s->rregs[ESP_RSEQ] = SEQ_MO;
|
|
s->cmdfifo_cdb_offset = 1;
|
|
|
|
/* Raise command completion interrupt */
|
|
s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
|
|
esp_raise_irq(s);
|
|
}
|
|
break;
|
|
|
|
case CMD_TI:
|
|
/* Copy FIFO into cmdfifo */
|
|
len = esp_fifo_pop_buf(&s->fifo, buf, fifo8_num_used(&s->fifo));
|
|
len = MIN(fifo8_num_free(&s->cmdfifo), len);
|
|
fifo8_push_all(&s->cmdfifo, buf, len);
|
|
|
|
/* ATN remains asserted until FIFO empty */
|
|
s->cmdfifo_cdb_offset = fifo8_num_used(&s->cmdfifo);
|
|
esp_set_phase(s, STAT_CD);
|
|
s->rregs[ESP_CMD] = 0;
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
esp_raise_irq(s);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case STAT_CD:
|
|
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 = MIN(fifo8_num_free(&s->cmdfifo), len);
|
|
fifo8_push_all(&s->cmdfifo, buf, len);
|
|
|
|
cmdlen = fifo8_num_used(&s->cmdfifo);
|
|
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);
|
|
} else {
|
|
/*
|
|
* If data was transferred from the FIFO then raise bus
|
|
* service interrupt to indicate transfer complete. Otherwise
|
|
* defer until the next FIFO write.
|
|
*/
|
|
if (len) {
|
|
/* Raise interrupt to indicate transfer complete */
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
esp_raise_irq(s);
|
|
}
|
|
}
|
|
break;
|
|
|
|
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 = 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);
|
|
}
|
|
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 = MIN(fifo8_num_free(&s->cmdfifo), len);
|
|
fifo8_push_all(&s->cmdfifo, buf, len);
|
|
|
|
do_cmd(s);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case STAT_DO:
|
|
/* Accumulate data in FIFO until non-DMA TI is executed */
|
|
break;
|
|
|
|
case STAT_DI:
|
|
if (!s->current_req) {
|
|
return;
|
|
}
|
|
if (s->async_len == 0) {
|
|
/* Defer until data is available. */
|
|
return;
|
|
}
|
|
if (fifo8_is_empty(&s->fifo)) {
|
|
fifo8_push(&s->fifo, s->async_buf[0]);
|
|
s->async_buf++;
|
|
s->async_len--;
|
|
s->ti_size--;
|
|
}
|
|
|
|
if (s->async_len == 0) {
|
|
scsi_req_continue(s->current_req);
|
|
return;
|
|
}
|
|
|
|
/* If preloading the FIFO, defer until TI command issued */
|
|
if (s->rregs[ESP_CMD] != CMD_TI) {
|
|
return;
|
|
}
|
|
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
esp_raise_irq(s);
|
|
break;
|
|
|
|
case STAT_ST:
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_ICCS:
|
|
fifo8_push(&s->fifo, s->status);
|
|
esp_set_phase(s, STAT_MI);
|
|
|
|
/* Process any message in phase data */
|
|
esp_do_nodma(s);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case STAT_MI:
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_ICCS:
|
|
fifo8_push(&s->fifo, 0);
|
|
|
|
/* Raise end of command interrupt */
|
|
s->rregs[ESP_RINTR] |= INTR_FC;
|
|
esp_raise_irq(s);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void esp_command_complete(SCSIRequest *req, size_t resid)
|
|
{
|
|
ESPState *s = req->hba_private;
|
|
int to_device = (esp_get_phase(s) == STAT_DO);
|
|
|
|
trace_esp_command_complete();
|
|
|
|
/*
|
|
* Non-DMA transfers from the target will leave the last byte in
|
|
* the FIFO so don't reset ti_size in this case
|
|
*/
|
|
if (s->dma || to_device) {
|
|
if (s->ti_size != 0) {
|
|
trace_esp_command_complete_unexpected();
|
|
}
|
|
}
|
|
|
|
s->async_len = 0;
|
|
if (req->status) {
|
|
trace_esp_command_complete_fail();
|
|
}
|
|
s->status = req->status;
|
|
|
|
/*
|
|
* Switch to status phase. For non-DMA transfers from the target the last
|
|
* byte is still in the FIFO
|
|
*/
|
|
s->ti_size = 0;
|
|
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_SEL | CMD_DMA:
|
|
case CMD_SEL:
|
|
case CMD_SELATN | CMD_DMA:
|
|
case CMD_SELATN:
|
|
/*
|
|
* No data phase for sequencer command so raise deferred bus service
|
|
* and function complete interrupt
|
|
*/
|
|
s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
|
|
s->rregs[ESP_RSEQ] = SEQ_CD;
|
|
break;
|
|
|
|
case CMD_TI | CMD_DMA:
|
|
case CMD_TI:
|
|
s->rregs[ESP_CMD] = 0;
|
|
break;
|
|
}
|
|
|
|
/* Raise bus service interrupt to indicate change to STATUS phase */
|
|
esp_set_phase(s, STAT_ST);
|
|
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;
|
|
s->current_dev = NULL;
|
|
}
|
|
}
|
|
|
|
void esp_transfer_data(SCSIRequest *req, uint32_t len)
|
|
{
|
|
ESPState *s = req->hba_private;
|
|
uint32_t dmalen = esp_get_tc(s);
|
|
|
|
trace_esp_transfer_data(dmalen, s->ti_size);
|
|
s->async_len = len;
|
|
s->async_buf = scsi_req_get_buf(req);
|
|
|
|
if (!s->data_ready) {
|
|
s->data_ready = true;
|
|
|
|
switch (s->rregs[ESP_CMD]) {
|
|
case CMD_SEL | CMD_DMA:
|
|
case CMD_SEL:
|
|
case CMD_SELATN | CMD_DMA:
|
|
case CMD_SELATN:
|
|
/*
|
|
* Initial incoming data xfer is complete for sequencer command
|
|
* so raise deferred bus service and function complete interrupt
|
|
*/
|
|
s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
|
|
s->rregs[ESP_RSEQ] = SEQ_CD;
|
|
break;
|
|
|
|
case CMD_SELATNS | CMD_DMA:
|
|
case CMD_SELATNS:
|
|
/*
|
|
* Initial incoming data xfer is complete so raise command
|
|
* completion interrupt
|
|
*/
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
s->rregs[ESP_RSEQ] = SEQ_MO;
|
|
break;
|
|
|
|
case CMD_TI | CMD_DMA:
|
|
case CMD_TI:
|
|
/*
|
|
* Bus service interrupt raised because of initial change to
|
|
* DATA phase
|
|
*/
|
|
s->rregs[ESP_CMD] = 0;
|
|
s->rregs[ESP_RINTR] |= INTR_BS;
|
|
break;
|
|
}
|
|
|
|
esp_raise_irq(s);
|
|
}
|
|
|
|
/*
|
|
* Always perform the initial transfer upon reception of the next TI
|
|
* command to ensure the DMA/non-DMA status of the command is correct.
|
|
* It is not possible to use s->dma directly in the section below as
|
|
* some OSs send non-DMA NOP commands after a DMA transfer. Hence if the
|
|
* async data transfer is delayed then s->dma is set incorrectly.
|
|
*/
|
|
|
|
if (s->rregs[ESP_CMD] == (CMD_TI | CMD_DMA)) {
|
|
/* When the SCSI layer returns more data, raise deferred INTR_BS */
|
|
esp_dma_ti_check(s);
|
|
|
|
esp_do_dma(s);
|
|
} else if (s->rregs[ESP_CMD] == CMD_TI) {
|
|
esp_do_nodma(s);
|
|
}
|
|
}
|
|
|
|
static void handle_ti(ESPState *s)
|
|
{
|
|
uint32_t dmalen;
|
|
|
|
if (s->dma && !s->dma_enabled) {
|
|
s->dma_cb = handle_ti;
|
|
return;
|
|
}
|
|
|
|
if (s->dma) {
|
|
dmalen = esp_get_tc(s);
|
|
trace_esp_handle_ti(dmalen);
|
|
esp_do_dma(s);
|
|
} else {
|
|
trace_esp_handle_ti(s->ti_size);
|
|
esp_do_nodma(s);
|
|
|
|
if (esp_get_phase(s) == STAT_DO) {
|
|
esp_nodma_ti_dataout(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
void esp_hard_reset(ESPState *s)
|
|
{
|
|
memset(s->rregs, 0, ESP_REGS);
|
|
memset(s->wregs, 0, ESP_REGS);
|
|
s->tchi_written = 0;
|
|
s->ti_size = 0;
|
|
s->async_len = 0;
|
|
fifo8_reset(&s->fifo);
|
|
fifo8_reset(&s->cmdfifo);
|
|
s->dma = 0;
|
|
s->dma_cb = NULL;
|
|
|
|
s->rregs[ESP_CFG1] = 7;
|
|
}
|
|
|
|
static void esp_soft_reset(ESPState *s)
|
|
{
|
|
qemu_irq_lower(s->irq);
|
|
qemu_irq_lower(s->irq_data);
|
|
esp_hard_reset(s);
|
|
}
|
|
|
|
static void esp_bus_reset(ESPState *s)
|
|
{
|
|
bus_cold_reset(BUS(&s->bus));
|
|
}
|
|
|
|
static void parent_esp_reset(ESPState *s, int irq, int level)
|
|
{
|
|
if (level) {
|
|
esp_soft_reset(s);
|
|
}
|
|
}
|
|
|
|
static void esp_run_cmd(ESPState *s)
|
|
{
|
|
uint8_t cmd = s->rregs[ESP_CMD];
|
|
|
|
if (cmd & CMD_DMA) {
|
|
s->dma = 1;
|
|
/* Reload DMA counter. */
|
|
if (esp_get_stc(s) == 0) {
|
|
esp_set_tc(s, 0x10000);
|
|
} else {
|
|
esp_set_tc(s, esp_get_stc(s));
|
|
}
|
|
} else {
|
|
s->dma = 0;
|
|
}
|
|
switch (cmd & CMD_CMD) {
|
|
case CMD_NOP:
|
|
trace_esp_mem_writeb_cmd_nop(cmd);
|
|
break;
|
|
case CMD_FLUSH:
|
|
trace_esp_mem_writeb_cmd_flush(cmd);
|
|
fifo8_reset(&s->fifo);
|
|
break;
|
|
case CMD_RESET:
|
|
trace_esp_mem_writeb_cmd_reset(cmd);
|
|
esp_soft_reset(s);
|
|
break;
|
|
case CMD_BUSRESET:
|
|
trace_esp_mem_writeb_cmd_bus_reset(cmd);
|
|
esp_bus_reset(s);
|
|
if (!(s->wregs[ESP_CFG1] & CFG1_RESREPT)) {
|
|
s->rregs[ESP_RINTR] |= INTR_RST;
|
|
esp_raise_irq(s);
|
|
}
|
|
break;
|
|
case CMD_TI:
|
|
trace_esp_mem_writeb_cmd_ti(cmd);
|
|
handle_ti(s);
|
|
break;
|
|
case CMD_ICCS:
|
|
trace_esp_mem_writeb_cmd_iccs(cmd);
|
|
write_response(s);
|
|
break;
|
|
case CMD_MSGACC:
|
|
trace_esp_mem_writeb_cmd_msgacc(cmd);
|
|
s->rregs[ESP_RINTR] |= INTR_DC;
|
|
s->rregs[ESP_RSEQ] = 0;
|
|
s->rregs[ESP_RFLAGS] = 0;
|
|
esp_raise_irq(s);
|
|
break;
|
|
case CMD_PAD:
|
|
trace_esp_mem_writeb_cmd_pad(cmd);
|
|
handle_pad(s);
|
|
break;
|
|
case CMD_SATN:
|
|
trace_esp_mem_writeb_cmd_satn(cmd);
|
|
break;
|
|
case CMD_RSTATN:
|
|
trace_esp_mem_writeb_cmd_rstatn(cmd);
|
|
break;
|
|
case CMD_SEL:
|
|
trace_esp_mem_writeb_cmd_sel(cmd);
|
|
handle_s_without_atn(s);
|
|
break;
|
|
case CMD_SELATN:
|
|
trace_esp_mem_writeb_cmd_selatn(cmd);
|
|
handle_satn(s);
|
|
break;
|
|
case CMD_SELATNS:
|
|
trace_esp_mem_writeb_cmd_selatns(cmd);
|
|
handle_satn_stop(s);
|
|
break;
|
|
case CMD_ENSEL:
|
|
trace_esp_mem_writeb_cmd_ensel(cmd);
|
|
s->rregs[ESP_RINTR] = 0;
|
|
break;
|
|
case CMD_DISSEL:
|
|
trace_esp_mem_writeb_cmd_dissel(cmd);
|
|
s->rregs[ESP_RINTR] = 0;
|
|
esp_raise_irq(s);
|
|
break;
|
|
default:
|
|
trace_esp_error_unhandled_command(cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint64_t esp_reg_read(ESPState *s, uint32_t saddr)
|
|
{
|
|
uint32_t val;
|
|
|
|
switch (saddr) {
|
|
case ESP_FIFO:
|
|
s->rregs[ESP_FIFO] = esp_fifo_pop(&s->fifo);
|
|
val = s->rregs[ESP_FIFO];
|
|
break;
|
|
case ESP_RINTR:
|
|
/*
|
|
* Clear sequence step, interrupt register and all status bits
|
|
* except TC
|
|
*/
|
|
val = s->rregs[ESP_RINTR];
|
|
s->rregs[ESP_RINTR] = 0;
|
|
esp_lower_irq(s);
|
|
s->rregs[ESP_RSTAT] &= STAT_TC | 7;
|
|
/*
|
|
* According to the datasheet ESP_RSEQ should be cleared, but as the
|
|
* emulation currently defers information transfers to the next TI
|
|
* command leave it for now so that pedantic guests such as the old
|
|
* Linux 2.6 driver see the correct flags before the next SCSI phase
|
|
* transition.
|
|
*
|
|
* s->rregs[ESP_RSEQ] = SEQ_0;
|
|
*/
|
|
break;
|
|
case ESP_TCHI:
|
|
/* Return the unique id if the value has never been written */
|
|
if (!s->tchi_written) {
|
|
val = s->chip_id;
|
|
} else {
|
|
val = s->rregs[saddr];
|
|
}
|
|
break;
|
|
case ESP_RFLAGS:
|
|
/* Bottom 5 bits indicate number of bytes in FIFO */
|
|
val = fifo8_num_used(&s->fifo);
|
|
break;
|
|
default:
|
|
val = s->rregs[saddr];
|
|
break;
|
|
}
|
|
|
|
trace_esp_mem_readb(saddr, val);
|
|
return val;
|
|
}
|
|
|
|
void esp_reg_write(ESPState *s, uint32_t saddr, uint64_t val)
|
|
{
|
|
trace_esp_mem_writeb(saddr, s->wregs[saddr], val);
|
|
switch (saddr) {
|
|
case ESP_TCHI:
|
|
s->tchi_written = true;
|
|
/* fall through */
|
|
case ESP_TCLO:
|
|
case ESP_TCMID:
|
|
s->rregs[ESP_RSTAT] &= ~STAT_TC;
|
|
break;
|
|
case ESP_FIFO:
|
|
if (!fifo8_is_full(&s->fifo)) {
|
|
esp_fifo_push(&s->fifo, val);
|
|
}
|
|
esp_do_nodma(s);
|
|
break;
|
|
case ESP_CMD:
|
|
s->rregs[saddr] = val;
|
|
esp_run_cmd(s);
|
|
break;
|
|
case ESP_WBUSID ... ESP_WSYNO:
|
|
break;
|
|
case ESP_CFG1:
|
|
case ESP_CFG2: case ESP_CFG3:
|
|
case ESP_RES3: case ESP_RES4:
|
|
s->rregs[saddr] = val;
|
|
break;
|
|
case ESP_WCCF ... ESP_WTEST:
|
|
break;
|
|
default:
|
|
trace_esp_error_invalid_write(val, saddr);
|
|
return;
|
|
}
|
|
s->wregs[saddr] = val;
|
|
}
|
|
|
|
static bool esp_mem_accepts(void *opaque, hwaddr addr,
|
|
unsigned size, bool is_write,
|
|
MemTxAttrs attrs)
|
|
{
|
|
return (size == 1) || (is_write && size == 4);
|
|
}
|
|
|
|
static bool esp_is_before_version_5(void *opaque, int version_id)
|
|
{
|
|
ESPState *s = ESP(opaque);
|
|
|
|
version_id = MIN(version_id, s->mig_version_id);
|
|
return version_id < 5;
|
|
}
|
|
|
|
static bool esp_is_version_5(void *opaque, int version_id)
|
|
{
|
|
ESPState *s = ESP(opaque);
|
|
|
|
version_id = MIN(version_id, s->mig_version_id);
|
|
return version_id >= 5;
|
|
}
|
|
|
|
static bool esp_is_version_6(void *opaque, int version_id)
|
|
{
|
|
ESPState *s = ESP(opaque);
|
|
|
|
version_id = MIN(version_id, s->mig_version_id);
|
|
return version_id >= 6;
|
|
}
|
|
|
|
static bool esp_is_between_version_5_and_6(void *opaque, int version_id)
|
|
{
|
|
ESPState *s = ESP(opaque);
|
|
|
|
version_id = MIN(version_id, s->mig_version_id);
|
|
return version_id >= 5 && version_id <= 6;
|
|
}
|
|
|
|
int esp_pre_save(void *opaque)
|
|
{
|
|
ESPState *s = ESP(object_resolve_path_component(
|
|
OBJECT(opaque), "esp"));
|
|
|
|
s->mig_version_id = vmstate_esp.version_id;
|
|
return 0;
|
|
}
|
|
|
|
static int esp_post_load(void *opaque, int version_id)
|
|
{
|
|
ESPState *s = ESP(opaque);
|
|
int len, i;
|
|
|
|
version_id = MIN(version_id, s->mig_version_id);
|
|
|
|
if (version_id < 5) {
|
|
esp_set_tc(s, s->mig_dma_left);
|
|
|
|
/* Migrate ti_buf to fifo */
|
|
len = s->mig_ti_wptr - s->mig_ti_rptr;
|
|
for (i = 0; i < len; i++) {
|
|
fifo8_push(&s->fifo, s->mig_ti_buf[i]);
|
|
}
|
|
|
|
/* Migrate cmdbuf to cmdfifo */
|
|
for (i = 0; i < s->mig_cmdlen; i++) {
|
|
fifo8_push(&s->cmdfifo, s->mig_cmdbuf[i]);
|
|
}
|
|
}
|
|
|
|
s->mig_version_id = vmstate_esp.version_id;
|
|
return 0;
|
|
}
|
|
|
|
const VMStateDescription vmstate_esp = {
|
|
.name = "esp",
|
|
.version_id = 7,
|
|
.minimum_version_id = 3,
|
|
.post_load = esp_post_load,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_BUFFER(rregs, ESPState),
|
|
VMSTATE_BUFFER(wregs, ESPState),
|
|
VMSTATE_INT32(ti_size, ESPState),
|
|
VMSTATE_UINT32_TEST(mig_ti_rptr, ESPState, esp_is_before_version_5),
|
|
VMSTATE_UINT32_TEST(mig_ti_wptr, ESPState, esp_is_before_version_5),
|
|
VMSTATE_BUFFER_TEST(mig_ti_buf, ESPState, esp_is_before_version_5),
|
|
VMSTATE_UINT32(status, ESPState),
|
|
VMSTATE_UINT32_TEST(mig_deferred_status, ESPState,
|
|
esp_is_before_version_5),
|
|
VMSTATE_BOOL_TEST(mig_deferred_complete, ESPState,
|
|
esp_is_before_version_5),
|
|
VMSTATE_UINT32(dma, ESPState),
|
|
VMSTATE_STATIC_BUFFER(mig_cmdbuf, ESPState, 0,
|
|
esp_is_before_version_5, 0, 16),
|
|
VMSTATE_STATIC_BUFFER(mig_cmdbuf, ESPState, 4,
|
|
esp_is_before_version_5, 16,
|
|
sizeof(typeof_field(ESPState, mig_cmdbuf))),
|
|
VMSTATE_UINT32_TEST(mig_cmdlen, ESPState, esp_is_before_version_5),
|
|
VMSTATE_UINT32(do_cmd, ESPState),
|
|
VMSTATE_UINT32_TEST(mig_dma_left, ESPState, esp_is_before_version_5),
|
|
VMSTATE_BOOL_TEST(data_ready, ESPState, esp_is_version_5),
|
|
VMSTATE_UINT8_TEST(cmdfifo_cdb_offset, ESPState, esp_is_version_5),
|
|
VMSTATE_FIFO8_TEST(fifo, ESPState, esp_is_version_5),
|
|
VMSTATE_FIFO8_TEST(cmdfifo, ESPState, esp_is_version_5),
|
|
VMSTATE_UINT8_TEST(mig_ti_cmd, ESPState,
|
|
esp_is_between_version_5_and_6),
|
|
VMSTATE_UINT8_TEST(lun, ESPState, esp_is_version_6),
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
};
|
|
|
|
static void sysbus_esp_mem_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned int size)
|
|
{
|
|
SysBusESPState *sysbus = opaque;
|
|
ESPState *s = ESP(&sysbus->esp);
|
|
uint32_t saddr;
|
|
|
|
saddr = addr >> sysbus->it_shift;
|
|
esp_reg_write(s, saddr, val);
|
|
}
|
|
|
|
static uint64_t sysbus_esp_mem_read(void *opaque, hwaddr addr,
|
|
unsigned int size)
|
|
{
|
|
SysBusESPState *sysbus = opaque;
|
|
ESPState *s = ESP(&sysbus->esp);
|
|
uint32_t saddr;
|
|
|
|
saddr = addr >> sysbus->it_shift;
|
|
return esp_reg_read(s, saddr);
|
|
}
|
|
|
|
static const MemoryRegionOps sysbus_esp_mem_ops = {
|
|
.read = sysbus_esp_mem_read,
|
|
.write = sysbus_esp_mem_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
.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 = ESP(&sysbus->esp);
|
|
|
|
trace_esp_pdma_write(size);
|
|
|
|
switch (size) {
|
|
case 1:
|
|
esp_pdma_write(s, val);
|
|
break;
|
|
case 2:
|
|
esp_pdma_write(s, val >> 8);
|
|
esp_pdma_write(s, val);
|
|
break;
|
|
}
|
|
esp_do_dma(s);
|
|
}
|
|
|
|
static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
|
|
unsigned int size)
|
|
{
|
|
SysBusESPState *sysbus = opaque;
|
|
ESPState *s = ESP(&sysbus->esp);
|
|
uint64_t val = 0;
|
|
|
|
trace_esp_pdma_read(size);
|
|
|
|
switch (size) {
|
|
case 1:
|
|
val = esp_pdma_read(s);
|
|
break;
|
|
case 2:
|
|
val = esp_pdma_read(s);
|
|
val = (val << 8) | esp_pdma_read(s);
|
|
break;
|
|
}
|
|
esp_do_dma(s);
|
|
return val;
|
|
}
|
|
|
|
static void *esp_load_request(QEMUFile *f, SCSIRequest *req)
|
|
{
|
|
ESPState *s = container_of(req->bus, ESPState, bus);
|
|
|
|
scsi_req_ref(req);
|
|
s->current_req = req;
|
|
return s;
|
|
}
|
|
|
|
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 = 4,
|
|
.impl.min_access_size = 1,
|
|
.impl.max_access_size = 2,
|
|
};
|
|
|
|
static const struct SCSIBusInfo esp_scsi_info = {
|
|
.tcq = false,
|
|
.max_target = ESP_MAX_DEVS,
|
|
.max_lun = 7,
|
|
|
|
.load_request = esp_load_request,
|
|
.transfer_data = esp_transfer_data,
|
|
.complete = esp_command_complete,
|
|
.cancel = esp_request_cancelled
|
|
};
|
|
|
|
static void sysbus_esp_gpio_demux(void *opaque, int irq, int level)
|
|
{
|
|
SysBusESPState *sysbus = SYSBUS_ESP(opaque);
|
|
ESPState *s = ESP(&sysbus->esp);
|
|
|
|
switch (irq) {
|
|
case 0:
|
|
parent_esp_reset(s, irq, level);
|
|
break;
|
|
case 1:
|
|
esp_dma_enable(s, irq, level);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void sysbus_esp_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
SysBusESPState *sysbus = SYSBUS_ESP(dev);
|
|
ESPState *s = ESP(&sysbus->esp);
|
|
|
|
if (!qdev_realize(DEVICE(s), NULL, errp)) {
|
|
return;
|
|
}
|
|
|
|
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-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", 4);
|
|
sysbus_init_mmio(sbd, &sysbus->pdma);
|
|
|
|
qdev_init_gpio_in(dev, sysbus_esp_gpio_demux, 2);
|
|
|
|
scsi_bus_init(&s->bus, sizeof(s->bus), dev, &esp_scsi_info);
|
|
}
|
|
|
|
static void sysbus_esp_hard_reset(DeviceState *dev)
|
|
{
|
|
SysBusESPState *sysbus = SYSBUS_ESP(dev);
|
|
ESPState *s = ESP(&sysbus->esp);
|
|
|
|
esp_hard_reset(s);
|
|
}
|
|
|
|
static void sysbus_esp_init(Object *obj)
|
|
{
|
|
SysBusESPState *sysbus = SYSBUS_ESP(obj);
|
|
|
|
object_initialize_child(obj, "esp", &sysbus->esp, TYPE_ESP);
|
|
}
|
|
|
|
static const VMStateDescription vmstate_sysbus_esp_scsi = {
|
|
.name = "sysbusespscsi",
|
|
.version_id = 2,
|
|
.minimum_version_id = 1,
|
|
.pre_save = esp_pre_save,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_UINT8_V(esp.mig_version_id, SysBusESPState, 2),
|
|
VMSTATE_STRUCT(esp, SysBusESPState, 0, vmstate_esp, ESPState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void sysbus_esp_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = sysbus_esp_realize;
|
|
dc->reset = sysbus_esp_hard_reset;
|
|
dc->vmsd = &vmstate_sysbus_esp_scsi;
|
|
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
|
}
|
|
|
|
static const TypeInfo sysbus_esp_info = {
|
|
.name = TYPE_SYSBUS_ESP,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_init = sysbus_esp_init,
|
|
.instance_size = sizeof(SysBusESPState),
|
|
.class_init = sysbus_esp_class_init,
|
|
};
|
|
|
|
static void esp_finalize(Object *obj)
|
|
{
|
|
ESPState *s = ESP(obj);
|
|
|
|
fifo8_destroy(&s->fifo);
|
|
fifo8_destroy(&s->cmdfifo);
|
|
}
|
|
|
|
static void esp_init(Object *obj)
|
|
{
|
|
ESPState *s = ESP(obj);
|
|
|
|
fifo8_create(&s->fifo, ESP_FIFO_SZ);
|
|
fifo8_create(&s->cmdfifo, ESP_CMDFIFO_SZ);
|
|
}
|
|
|
|
static void esp_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
/* internal device for sysbusesp/pciespscsi, not user-creatable */
|
|
dc->user_creatable = false;
|
|
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
|
}
|
|
|
|
static const TypeInfo esp_info = {
|
|
.name = TYPE_ESP,
|
|
.parent = TYPE_DEVICE,
|
|
.instance_init = esp_init,
|
|
.instance_finalize = esp_finalize,
|
|
.instance_size = sizeof(ESPState),
|
|
.class_init = esp_class_init,
|
|
};
|
|
|
|
static void esp_register_types(void)
|
|
{
|
|
type_register_static(&sysbus_esp_info);
|
|
type_register_static(&esp_info);
|
|
}
|
|
|
|
type_init(esp_register_types)
|