Files
86Box/src/scsi/scsi_pcscsi.c
TC1995 d60602dd5d 53c9x changes of the night (August 11th, 2025)
1. Add the original AMD 53c974 (AMD bios only and revision 0x00 compared to the A revision which is 0x10 in the PCI regs) as well as correcting the SCSI bus reset when prompted (ESP CMD 0x03), the latter fixes DawiControl 53c974 drivers on win9x.
2. Check if DMA length is not 0 for DMA transfers.
3. More logs for possible problem diagnostics.
2025-08-11 22:48:30 +02:00

2838 lines
84 KiB
C

/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Implementation of the Tekram DC-390 SCSI and related MCA
* controllers using the NCR 53c9x series of chips.
*
*
*
* Authors: Fabrice Bellard (QEMU)
* Herve Poussineau (QEMU)
* TheCollector1995, <mariogplayer@gmail.com>
* Miran Grca, <mgrca8@gmail.com>
*
* Copyright 2005-2018 Fabrice Bellard.
* Copyright 2012-2018 Herve Poussineau.
* Copyright 2017-2018 Miran Grca.
*/
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define HAVE_STDARG_H
#include <wchar.h>
#include <86box/86box.h>
#include <86box/io.h>
#include <86box/timer.h>
#include <86box/dma.h>
#include <86box/pic.h>
#include <86box/mem.h>
#include <86box/rom.h>
#include <86box/mca.h>
#include <86box/pci.h>
#include <86box/device.h>
#include <86box/nvr.h>
#include <86box/plat.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/scsi_pcscsi.h>
#include <86box/vid_ati_eeprom.h>
#include <86box/fifo8.h>
#include "cpu.h"
#define DC390_ROM "roms/scsi/esp_pci/INT13.BIN"
#define AM53C974_3_01_AMD_ROM "roms/scsi/esp_pci/ebrom.bin"
#define AM53C974_3_43_ROM "roms/scsi/esp_pci/2974BIOS.BIN"
#define AM53C974_4_00_ROM "roms/scsi/esp_pci/2974bios-4-00.bin"
#define AM53C974_5_00_ROM "roms/scsi/esp_pci/2974bios-5-00.bin"
#define AM53C974_5_11_ROM "roms/scsi/esp_pci/2974bios-5-11.bin"
#define ESP_REGS 16
#define ESP_FIFO_SZ 16
#define ESP_CMDFIFO_SZ 32
enum ESPASCMode {
ESP_ASC_MODE_DIS = 0, /* Disconnected */
ESP_ASC_MODE_INI = 1, /* Initiator */
ESP_ASC_MODE_TGT = 2 /* Target */
};
#define ESP_TCLO 0x0
#define ESP_TCMID 0x1
#define ESP_FIFO 0x2
#define ESP_CMD 0x3
#define ESP_RSTAT 0x4
#define ESP_WBUSID 0x4
#define ESP_RINTR 0x5
#define ESP_WSEL 0x5
#define ESP_RSEQ 0x6
#define ESP_WSYNTP 0x6
#define ESP_RFLAGS 0x7
#define ESP_WSYNO 0x7
#define ESP_CFG1 0x8
#define ESP_RRES1 0x9
#define ESP_WCCF 0x9
#define ESP_RRES2 0xa
#define ESP_WTEST 0xa
#define ESP_CFG2 0xb
#define ESP_CFG3 0xc
#define ESP_RES3 0xd
#define ESP_TCHI 0xe
#define ESP_RES4 0xf
#define CMD_DMA 0x80
#define CMD_CMD 0x7f
#define CMD_GRP_MASK 0x70
#define CMD_GRP_MISC 0x00
#define CMD_GRP_INIT 0x01
#define CMD_GRP_TRGT 0x02
#define CMD_GRP_DISC 0x04
#define CMD_NOP 0x00
#define CMD_FLUSH 0x01
#define CMD_RESET 0x02
#define CMD_BUSRESET 0x03
#define CMD_TI 0x10
#define CMD_ICCS 0x11
#define CMD_MSGACC 0x12
#define CMD_PAD 0x18
#define CMD_SATN 0x1a
#define CMD_RSTATN 0x1b
#define CMD_SEL 0x41
#define CMD_SELATN 0x42
#define CMD_SELATNS 0x43
#define CMD_ENSEL 0x44
#define CMD_DISSEL 0x45
#define STAT_DO 0x00
#define STAT_DI 0x01
#define STAT_CD 0x02
#define STAT_ST 0x03
#define STAT_MO 0x06
#define STAT_MI 0x07
#define STAT_PIO_MASK 0x06
#define STAT_TC 0x10
#define STAT_PE 0x20
#define STAT_GE 0x40
#define STAT_INT 0x80
#define BUSID_DID 0x07
#define INTR_FC 0x08
#define INTR_BS 0x10
#define INTR_DC 0x20
#define INTR_IL 0x40
#define INTR_RST 0x80
#define SEQ_0 0x0
#define SEQ_MO 0x1
#define SEQ_CD 0x4
#define CFG1_RESREPT 0x40
#define TCHI_ESP100A 0x01
#define TCHI_FAS100A 0x04
#define TCHI_AM53C974 0x12
#define DMA_CMD 0x0
#define DMA_STC 0x1
#define DMA_SPA 0x2
#define DMA_WBC 0x3
#define DMA_WAC 0x4
#define DMA_STAT 0x5
#define DMA_SMDLA 0x6
#define DMA_WMAC 0x7
#define DMA_CMD_MASK 0x03
#define DMA_CMD_DIAG 0x04
#define DMA_CMD_MDL 0x10
#define DMA_CMD_INTE_P 0x20
#define DMA_CMD_INTE_D 0x40
#define DMA_CMD_DIR 0x80
#define DMA_STAT_PWDN 0x01
#define DMA_STAT_ERROR 0x02
#define DMA_STAT_ABORT 0x04
#define DMA_STAT_DONE 0x08
#define DMA_STAT_SCSIINT 0x10
#define DMA_STAT_BCMBLT 0x20
#define SBAC_STATUS (1 << 24)
#define SBAC_PABTEN (1 << 25)
typedef struct esp_t {
char *bios_path;
char nvr_path[64];
uint8_t pci_slot;
int has_bios;
int BIOSBase;
int MMIOBase;
rom_t bios;
ati_eeprom_t eeprom;
int PCIBase;
uint8_t rregs[ESP_REGS];
uint8_t wregs[ESP_REGS];
int irq;
int tchi_written;
uint32_t ti_size;
uint32_t status;
uint32_t dma;
Fifo8 fifo;
uint8_t bus;
uint8_t id, lun;
Fifo8 cmdfifo;
uint8_t cmdfifo_cdb_offset;
uint8_t asc_mode;
int data_ready;
int32_t xfer_counter;
int dma_enabled;
uint32_t buffer_pos;
uint32_t dma_regs[8];
uint32_t sbac;
double period;
pc_timer_t timer;
int local;
int mca;
uint16_t Base;
uint8_t HostID;
uint8_t DmaChannel;
struct {
uint8_t mode;
uint8_t status;
int interrupt;
int pos;
} dma_86c01;
uint8_t irq_state;
uint8_t pos_regs[8];
} esp_t;
static esp_t reset_state = { 0 };
#define READ_FROM_DEVICE 1
#define WRITE_TO_DEVICE 0
uint8_t esp_pci_regs[256];
bar_t esp_pci_bar[2];
#ifdef ENABLE_ESP_LOG
int esp_do_log = ENABLE_ESP_LOG;
static void
esp_log(const char *fmt, ...)
{
va_list ap;
if (esp_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
# define esp_log(fmt, ...)
#endif
static void esp_dma_enable(esp_t *dev, int level);
static void esp_do_dma(esp_t *dev);
static void esp_do_nodma(esp_t *dev);
static void esp_pci_dma_memory_rw(esp_t *dev, uint8_t *buf, uint32_t len, int dir);
static void esp_timer_on(esp_t *dev, scsi_device_t *sd, double p);
static void esp_command_complete(void *priv, uint32_t status);
static void esp_dma_ti_check(esp_t *dev);
static void esp_nodma_ti_dataout(esp_t *dev);
static void esp_pci_soft_reset(esp_t *dev);
static void esp_pci_hard_reset(esp_t *dev);
static void handle_ti(esp_t *dev);
static int
esp_cdb_length(uint8_t *buf)
{
int cdb_len;
switch (buf[0] >> 5) {
case 0:
case 3:
cdb_len = 6;
break;
case 1:
case 2:
case 6: /*Vendor unique*/
cdb_len = 10;
break;
case 4:
cdb_len = 16;
break;
case 5:
cdb_len = 12;
break;
default:
cdb_len = -1;
break;
}
return cdb_len;
}
static void
esp_pci_update_irq(esp_t *dev)
{
int scsi_level = !!(dev->dma_regs[DMA_STAT] & DMA_STAT_SCSIINT);
int dma_level = (dev->dma_regs[DMA_CMD] & DMA_CMD_INTE_D) ?
!!(dev->dma_regs[DMA_STAT] & DMA_STAT_DONE) : 0;
int level = scsi_level || dma_level;
if (level) {
pci_set_irq(dev->pci_slot, PCI_INTA, &dev->irq_state);
esp_log("Raising PCI IRQ..., SCSIL=%d, DMAL=%d\n", scsi_level, dma_level);
} else {
pci_clear_irq(dev->pci_slot, PCI_INTA, &dev->irq_state);
esp_log("Lowering PCI IRQ..., SCSIL=%d, DMAL=%d\n", scsi_level, dma_level);
}
}
static void
esp_irq(esp_t *dev, int level)
{
if (dev->mca) {
if (level) {
picintlevel(1 << dev->irq, &dev->irq_state);
dev->dma_86c01.mode |= 0x40;
esp_log("Raising IRQ...\n");
} else {
picintclevel(1 << dev->irq, &dev->irq_state);
dev->dma_86c01.mode &= ~0x40;
esp_log("Lowering IRQ...\n");
}
} else {
if (level) {
dev->dma_regs[DMA_STAT] |= DMA_STAT_SCSIINT;
/*
* If raising the ESP IRQ to indicate end of DMA transfer, set
* DMA_STAT_DONE at the same time. In theory this should be done in
* esp_pci_dma_memory_rw(), however there is a delay between setting
* DMA_STAT_DONE and the ESP IRQ arriving which is visible to the
* guest that can cause confusion e.g. Linux
*/
esp_log("ESP IRQ issuing: WBC=%d, CMDMask=%03x.\n", dev->dma_regs[DMA_WBC], dev->dma_regs[DMA_CMD] & DMA_CMD_MASK);
if (((dev->dma_regs[DMA_CMD] & DMA_CMD_MASK) == 0x03) &&
(dev->dma_regs[DMA_WBC] == 0))
dev->dma_regs[DMA_STAT] |= DMA_STAT_DONE;
} else
dev->dma_regs[DMA_STAT] &= ~DMA_STAT_SCSIINT;
esp_pci_update_irq(dev);
}
}
static void
esp_raise_irq(esp_t *dev)
{
if (!(dev->rregs[ESP_RSTAT] & STAT_INT)) {
dev->rregs[ESP_RSTAT] |= STAT_INT;
esp_irq(dev, 1);
}
}
static void
esp_lower_irq(esp_t *dev)
{
if (dev->rregs[ESP_RSTAT] & STAT_INT) {
dev->rregs[ESP_RSTAT] &= ~STAT_INT;
esp_irq(dev, 0);
}
}
static void
esp_set_phase(esp_t *dev, uint8_t phase)
{
dev->rregs[ESP_RSTAT] &= ~7;
dev->rregs[ESP_RSTAT] |= phase;
}
static uint8_t
esp_get_phase(esp_t *dev)
{
return dev->rregs[ESP_RSTAT] & 7;
}
static void
esp_fifo_push(esp_t *dev, uint8_t val)
{
if (fifo8_num_used(&dev->fifo) == dev->fifo.capacity)
return;
fifo8_push(&dev->fifo, val);
}
static uint8_t
esp_fifo_pop(esp_t *dev)
{
uint8_t val;
if (fifo8_is_empty(&dev->fifo))
val = 0;
else
val = fifo8_pop(&dev->fifo);
return val;
}
static uint32_t
esp_fifo_pop_buf(esp_t *dev, uint8_t *dest, int maxlen)
{
uint32_t len = fifo8_pop_buf(&dev->fifo, dest, maxlen);
return len;
}
static uint32_t
esp_get_tc(esp_t *dev)
{
uint32_t dmalen;
dmalen = dev->rregs[ESP_TCLO] & 0xff;
dmalen |= dev->rregs[ESP_TCMID] << 8;
dmalen |= dev->rregs[ESP_TCHI] << 16;
return dmalen;
}
static void
esp_set_tc(esp_t *dev, uint32_t dmalen)
{
uint32_t old_tc = esp_get_tc(dev);
dev->rregs[ESP_TCLO] = dmalen & 0xff;
dev->rregs[ESP_TCMID] = dmalen >> 8;
dev->rregs[ESP_TCHI] = dmalen >> 16;
esp_log("OLDTC=%d, DMALEN=%d.\n", old_tc, dmalen);
if (old_tc && !dmalen)
dev->rregs[ESP_RSTAT] |= STAT_TC;
}
static uint32_t
esp_get_stc(esp_t *dev)
{
uint32_t dmalen;
dmalen = dev->wregs[ESP_TCLO] & 0xff;
dmalen |= (dev->wregs[ESP_TCMID] << 8);
dmalen |= (dev->wregs[ESP_TCHI] << 16);
esp_log("STCW=%d.\n", dmalen);
return dmalen;
}
static int
esp_select(esp_t *dev)
{
scsi_device_t *sd;
dev->id = dev->wregs[ESP_WBUSID] & BUSID_DID;
sd = &scsi_devices[dev->bus][dev->id];
dev->ti_size = 0;
dev->rregs[ESP_RSEQ] = SEQ_0;
if (!scsi_device_present(sd)) {
esp_log("ESP SCSI no devices on ID %d, LUN %d\n", dev->id, dev->lun);
/* No such drive */
dev->rregs[ESP_RSTAT] = 0;
dev->asc_mode = ESP_ASC_MODE_DIS;
dev->rregs[ESP_RINTR] = INTR_DC;
esp_raise_irq(dev);
return -1;
} else
esp_log("ESP SCSI device present on ID %d, LUN %d\n", dev->id, dev->lun);
dev->asc_mode = ESP_ASC_MODE_INI;
return 0;
}
/* Callback to indicate that the SCSI layer has completed a transfer. */
static void
esp_transfer_data(esp_t *dev)
{
if (!dev->data_ready) {
dev->data_ready = 1;
switch (dev->rregs[ESP_CMD]) {
case CMD_SEL:
case (CMD_SEL | CMD_DMA):
case CMD_SELATN:
case (CMD_SELATN | CMD_DMA):
/*
* Initial incoming data xfer is complete for sequencer command
* so raise deferred bus service and function complete interrupt
*/
dev->rregs[ESP_RINTR] |= (INTR_BS | INTR_FC);
dev->rregs[ESP_RSEQ] = SEQ_CD;
esp_raise_irq(dev);
break;
case CMD_SELATNS:
case (CMD_SELATNS | CMD_DMA):
/*
* Initial incoming data xfer is complete so raise command
* completion interrupt
*/
dev->rregs[ESP_RINTR] |= INTR_BS;
dev->rregs[ESP_RSEQ] = SEQ_MO;
esp_raise_irq(dev);
break;
case CMD_TI:
case (CMD_TI | CMD_DMA):
/*
* If the final COMMAND phase data was transferred using a TI
* command, clear ESP_CMD to terminate the TI command and raise
* the completion interrupt
*/
dev->rregs[ESP_CMD] = 0;
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
break;
default:
break;
}
}
/*
* 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 (dev->rregs[ESP_CMD] == (CMD_TI | CMD_DMA)) {
/* When the SCSI layer returns more data, raise deferred INTR_BS */
esp_dma_ti_check(dev);
esp_do_dma(dev);
} else if (dev->rregs[ESP_CMD] == CMD_TI)
esp_do_nodma(dev);
}
static void
esp_do_command_phase(esp_t *dev)
{
uint32_t cmdlen;
uint8_t buf[ESP_CMDFIFO_SZ];
scsi_device_t *sd;
sd = &scsi_devices[dev->bus][dev->id];
sd->buffer_length = -1;
cmdlen = fifo8_num_used(&dev->cmdfifo);
if (!cmdlen)
return;
fifo8_pop_buf(&dev->cmdfifo, buf, cmdlen);
for (int i = 0; i < cmdlen; i++)
esp_log("CDB[%i] = %02x\n", i, buf[i]);
scsi_device_command_phase0(sd, buf);
dev->buffer_pos = 0;
dev->ti_size = sd->buffer_length;
dev->xfer_counter = sd->buffer_length;
esp_log("ESP SCSI Command = 0x%02x, ID = %d, LUN = %d, len = %d, phase = %02x, PCI DMA cmd mask = %02x.\n", buf[0], dev->id, dev->lun, sd->buffer_length, sd->phase, dev->dma_regs[DMA_CMD] & DMA_CMD_MASK);
fifo8_reset(&dev->cmdfifo);
dev->data_ready = 0;
if (sd->buffer_length > 0) {
if (sd->phase == SCSI_PHASE_DATA_IN) {
esp_set_phase(dev, STAT_DI);
esp_log("ESP Data In\n");
esp_timer_on(dev, sd, scsi_device_get_callback(sd));
} else if (sd->phase == SCSI_PHASE_DATA_OUT) {
esp_set_phase(dev, STAT_DO);
dev->ti_size = -sd->buffer_length;
esp_log("ESP Data Out\n");
esp_timer_on(dev, sd, scsi_device_get_callback(sd));
}
esp_log("ESP SCSI Start reading/writing\n");
esp_do_dma(dev);
} else {
esp_log("ESP SCSI Command with no length\n");
esp_command_complete(dev, sd->status);
}
esp_transfer_data(dev);
}
static void
esp_do_message_phase(esp_t *dev)
{
int len;
uint8_t message;
if (dev->cmdfifo_cdb_offset) {
message = fifo8_is_empty(&dev->cmdfifo) ? 0 :
fifo8_pop(&dev->cmdfifo);
dev->lun = message & 7;
dev->cmdfifo_cdb_offset--;
esp_log("Scanning LUN=%d.\n", dev->lun);
if (scsi_device_present(&scsi_devices[dev->bus][dev->id]) && (dev->lun > 0)) {
/* We only support LUN 0 */
esp_log("LUN = %i\n", dev->lun);
dev->rregs[ESP_RSTAT] = 0;
dev->asc_mode = ESP_ASC_MODE_DIS;
dev->rregs[ESP_RINTR] = INTR_DC;
dev->rregs[ESP_RSEQ] = SEQ_0;
esp_raise_irq(dev);
fifo8_reset(&dev->cmdfifo);
return;
}
scsi_device_identify(&scsi_devices[dev->bus][dev->id], dev->lun);
}
esp_log("CDB offset = %i\n", dev->cmdfifo_cdb_offset);
if (dev->cmdfifo_cdb_offset) {
len = MIN(dev->cmdfifo_cdb_offset, fifo8_num_used(&dev->cmdfifo));
fifo8_drop(&dev->cmdfifo, len);
dev->cmdfifo_cdb_offset = 0;
}
}
static void
esp_do_cmd(esp_t *dev)
{
esp_log("DO CMD.\n");
esp_do_message_phase(dev);
if (dev->cmdfifo_cdb_offset >= 0)
esp_do_command_phase(dev);
}
static void
esp_dma_enable(esp_t *dev, int level)
{
if (level) {
esp_log("ESP DMA Enabled\n");
dev->dma_enabled = 1;
timer_stop(&dev->timer);
if (((dev->rregs[ESP_CMD] & CMD_CMD) != CMD_TI) && ((dev->rregs[ESP_CMD] & CMD_CMD) != CMD_PAD)) {
if (dev->wregs[ESP_WCCF] & 0x07)
timer_on_auto(&dev->timer, ((double)(dev->wregs[ESP_WCCF] & 0x07)) * 5.0);
else
timer_on_auto(&dev->timer, 40.0);
} else {
esp_log("Period = %lf\n", dev->period);
timer_on_auto(&dev->timer, dev->period);
}
} else {
esp_log("ESP DMA Disabled\n");
dev->dma_enabled = 0;
}
}
static void
esp_hard_reset(esp_t *dev)
{
memset(dev->rregs, 0, ESP_REGS);
memset(dev->wregs, 0, ESP_REGS);
dev->ti_size = 0;
fifo8_reset(&dev->fifo);
fifo8_reset(&dev->cmdfifo);
dev->dma = 0;
dev->tchi_written = 0;
dev->asc_mode = ESP_ASC_MODE_DIS;
dev->rregs[ESP_CFG1] = dev->mca ? dev->HostID : 7;
dev->sbac = 1 << 19;
esp_log("ESP Reset\n");
timer_stop(&dev->timer);
}
static int
esp_cdb_ready(esp_t *dev)
{
int len = fifo8_num_used(&dev->cmdfifo) - dev->cmdfifo_cdb_offset;
const uint8_t *pbuf;
uint32_t n;
int cdblen;
if (len <= 0)
return 0;
pbuf = fifo8_peek_bufptr(&dev->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 0;
}
cdblen = esp_cdb_length((uint8_t *)&pbuf[dev->cmdfifo_cdb_offset]);
return (cdblen < 0) ? 0 : (len >= cdblen);
}
static void
esp_dma_ti_check(esp_t *dev)
{
if ((esp_get_tc(dev) == 0) && (fifo8_num_used(&dev->fifo) < 2)) {
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
}
}
static void
esp_do_dma(esp_t *dev)
{
scsi_device_t *sd = &scsi_devices[dev->bus][dev->id];
uint8_t buf[ESP_CMDFIFO_SZ];
uint32_t len;
len = esp_get_tc(dev);
esp_log("ESP SCSI Actual DMA len=%d, cfg3=%02x, phase=%x.\n", len, dev->rregs[ESP_CFG3], esp_get_phase(dev));
switch (esp_get_phase(dev)) {
case STAT_MO:
len = MIN(len, fifo8_num_free(&dev->cmdfifo));
esp_log("ESP SCSI Message Out len=%d.\n", len);
if (len) {
if (dev->mca) {
dma_set_drq(dev->DmaChannel, 1);
while (dev->dma_86c01.pos < len) {
int val = dma_channel_read(dev->DmaChannel);
buf[dev->dma_86c01.pos++] = val & 0xff;
}
dev->dma_86c01.pos = 0;
dma_set_drq(dev->DmaChannel, 0);
} else
esp_pci_dma_memory_rw(dev, buf, len, WRITE_TO_DEVICE);
esp_set_tc(dev, esp_get_tc(dev) - len);
}
fifo8_push_all(&dev->cmdfifo, buf, len);
dev->cmdfifo_cdb_offset += len;
switch (dev->rregs[ESP_CMD]) {
case (CMD_SELATN | CMD_DMA):
if (fifo8_num_used(&dev->cmdfifo) >= 1) {
/* First byte received, switch to command phase */
esp_set_phase(dev, STAT_CD);
dev->rregs[ESP_RSEQ] = SEQ_CD;
dev->cmdfifo_cdb_offset = 1;
if (fifo8_num_used(&dev->cmdfifo) > 1) {
/* Process any additional command phase data */
esp_do_dma(dev);
}
}
break;
case (CMD_SELATNS | CMD_DMA):
if (fifo8_num_used(&dev->cmdfifo) == 1) {
/* First byte received, stop in message out phase */
dev->rregs[ESP_RSEQ] = SEQ_MO;
dev->cmdfifo_cdb_offset = 1;
/* Raise command completion interrupt */
dev->rregs[ESP_RINTR] |= (INTR_BS | INTR_FC);
esp_raise_irq(dev);
}
break;
case (CMD_TI | CMD_DMA):
/* ATN remains asserted until TC == 0 */
if (esp_get_tc(dev) == 0) {
esp_set_phase(dev, STAT_CD);
dev->rregs[ESP_CMD] = 0;
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
}
break;
default:
break;
}
break;
case STAT_CD:
len = MIN(len, fifo8_num_free(&dev->cmdfifo));
if (len) {
if (dev->mca) {
dma_set_drq(dev->DmaChannel, 1);
while (dev->dma_86c01.pos < len) {
int val = dma_channel_read(dev->DmaChannel);
buf[dev->dma_86c01.pos++] = val & 0xff;
}
dev->dma_86c01.pos = 0;
dma_set_drq(dev->DmaChannel, 0);
} else
esp_pci_dma_memory_rw(dev, buf, len, WRITE_TO_DEVICE);
fifo8_push_all(&dev->cmdfifo, buf, len);
esp_set_tc(dev, esp_get_tc(dev) - len);
}
dev->ti_size = 0;
if (esp_get_tc(dev) == 0) {
/* Command has been received */
esp_do_cmd(dev);
}
break;
case STAT_DO:
if (!dev->xfer_counter && esp_get_tc(dev)) {
/* Defer until data is available. */
return;
}
if (len > dev->xfer_counter)
len = dev->xfer_counter;
switch (dev->rregs[ESP_CMD]) {
case (CMD_TI | CMD_DMA):
if (len) {
if (dev->mca) {
dma_set_drq(dev->DmaChannel, 1);
while (dev->dma_86c01.pos < len) {
int val = dma_channel_read(dev->DmaChannel);
esp_log("ESP SCSI DMA write for 53C9x: pos = %i, val = %02x\n", dev->dma_86c01.pos, val & 0xff);
sd->sc->temp_buffer[dev->buffer_pos + dev->dma_86c01.pos] = val & 0xff;
dev->dma_86c01.pos++;
}
dma_set_drq(dev->DmaChannel, 0);
dev->dma_86c01.pos = 0;
} else
esp_pci_dma_memory_rw(dev, sd->sc->temp_buffer + dev->buffer_pos, len, WRITE_TO_DEVICE);
esp_set_tc(dev, esp_get_tc(dev) - len);
}
dev->buffer_pos += len;
dev->xfer_counter -= len;
dev->ti_size += len;
break;
case (CMD_PAD | CMD_DMA):
/* Copy TC zero bytes into the incoming stream */
memset(sd->sc->temp_buffer + dev->buffer_pos, 0, len);
dev->buffer_pos += len;
dev->xfer_counter -= len;
dev->ti_size += len;
break;
default:
break;
}
if ((dev->xfer_counter <= 0) && (fifo8_num_used(&dev->fifo) < 2)) {
/* Defer until the scsi layer has completed */
if (dev->ti_size < 0) {
esp_log("ESP SCSI Keep writing\n");
esp_do_dma(dev);
} else {
esp_log("ESP SCSI Write finished\n");
scsi_device_command_phase1(sd);
esp_command_complete(dev, sd->status);
}
return;
}
esp_dma_ti_check(dev);
break;
case STAT_DI:
if (!dev->xfer_counter && esp_get_tc(dev)) {
/* Defer until data is available. */
return;
}
if (len > dev->xfer_counter)
len = dev->xfer_counter;
switch (dev->rregs[ESP_CMD]) {
case (CMD_TI | CMD_DMA):
if (len) {
if (dev->mca) {
dma_set_drq(dev->DmaChannel, 1);
while (dev->dma_86c01.pos < len) {
dma_channel_write(dev->DmaChannel, sd->sc->temp_buffer[dev->buffer_pos + dev->dma_86c01.pos]);
esp_log("ESP SCSI DMA read for 53C9x: pos = %i, val = %02x\n", dev->dma_86c01.pos, sd->sc->temp_buffer[dev->buffer_pos + dev->dma_86c01.pos]);
dev->dma_86c01.pos++;
}
dev->dma_86c01.pos = 0;
dma_set_drq(dev->DmaChannel, 0);
} else
esp_pci_dma_memory_rw(dev, sd->sc->temp_buffer + dev->buffer_pos, len, READ_FROM_DEVICE);
}
dev->buffer_pos += len;
dev->xfer_counter -= len;
dev->ti_size -= len;
esp_set_tc(dev, esp_get_tc(dev) - len);
break;
case (CMD_PAD | CMD_DMA):
dev->buffer_pos += len;
dev->xfer_counter -= len;
dev->ti_size -= len;
esp_set_tc(dev, esp_get_tc(dev) - len);
break;
default:
break;
}
if ((dev->xfer_counter <= 0) && (fifo8_num_used(&dev->fifo) < 2)) {
/* Defer until the scsi layer has completed */
if (dev->ti_size <= 0) {
esp_log("ESP SCSI Read finished\n");
scsi_device_command_phase1(sd);
esp_command_complete(dev, sd->status);
} else {
esp_log("ESP SCSI Keep reading\n");
esp_do_dma(dev);
}
return;
}
esp_dma_ti_check(dev);
break;
case STAT_ST:
switch (dev->rregs[ESP_CMD]) {
case (CMD_ICCS | CMD_DMA):
len = MIN(len, 1);
if (len) {
buf[0] = dev->status;
/* Length already non-zero */
if (dev->mca) {
dma_set_drq(dev->DmaChannel, 1);
while (dev->dma_86c01.pos < len) {
dma_channel_write(dev->DmaChannel, buf[dev->dma_86c01.pos]);
dev->dma_86c01.pos++;
}
dev->dma_86c01.pos = 0;
dma_set_drq(dev->DmaChannel, 0);
} else
esp_pci_dma_memory_rw(dev, buf, len, READ_FROM_DEVICE);
esp_set_tc(dev, esp_get_tc(dev) - len);
esp_set_phase(dev, STAT_MI);
if (esp_get_tc(dev) > 0) {
/* Process any message in phase data */
esp_do_dma(dev);
}
}
break;
default:
/* Consume remaining data if the guest underflows TC */
if (fifo8_num_used(&dev->fifo) < 2) {
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
}
break;
}
break;
case STAT_MI:
switch (dev->rregs[ESP_CMD]) {
case (CMD_ICCS | CMD_DMA):
len = MIN(len, 1);
if (len) {
buf[0] = 0;
/* Length already non-zero */
if (dev->mca) {
dma_set_drq(dev->DmaChannel, 1);
while (dev->dma_86c01.pos < len) {
dma_channel_write(dev->DmaChannel, buf[dev->dma_86c01.pos]);
dev->dma_86c01.pos++;
}
dev->dma_86c01.pos = 0;
dma_set_drq(dev->DmaChannel, 0);
} else
esp_pci_dma_memory_rw(dev, buf, len, READ_FROM_DEVICE);
esp_set_tc(dev, esp_get_tc(dev) - len);
/* Raise end of command interrupt */
dev->rregs[ESP_RINTR] |= INTR_FC;
esp_raise_irq(dev);
}
break;
default:
break;
}
break;
default:
break;
}
}
static void
esp_nodma_ti_dataout(esp_t *dev)
{
scsi_device_t *sd = &scsi_devices[dev->bus][dev->id];
int len;
if (!dev->xfer_counter) {
/* Defer until data is available. */
return;
}
len = MIN(dev->xfer_counter, ESP_FIFO_SZ);
len = MIN(len, fifo8_num_used(&dev->fifo));
esp_fifo_pop_buf(dev, sd->sc->temp_buffer + dev->buffer_pos, len);
dev->buffer_pos += len;
dev->xfer_counter -= len;
dev->ti_size += len;
if (dev->xfer_counter <= 0) {
if (dev->ti_size < 0) {
esp_log("ESP SCSI Keep writing\n");
esp_nodma_ti_dataout(dev);
} else {
esp_log("ESP SCSI Write finished\n");
scsi_device_command_phase1(sd);
esp_command_complete(dev, sd->status);
}
return;
}
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
}
static void
esp_do_nodma(esp_t *dev)
{
scsi_device_t *sd = &scsi_devices[dev->bus][dev->id];
uint8_t buf[ESP_FIFO_SZ];
int len;
esp_log("No DMA phase=%x.\n", esp_get_phase(dev));
switch (esp_get_phase(dev)) {
case STAT_MO:
switch (dev->rregs[ESP_CMD]) {
case CMD_SELATN:
/* Copy FIFO into cmdfifo */
len = esp_fifo_pop_buf(dev, buf, fifo8_num_used(&dev->fifo));
len = MIN(fifo8_num_free(&dev->cmdfifo), len);
esp_log("ESP Message Out CMD SelAtn len=%d.\n", len);
fifo8_push_all(&dev->cmdfifo, buf, len);
esp_log("ESP Message Out CMD SelAtn FIFO num used=%d.\n", fifo8_num_used(&dev->cmdfifo));
if (fifo8_num_used(&dev->cmdfifo) >= 1) {
/* First byte received, switch to command phase */
esp_set_phase(dev, STAT_CD);
dev->rregs[ESP_RSEQ] = SEQ_CD;
dev->cmdfifo_cdb_offset = 1;
if (fifo8_num_used(&dev->cmdfifo) > 1) {
/* Process any additional command phase data */
esp_do_nodma(dev);
}
}
break;
case CMD_SELATNS:
/* Copy one byte from FIFO into cmdfifo */
len = esp_fifo_pop_buf(dev, buf, MIN(fifo8_num_used(&dev->fifo), 1));
esp_log("ESP Message Out CMD SelAtnStop len1=%d.\n", len);
len = MIN(fifo8_num_free(&dev->cmdfifo), len);
esp_log("ESP Message Out CMD SelAtnStop len2=%d.\n", len);
fifo8_push_all(&dev->cmdfifo, buf, len);
esp_log("ESP Message Out CMD SelAtnStop FIFO num used=%d.\n", fifo8_num_used(&dev->cmdfifo));
if (fifo8_num_used(&dev->cmdfifo) >= 1) {
/* First byte received, stop in message out phase */
dev->rregs[ESP_RSEQ] = SEQ_MO;
dev->cmdfifo_cdb_offset = 1;
/* Raise command completion interrupt */
dev->rregs[ESP_RINTR] |= (INTR_BS | INTR_FC);
esp_raise_irq(dev);
}
break;
case CMD_TI:
/* Copy FIFO into cmdfifo */
len = esp_fifo_pop_buf(dev, buf, fifo8_num_used(&dev->fifo));
esp_log("ESP Message Out CMD TI len1=%d.\n", len);
len = MIN(fifo8_num_free(&dev->cmdfifo), len);
esp_log("ESP Message Out CMD TI len2=%d.\n", len);
fifo8_push_all(&dev->cmdfifo, buf, len);
/* ATN remains asserted until FIFO empty */
dev->cmdfifo_cdb_offset = fifo8_num_used(&dev->cmdfifo);
esp_log("ESP Message Out CMD TI CDB offset=%d.\n", dev->cmdfifo_cdb_offset);
esp_set_phase(dev, STAT_CD);
dev->rregs[ESP_CMD] = 0;
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
break;
default:
break;
}
break;
case STAT_CD:
switch (dev->rregs[ESP_CMD]) {
case CMD_TI:
/* Copy FIFO into cmdfifo */
len = esp_fifo_pop_buf(dev, buf, fifo8_num_used(&dev->fifo));
len = MIN(fifo8_num_free(&dev->cmdfifo), len);
fifo8_push_all(&dev->cmdfifo, buf, len);
/* CDB may be transferred in one or more TI commands */
if (esp_cdb_ready(dev)) {
/* Command has been received */
esp_do_cmd(dev);
} 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 */
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
}
}
break;
case (CMD_SEL | CMD_DMA):
case (CMD_SELATN | CMD_DMA):
/* Copy FIFO into cmdfifo */
len = esp_fifo_pop_buf(dev, buf, fifo8_num_used(&dev->fifo));
len = MIN(fifo8_num_free(&dev->cmdfifo), len);
fifo8_push_all(&dev->cmdfifo, buf, len);
/* Handle when DMA transfer is terminated by non-DMA FIFO write */
if (esp_cdb_ready(dev)) {
/* Command has been received */
esp_do_cmd(dev);
}
break;
case CMD_SEL:
case CMD_SELATN:
/* FIFO already contain entire CDB: copy to cmdfifo and execute */
len = esp_fifo_pop_buf(dev, buf, fifo8_num_used(&dev->fifo));
len = MIN(fifo8_num_free(&dev->cmdfifo), len);
fifo8_push_all(&dev->cmdfifo, buf, len);
esp_do_cmd(dev);
break;
default:
break;
}
break;
case STAT_DO:
/* Accumulate data in FIFO until non-DMA TI is executed */
break;
case STAT_DI:
if (!dev->xfer_counter) {
/* Defer until data is available. */
return;
}
if (fifo8_is_empty(&dev->fifo)) {
esp_fifo_push(dev, sd->sc->temp_buffer[dev->buffer_pos]);
dev->buffer_pos++;
dev->ti_size--;
dev->xfer_counter--;
}
if (dev->xfer_counter <= 0) {
if (dev->ti_size <= 0) {
esp_log("ESP FIFO Read finished\n");
scsi_device_command_phase1(sd);
esp_command_complete(dev, sd->status);
} else {
esp_log("ESP FIFO Keep reading\n");
esp_do_nodma(dev);
}
return;
}
/* If preloading the FIFO, defer until TI command issued */
if (dev->rregs[ESP_CMD] != CMD_TI)
return;
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
break;
case STAT_ST:
switch (dev->rregs[ESP_CMD]) {
case CMD_ICCS:
esp_log("ICCS Status=%x.\n", dev->status);
esp_fifo_push(dev, dev->status);
esp_set_phase(dev, STAT_MI);
/* Process any message in phase data */
esp_do_nodma(dev);
break;
default:
break;
}
break;
case STAT_MI:
switch (dev->rregs[ESP_CMD]) {
case CMD_ICCS:
esp_fifo_push(dev, 0);
/* Raise end of command interrupt */
dev->rregs[ESP_RINTR] |= INTR_FC;
esp_raise_irq(dev);
break;
default:
break;
}
break;
}
}
/* Callback to indicate that the SCSI layer has completed a command. */
static void
esp_command_complete(void *priv, uint32_t status)
{
esp_t *dev = (esp_t *) priv;
scsi_device_t *sd = &scsi_devices[dev->bus][dev->id];
dev->ti_size = 0;
dev->status = status;
switch (dev->rregs[ESP_CMD]) {
case CMD_SEL:
case (CMD_SEL | CMD_DMA):
case CMD_SELATN:
case (CMD_SELATN | CMD_DMA):
/*
* Switch to status phase. For non-DMA transfers from the target the last
* byte is still in the FIFO
*/
dev->rregs[ESP_RINTR] |= (INTR_BS | INTR_FC);
dev->rregs[ESP_RSEQ] = SEQ_CD;
break;
case CMD_TI:
case (CMD_TI | CMD_DMA):
dev->rregs[ESP_CMD] = 0;
break;
default:
break;
}
/* Raise bus service interrupt to indicate change to STATUS phase */
scsi_device_identify(sd, SCSI_LUN_USE_CDB);
esp_set_phase(dev, STAT_ST);
dev->rregs[ESP_RINTR] |= INTR_BS;
esp_raise_irq(dev);
}
static void
esp_timer_on(esp_t *dev, scsi_device_t *sd, double p)
{
if ((dev->rregs[ESP_CFG3] & 0x18) == 0x18) {
/* Fast SCSI: 10000000 bytes per second */
dev->period = (p > 0.0) ? p : (((double) sd->buffer_length) * 0.1);
} else {
/* Normal SCSI: 5000000 bytes per second */
dev->period = (p > 0.0) ? p : (((double) sd->buffer_length) * 0.2);
}
if ((dev->wregs[ESP_WCCF] & 0x07) == 0x00)
timer_on_auto(&dev->timer, dev->period + 40.0);
else
timer_on_auto(&dev->timer, dev->period + (((double)(dev->wregs[ESP_WCCF] & 0x07)) * 5.0));
}
static void
handle_pad(esp_t *dev)
{
if (dev->dma) {
esp_log("ESP Handle PAD, do data, minlen = %i\n", esp_get_tc(dev));
esp_do_dma(dev);
} else {
esp_log("ESP Handle PAD, do nodma, minlen = %i\n", dev->xfer_counter);
esp_do_nodma(dev);
}
}
static void
handle_ti(esp_t *dev)
{
if (dev->dma) {
esp_log("ESP Handle TI, do data, minlen = %i\n", esp_get_tc(dev));
esp_do_dma(dev);
} else {
esp_log("ESP Handle TI, do nodma, minlen = %i\n", dev->xfer_counter);
esp_do_nodma(dev);
if (esp_get_phase(dev) == STAT_DO)
esp_nodma_ti_dataout(dev);
}
}
static void
handle_s_without_atn(void *priv)
{
esp_t *dev = (esp_t *) priv;
if (esp_select(dev) < 0)
return;
esp_log("Selection without ATN.\n");
esp_set_phase(dev, STAT_CD);
dev->cmdfifo_cdb_offset = 0;
if (dev->dma)
esp_do_dma(dev);
else
esp_do_nodma(dev);
}
static void
handle_satn(void *priv)
{
esp_t *dev = (esp_t *) priv;
if (esp_select(dev) < 0)
return;
esp_log("Selection with ATN.\n");
esp_set_phase(dev, STAT_MO);
if (dev->dma)
esp_do_dma(dev);
else
esp_do_nodma(dev);
}
static void
handle_satn_stop(void *priv)
{
esp_t *dev = (esp_t *) priv;
if (esp_select(dev) < 0)
return;
esp_log("Selection with ATN and Stop.\n");
esp_set_phase(dev, STAT_MO);
if (dev->dma)
esp_do_dma(dev);
else
esp_do_nodma(dev);
}
static void
esp_write_response(esp_t *dev)
{
if (dev->dma)
esp_do_dma(dev);
else
esp_do_nodma(dev);
}
static void
esp_callback(void *priv)
{
esp_t *dev = (esp_t *) priv;
if (dev->dma_enabled || !dev->dma || ((dev->rregs[ESP_CMD] & CMD_CMD) == CMD_PAD)) {
if ((dev->rregs[ESP_CMD] & CMD_CMD) == CMD_TI) {
esp_log("ESP SCSI Handle TI Callback\n");
handle_ti(dev);
} else if ((dev->rregs[ESP_CMD] & CMD_CMD) == CMD_PAD) {
esp_log("ESP SCSI Handle PAD Callback\n");
handle_pad(dev);
}
}
}
static int
esp_cmd_is_valid(esp_t *dev, uint8_t cmd)
{
uint8_t cmd_group = (cmd & CMD_GRP_MASK) >> 4;
/* Always allow misc commands */
if (cmd_group == CMD_GRP_MISC)
return 1;
switch (dev->asc_mode) {
case ESP_ASC_MODE_DIS:
/* Disconnected mode: only allow disconnected commands */
if (cmd_group == CMD_GRP_DISC)
return 1;
break;
case ESP_ASC_MODE_INI:
/* Initiator mode: allow initiator commands */
if (cmd_group == CMD_GRP_INIT)
return 1;
break;
default:
break;
}
return 0;
}
static uint32_t
esp_reg_read(esp_t *dev, uint32_t saddr)
{
uint32_t ret;
switch (saddr) {
case ESP_FIFO:
dev->rregs[ESP_FIFO] = esp_fifo_pop(dev);
ret = dev->rregs[ESP_FIFO];
break;
case ESP_RINTR:
/* Clear sequence step, interrupt register and all status bits
except TC */
ret = dev->rregs[ESP_RINTR];
dev->rregs[ESP_RINTR] = 0;
dev->rregs[ESP_RSTAT] &= ~(0x08 | STAT_PE | STAT_GE | STAT_TC);
esp_lower_irq(dev);
esp_log("Read Interrupt=%02x (old).\n", ret);
break;
case ESP_TCHI: /* Return the unique id if the value has never been written */
if (!dev->tchi_written) {
if (dev->mca)
ret = TCHI_ESP100A;
else
ret = TCHI_AM53C974;
esp_log("ChipID=%02x.\n", ret);
} else {
ret = dev->rregs[ESP_TCHI];
esp_log("Read TCHI Register=%02x.\n", ret);
}
break;
case ESP_RFLAGS:
ret = fifo8_num_used(&dev->fifo);
break;
case ESP_RSTAT:
ret = dev->rregs[ESP_RSTAT];
esp_log("Read SCSI Status Register=%02x.\n", ret);
break;
case ESP_RSEQ:
ret = dev->rregs[ESP_RSEQ];
esp_log("Read Sequence Step=%02x, intr=%02x.\n", ret, dev->rregs[ESP_RINTR]);
break;
case ESP_CFG2:
ret = dev->rregs[ESP_CFG2] & 0x40;
esp_log("Read CFG2 Register=%02x.\n", ret);
break;
default:
ret = dev->rregs[saddr];
break;
}
esp_log("%04X:%08X: Read ESP reg%02x=%02x\n", CS, cpu_state.pc, saddr, ret);
return ret;
}
static void
esp_reg_write(esp_t *dev, uint32_t saddr, uint32_t val)
{
esp_log("Write reg %02x = %02x\n", saddr, val);
switch (saddr) {
case ESP_TCHI:
dev->tchi_written = 1;
fallthrough;
case ESP_TCLO:
case ESP_TCMID:
esp_log("ESP TCW reg%02x = %02x.\n", saddr, val);
dev->rregs[ESP_RSTAT] &= ~STAT_TC;
break;
case ESP_FIFO:
if (!fifo8_is_full(&dev->fifo))
esp_fifo_push(dev, val);
esp_do_nodma(dev);
break;
case ESP_CMD:
dev->rregs[ESP_CMD] = val;
if (!esp_cmd_is_valid(dev, dev->rregs[ESP_CMD])) {
dev->rregs[ESP_RSTAT] |= INTR_IL;
esp_raise_irq(dev);
break;
}
if (val & CMD_DMA) {
dev->dma = 1;
/* Reload DMA counter. */
esp_set_tc(dev, esp_get_stc(dev));
if (!esp_get_stc(dev)) {
if (dev->rregs[ESP_CFG2] & 0x40)
esp_set_tc(dev, 0x1000000);
else
esp_set_tc(dev, 0x10000);
}
} else {
dev->dma = 0;
esp_log("ESP Command not for DMA\n");
}
if (dev->mca)
esp_dma_enable(dev, dev->dma);
esp_log("[%04X:%08X]: ESP Command = %02x, DMA ena1 = %d, DMA ena2 = %d\n", CS, cpu_state.pc, val & (CMD_CMD | CMD_DMA), dev->dma, dev->dma_enabled);
switch (val & CMD_CMD) {
case CMD_NOP:
break;
case CMD_FLUSH:
fifo8_reset(&dev->fifo);
timer_on_auto(&dev->timer, 10.0);
break;
case CMD_RESET:
if (dev->mca) {
esp_lower_irq(dev);
esp_hard_reset(dev);
} else
esp_pci_soft_reset(dev);
break;
case CMD_BUSRESET:
esp_log("ESP Bus Reset val=%02x.\n", (dev->rregs[ESP_CFG1] & CFG1_RESREPT));
if (dev->mca) {
esp_lower_irq(dev);
esp_hard_reset(dev);
} else
esp_pci_soft_reset(dev);
for (uint8_t i = 0; i < 16; i++) {
scsi_device_reset(&scsi_devices[dev->bus][i]);
}
if (!(dev->rregs[ESP_CFG1] & CFG1_RESREPT)) {
dev->rregs[ESP_RINTR] |= INTR_RST;
esp_log("ESP Bus Reset with IRQ\n");
esp_raise_irq(dev);
}
break;
case CMD_TI:
esp_log("Transfer Information val=%02X\n", val);
break;
case CMD_ICCS:
esp_log("ESP SCSI ICCS\n");
esp_write_response(dev);
dev->rregs[ESP_RINTR] |= INTR_FC;
dev->rregs[ESP_RSTAT] |= STAT_MI;
break;
case CMD_SEL:
handle_s_without_atn(dev);
break;
case CMD_SELATN:
handle_satn(dev);
break;
case CMD_SELATNS:
handle_satn_stop(dev);
break;
case CMD_MSGACC:
dev->asc_mode = ESP_ASC_MODE_DIS;
dev->rregs[ESP_RINTR] |= INTR_DC;
dev->rregs[ESP_RFLAGS] = 0;
esp_log("ESP SCSI MSGACC IRQ\n");
esp_raise_irq(dev);
break;
case CMD_PAD:
esp_log("val = %02X\n", val);
timer_stop(&dev->timer);
timer_on_auto(&dev->timer, dev->period);
esp_log("ESP Transfer Pad\n");
break;
case CMD_SATN:
case CMD_RSTATN:
break;
case CMD_ENSEL:
dev->rregs[ESP_RINTR] = 0;
esp_log("ESP Enable Selection.\n");
break;
case CMD_DISSEL:
dev->rregs[ESP_RINTR] = 0;
esp_log("ESP Disable Selection\n");
esp_raise_irq(dev);
break;
default:
break;
}
break;
case ESP_WBUSID:
esp_log("ESP BUS ID=%d.\n", val & BUSID_DID);
break;
case ESP_WSEL:
case ESP_WSYNTP:
case ESP_WSYNO:
break;
case ESP_CFG1:
dev->rregs[ESP_CFG1] = val;
esp_log("ESP CFG1=%02x.\n", val);
break;
case ESP_CFG2:
dev->rregs[ESP_CFG2] = val & ~0x8f;
esp_log("ESP CFG2=%02x.\n", dev->rregs[ESP_CFG2]);
break;
case ESP_CFG3:
case ESP_RES3:
case ESP_RES4:
dev->rregs[saddr] = val;
break;
case ESP_WCCF:
case ESP_WTEST:
break;
default:
esp_log("Unhandled writeb 0x%x = 0x%x\n", saddr, val);
break;
}
dev->wregs[saddr] = val;
}
static void
esp_pci_dma_memory_rw(esp_t *dev, uint8_t *buf, uint32_t len, int dir)
{
uint32_t addr;
int expected_dir;
int sg_pos = 0;
uint32_t DMALen;
uint32_t DMAPtr;
uint32_t WAC = 0;
if (dev->dma_regs[DMA_CMD] & DMA_CMD_DIR)
expected_dir = READ_FROM_DEVICE;
else
expected_dir = WRITE_TO_DEVICE;
if (dir != expected_dir) {
esp_log("ESP unexpected direction\n");
return;
}
if (dev->dma_regs[DMA_WBC] < len)
len = dev->dma_regs[DMA_WBC];
esp_log("DMA Length=%d.\n", len);
if (dev->dma_regs[DMA_CMD] & DMA_CMD_MDL) {
if (len) {
dma_bm_read(dev->dma_regs[DMA_WMAC], (uint8_t *)&DMAPtr, 4, 4);
dev->dma_regs[DMA_WAC] = DMAPtr | dev->dma_regs[DMA_SPA];
DMALen = len;
WAC = dev->dma_regs[DMA_SPA];
for (uint32_t i = 0; i < len; i += 4) {
if (WAC == 0) {
dma_bm_read(dev->dma_regs[DMA_WMAC], (uint8_t *)&DMAPtr, 4, 4);
dev->dma_regs[DMA_WAC] = DMAPtr;
}
addr = dev->dma_regs[DMA_WAC];
esp_log("Data Buffer %s: length %d (%u), pointer 0x%04X\n",
expected_dir ? "read" : "write", len, len, addr);
if (addr && DMALen) {
if (expected_dir)
dma_bm_write(addr, &buf[sg_pos], DMALen, 4);
else
dma_bm_read(addr, &buf[sg_pos], DMALen, 4);
}
sg_pos += 4;
DMALen -= 4;
/* update status registers */
dev->dma_regs[DMA_WBC] -= 4;
dev->dma_regs[DMA_WAC] += 4;
WAC += 4;
if (WAC >= 0x1000) {
WAC = 0;
dev->dma_regs[DMA_WMAC] += 4;
}
if (DMALen < 0)
DMALen = 0;
if (dev->dma_regs[DMA_WBC] <= 0) {
dev->dma_regs[DMA_WBC] = 0;
break;
}
}
}
} else {
if (len) {
addr = dev->dma_regs[DMA_WAC];
if (expected_dir)
dma_bm_write(addr, buf, len, 4);
else
dma_bm_read(addr, buf, len, 4);
/* update status registers */
dev->dma_regs[DMA_WBC] -= len;
dev->dma_regs[DMA_WAC] += len;
}
}
esp_log("Finished count=%d.\n", dev->dma_regs[DMA_WBC]);
if (dev->dma_regs[DMA_WBC] == 0) {
esp_log("DMA transfer finished.\n");
dev->dma_regs[DMA_STAT] |= DMA_STAT_DONE;
}
}
static uint32_t
esp_pci_dma_read(esp_t *dev, uint16_t saddr)
{
uint32_t ret;
ret = dev->dma_regs[saddr];
if (saddr == DMA_STAT) {
if (!(dev->sbac & SBAC_STATUS)) {
dev->dma_regs[DMA_STAT] &= ~(DMA_STAT_PWDN | DMA_STAT_ERROR | DMA_STAT_ABORT | DMA_STAT_DONE);
esp_log("ESP PCI DMA Read done cleared\n");
esp_pci_update_irq(dev);
}
}
esp_log("ESP PCI DMA Read regs addr=%04x, ret=%06x, STAT=%02x\n", saddr, ret, dev->dma_regs[DMA_STAT]);
return ret;
}
static void
esp_pci_dma_write(esp_t *dev, uint16_t saddr, uint32_t val)
{
uint32_t mask;
switch (saddr) {
case DMA_CMD:
dev->dma_regs[DMA_CMD] = val;
esp_log("ESP PCI DMA Write CMD = %02x\n", val & DMA_CMD_MASK);
switch (val & DMA_CMD_MASK) {
case 0: /*IDLE*/
esp_log("IDLE/NOP\n");
esp_dma_enable(dev, 0);
break;
case 1: /*BLAST*/
dev->dma_regs[DMA_STAT] |= DMA_STAT_BCMBLT;
break;
case 2: /*ABORT*/
scsi_device_command_stop(&scsi_devices[dev->bus][dev->id]);
break;
case 3: /*START*/
dev->dma_regs[DMA_WBC] = dev->dma_regs[DMA_STC];
dev->dma_regs[DMA_WAC] = dev->dma_regs[DMA_SPA];
if (val & DMA_CMD_MDL)
dev->dma_regs[DMA_WMAC] = dev->dma_regs[DMA_SMDLA] & 0xfffffffc;
dev->dma_regs[DMA_STAT] &= ~(DMA_STAT_BCMBLT | DMA_STAT_SCSIINT | DMA_STAT_DONE | DMA_STAT_ABORT | DMA_STAT_ERROR | DMA_STAT_PWDN);
esp_dma_enable(dev, 1);
esp_log("PCI DMA enable, MDL bit=%02x, SPA=%08x, SMDLA=%08x, STC=%d, ID=%d, SCSICMD=%02x.\n", val & DMA_CMD_MDL, dev->dma_regs[DMA_SPA], dev->dma_regs[DMA_SMDLA], dev->dma_regs[DMA_STC], dev->id, dev->cmdfifo.data[1]);
break;
default: /* can't happen */
abort();
break;
}
break;
case DMA_STC:
dev->dma_regs[DMA_STC] = val;
esp_log("DMASTC PCI write=%08x.\n", val);
break;
case DMA_SPA:
dev->dma_regs[DMA_SPA] = val;
esp_log("DMASPA PCI write=%08x.\n", val);
break;
case DMA_SMDLA:
dev->dma_regs[DMA_SMDLA] = val;
esp_log("DMASMDLA PCI write=%08x.\n", val);
break;
case DMA_STAT:
if (dev->sbac & SBAC_STATUS) {
/* clear some bits on write */
mask = DMA_STAT_ERROR | DMA_STAT_ABORT | DMA_STAT_DONE;
dev->dma_regs[DMA_STAT] &= ~(val & mask);
esp_pci_update_irq(dev);
}
break;
default:
break;
}
}
static void
esp_pci_soft_reset(esp_t *dev)
{
esp_irq(dev, 0);
dev->rregs[ESP_RSTAT] &= ~STAT_INT;
esp_pci_hard_reset(dev);
}
static void
esp_pci_hard_reset(esp_t *dev)
{
esp_hard_reset(dev);
dev->dma_regs[DMA_CMD] &= ~(DMA_CMD_DIR | DMA_CMD_INTE_D | DMA_CMD_INTE_P
| DMA_CMD_MDL | DMA_CMD_DIAG | DMA_CMD_MASK);
dev->dma_regs[DMA_WBC] &= ~0xffff;
dev->dma_regs[DMA_WAC] = 0xffffffff;
dev->dma_regs[DMA_STAT] &= ~(DMA_STAT_BCMBLT | DMA_STAT_SCSIINT
| DMA_STAT_DONE | DMA_STAT_ABORT
| DMA_STAT_ERROR);
dev->dma_regs[DMA_WMAC] = 0xfffffffc;
}
static uint32_t
esp_io_pci_read(esp_t *dev, uint32_t addr, unsigned int size)
{
uint32_t ret;
addr &= 0x7f;
if (addr < 0x40) {
/* SCSI core reg */
ret = esp_reg_read(dev, addr >> 2);
} else if (addr < 0x60) {
/* PCI DMA CCB */
ret = esp_pci_dma_read(dev, (addr - 0x40) >> 2);
esp_log("ESP PCI DMA CCB read addr = %02x, ret = %02x\n", (addr - 0x40) >> 2, ret);
} else if (addr == 0x70) {
/* DMA SCSI Bus and control */
ret = dev->sbac;
esp_log("ESP PCI SBAC read=%08x\n", ret);
} else {
/* Invalid region */
ret = 0;
}
/* give only requested data */
ret >>= (addr & 3) * 8;
ret &= ~(~(uint64_t) 0 << (8 * size));
if (addr == 0x70)
esp_log("%04X:%08X: SBAC PCI I/O read: addr=%02x, val=%02x\n", CS, cpu_state.pc, addr, ret);
else
esp_log("%04X:%08X: ESP PCI I/O read: addr=%02x, val=%02x\n", CS, cpu_state.pc, addr, ret);
return ret;
}
static void
esp_io_pci_write(esp_t *dev, uint32_t addr, uint32_t val, unsigned int size)
{
uint32_t current;
uint32_t mask;
int shift;
addr &= 0x7f;
if (size < 4 || addr & 3) {
/* need to upgrade request: we only support 4-bytes accesses */
current = 0;
if (addr < 0x40) {
current = dev->wregs[addr >> 2];
} else if (addr < 0x60) {
current = dev->dma_regs[(addr - 0x40) >> 2];
} else if (addr == 0x70) {
current = dev->sbac;
}
shift = (4 - size) * 8;
mask = (~(uint32_t) 0 << shift) >> shift;
shift = ((4 - (addr & 3)) & 3) * 8;
val <<= shift;
val |= current & ~(mask << shift);
addr &= ~3;
size = 4;
}
esp_log("ESP PCI I/O write: addr = %02x, val = %02x\n", addr, val);
if (addr < 0x40) {
/* SCSI core reg */
esp_reg_write(dev, addr >> 2, val);
} else if (addr < 0x60) {
/* PCI DMA CCB */
esp_pci_dma_write(dev, (addr - 0x40) >> 2, val);
} else if (addr == 0x70) {
/* DMA SCSI Bus and control */
esp_log("ESP PCI SBAC write=%08x\n", val);
dev->sbac = val;
}
}
static void
esp_pci_io_writeb(uint16_t addr, uint8_t val, void *priv)
{
esp_t *dev = (esp_t *) priv;
esp_io_pci_write(dev, addr, val, 1);
}
static void
esp_pci_io_writew(uint16_t addr, uint16_t val, void *priv)
{
esp_t *dev = (esp_t *) priv;
esp_io_pci_write(dev, addr, val, 2);
}
static void
esp_pci_io_writel(uint16_t addr, uint32_t val, void *priv)
{
esp_t *dev = (esp_t *) priv;
esp_io_pci_write(dev, addr, val, 4);
}
static uint8_t
esp_pci_io_readb(uint16_t addr, void *priv)
{
esp_t *dev = (esp_t *) priv;
return esp_io_pci_read(dev, addr, 1);
}
static uint16_t
esp_pci_io_readw(uint16_t addr, void *priv)
{
esp_t *dev = (esp_t *) priv;
return esp_io_pci_read(dev, addr, 2);
}
static uint32_t
esp_pci_io_readl(uint16_t addr, void *priv)
{
esp_t *dev = (esp_t *) priv;
return esp_io_pci_read(dev, addr, 4);
}
static void
esp_io_set(esp_t *dev, uint32_t base, uint16_t len)
{
esp_log("ESP: [PCI] Setting I/O handler at %04X\n", base);
io_sethandler(base, len,
esp_pci_io_readb, esp_pci_io_readw, esp_pci_io_readl,
esp_pci_io_writeb, esp_pci_io_writew, esp_pci_io_writel, dev);
}
static void
esp_io_remove(esp_t *dev, uint32_t base, uint16_t len)
{
esp_log("ESP: [PCI] Removing I/O handler at %04X\n", base);
io_removehandler(base, len,
esp_pci_io_readb, esp_pci_io_readw, esp_pci_io_readl,
esp_pci_io_writeb, esp_pci_io_writew, esp_pci_io_writel, dev);
}
static void
esp_bios_set_addr(esp_t *dev, uint32_t base)
{
mem_mapping_set_addr(&dev->bios.mapping, base, 0x8000);
}
static void
esp_bios_disable(esp_t *dev)
{
mem_mapping_disable(&dev->bios.mapping);
}
#define EE_ADAPT_SCSI_ID 64
#define EE_MODE2 65
#define EE_DELAY 66
#define EE_TAG_CMD_NUM 67
#define EE_ADAPT_OPTIONS 68
#define EE_BOOT_SCSI_ID 69
#define EE_BOOT_SCSI_LUN 70
#define EE_CHKSUM1 126
#define EE_CHKSUM2 127
#define EE_ADAPT_OPTION_F6_F8_AT_BOOT 0x01
#define EE_ADAPT_OPTION_BOOT_FROM_CDROM 0x02
#define EE_ADAPT_OPTION_INT13 0x04
#define EE_ADAPT_OPTION_SCAM_SUPPORT 0x08
/*To do: make this separate from the SCSI card*/
static void
dc390_save_eeprom(esp_t *dev)
{
FILE *fp = nvr_fopen(dev->nvr_path, "wb");
if (!fp)
return;
fwrite(dev->eeprom.data, 1, 128, fp);
fclose(fp);
}
static void
dc390_write_eeprom(esp_t *dev, int ena, int clk, int dat)
{
/*Actual EEPROM is the same as the one used by the ATI cards, the 93cxx series.*/
ati_eeprom_t *eeprom = &dev->eeprom;
uint8_t tick = eeprom->count;
uint8_t eedo = eeprom->out;
uint16_t address = eeprom->address;
uint8_t command = eeprom->opcode;
esp_log("EEPROM CS=%02x,SK=%02x,DI=%02x,DO=%02x,tick=%d\n",
ena, clk, dat, eedo, tick);
if (!eeprom->oldena && ena) {
esp_log("EEPROM Start chip select cycle\n");
tick = 0;
command = 0;
address = 0;
} else if (eeprom->oldena && !ena) {
if (!eeprom->wp) {
uint8_t subcommand = address >> 4;
if (command == 0 && subcommand == 2) {
esp_log("EEPROM Erase All\n");
for (address = 0; address < 64; address++)
eeprom->data[address] = 0xffff;
dc390_save_eeprom(dev);
} else if (command == 3) {
esp_log("EEPROM Erase Word\n");
eeprom->data[address] = 0xffff;
dc390_save_eeprom(dev);
} else if (tick >= 26) {
if (command == 1) {
esp_log("EEPROM Write Word\n");
eeprom->data[address] &= eeprom->dat;
dc390_save_eeprom(dev);
} else if (command == 0 && subcommand == 1) {
esp_log("EEPROM Write All\n");
for (address = 0; address < 64; address++)
eeprom->data[address] &= eeprom->dat;
dc390_save_eeprom(dev);
}
}
}
eedo = 1;
esp_log("EEPROM DO read\n");
} else if (ena && !eeprom->oldclk && clk) {
if (tick == 0) {
if (dat == 0) {
esp_log("EEPROM Got correct 1st start bit, waiting for 2nd start bit (1)\n");
tick++;
} else {
esp_log("EEPROM Wrong 1st start bit (is 1, should be 0)\n");
tick = 2;
}
} else if (tick == 1) {
if (dat != 0) {
esp_log("EEPROM Got correct 2nd start bit, getting command + address\n");
tick++;
} else {
esp_log("EEPROM 1st start bit is longer than needed\n");
}
} else if (tick < 4) {
tick++;
command <<= 1;
if (dat)
command += 1;
} else if (tick < 10) {
tick++;
address = (address << 1) | dat;
if (tick == 10) {
esp_log("EEPROM command = %02x, address = %02x (val = %04x)\n", command,
address, eeprom->data[address]);
if (command == 2)
eedo = 0;
address = address % 64;
if (command == 0) {
switch (address >> 4) {
case 0:
esp_log("EEPROM Write disable command\n");
eeprom->wp = 1;
break;
case 1:
esp_log("EEPROM Write all command\n");
break;
case 2:
esp_log("EEPROM Erase all command\n");
break;
case 3:
esp_log("EEPROM Write enable command\n");
eeprom->wp = 0;
break;
default:
break;
}
} else {
esp_log("EEPROM Read, write or erase word\n");
eeprom->dat = eeprom->data[address];
}
}
} else if (tick < 26) {
tick++;
if (command == 2) {
esp_log("EEPROM Read Word\n");
eedo = ((eeprom->dat & 0x8000) != 0);
}
eeprom->dat <<= 1;
eeprom->dat += dat;
} else {
esp_log("EEPROM Additional unneeded tick, not processed\n");
}
}
eeprom->count = tick;
eeprom->oldena = ena;
eeprom->oldclk = clk;
eeprom->out = eedo;
eeprom->address = address;
eeprom->opcode = command;
esp_log("EEPROM EEDO = %d\n", eeprom->out);
}
static void
dc390_load_eeprom(esp_t *dev)
{
ati_eeprom_t *eeprom = &dev->eeprom;
uint8_t *nvr = (uint8_t *) eeprom->data;
int i;
uint16_t checksum = 0;
FILE *fp;
eeprom->out = 1;
fp = nvr_fopen(dev->nvr_path, "rb");
if (fp) {
esp_log("EEPROM Load\n");
if (fread(nvr, 1, 128, fp) != 128)
fatal("dc390_eeprom_load(): Error reading data\n");
fclose(fp);
} else {
for (i = 0; i < 16; i++) {
nvr[i * 2] = 0x57;
nvr[i * 2 + 1] = 0x00;
}
esp_log("EEPROM Defaults\n");
nvr[EE_ADAPT_SCSI_ID] = 7;
nvr[EE_MODE2] = 0x0f;
nvr[EE_TAG_CMD_NUM] = 0x04;
nvr[EE_ADAPT_OPTIONS] = EE_ADAPT_OPTION_F6_F8_AT_BOOT | EE_ADAPT_OPTION_BOOT_FROM_CDROM | EE_ADAPT_OPTION_INT13;
for (i = 0; i < EE_CHKSUM1; i += 2) {
checksum += ((nvr[i] & 0xff) | (nvr[i + 1] << 8));
esp_log("Checksum calc = %04x, nvr = %02x\n", checksum, nvr[i]);
}
checksum = 0x1234 - checksum;
nvr[EE_CHKSUM1] = checksum & 0xff;
nvr[EE_CHKSUM2] = checksum >> 8;
esp_log("EEPROM Checksum = %04x\n", checksum);
}
}
static uint8_t
esp_pci_read(UNUSED(int func), int addr, void *priv)
{
esp_t *dev = (esp_t *) priv;
esp_log("ESP PCI: Reading register %02X\n", addr & 0xff);
switch (addr) {
case 0x00:
// esp_log("ESP PCI: Read DO line = %02x\n", dev->eeprom.out);
if (!dev->has_bios || dev->local)
return 0x22;
else {
if (dev->eeprom.out)
return 0x22;
else {
dev->eeprom.out = 1;
return 2;
}
}
break;
case 0x01:
return 0x10;
case 0x02:
return 0x20;
case 0x03:
return 0x20;
case 0x04:
return esp_pci_regs[0x04] | 0x80; /*Respond to IO*/
case 0x05:
return esp_pci_regs[0x05];
case 0x07:
return esp_pci_regs[0x07] | 0x02;
case 0x08:
return (dev->local == 1) ? 0 : 0x10; /*Revision ID*/
case 0x09:
return 0; /*Programming interface*/
case 0x0A:
return 0; /*devubclass*/
case 0x0B:
return 1; /*Class code*/
case 0x0C:
return esp_pci_regs[0x0c];
case 0x0E:
return 0; /*Header type */
case 0x10:
return esp_pci_bar[0].addr_regs[0] | 0x01; /*I/O space*/
case 0x11:
return esp_pci_bar[0].addr_regs[1];
case 0x12:
return esp_pci_bar[0].addr_regs[2];
case 0x13:
return esp_pci_bar[0].addr_regs[3];
case 0x30:
if (!dev->has_bios)
return 0;
return esp_pci_bar[1].addr_regs[0];
case 0x31:
if (!dev->has_bios)
return 0;
return esp_pci_bar[1].addr_regs[1];
case 0x32:
if (!dev->has_bios)
return 0;
return esp_pci_bar[1].addr_regs[2];
case 0x33:
if (!dev->has_bios)
return 0;
return esp_pci_bar[1].addr_regs[3];
case 0x3C:
return dev->irq;
case 0x3D:
return PCI_INTA;
case 0x3E:
return 0x04;
case 0x3F:
return 0x28;
case 0x40 ... 0x4f:
return esp_pci_regs[addr];
default:
break;
}
return 0;
}
static void
esp_pci_write(UNUSED(int func), int addr, uint8_t val, void *priv)
{
esp_t *dev = (esp_t *) priv;
uint8_t valxor;
int eesk;
int eedi;
esp_log("%04X:%08X: ESP PCI: Write value %02X to register %02X\n", CS, cpu_state.pc, val, addr);
if (!dev->local) {
if ((addr >= 0x80) && (addr <= 0xFF)) {
if (addr == 0x80) {
eesk = val & 0x80 ? 1 : 0;
eedi = val & 0x40 ? 1 : 0;
dc390_write_eeprom(dev, 1, eesk, eedi);
} else if (addr == 0xc0)
dc390_write_eeprom(dev, 0, 0, 0);
// esp_log("ESP PCI: Write value %02X to register %02X\n", val, addr);
return;
}
}
switch (addr) {
case 0x04:
valxor = (val & 0x01) ^ esp_pci_regs[addr];
if (valxor & PCI_COMMAND_IO) {
esp_io_remove(dev, dev->PCIBase, 0x80);
if ((val & PCI_COMMAND_IO) && (dev->PCIBase != 0))
esp_io_set(dev, dev->PCIBase, 0x80);
}
if (dev->has_bios && (valxor & PCI_COMMAND_MEM)) {
esp_bios_disable(dev);
if ((val & PCI_COMMAND_MEM) && (esp_pci_bar[1].addr & 0x00000001))
esp_bios_set_addr(dev, dev->BIOSBase);
}
if (dev->has_bios)
esp_pci_regs[addr] = val & 0x47;
else
esp_pci_regs[addr] = val & 0x45;
break;
case 0x05:
esp_pci_regs[addr] = val & 0x01;
break;
case 0x07:
esp_pci_regs[addr] &= ~(val & 0xf9);
break;
case 0x0c:
esp_pci_regs[addr] = val;
break;
case 0x10:
case 0x11:
case 0x12:
case 0x13:
/* I/O Base set. */
/* First, remove the old I/O. */
esp_io_remove(dev, dev->PCIBase, 0x80);
/* Then let's set the PCI regs. */
esp_pci_bar[0].addr_regs[addr & 3] = val;
/* Then let's calculate the new I/O base. */
esp_pci_bar[0].addr &= 0xff80;
dev->PCIBase = esp_pci_bar[0].addr;
/* Log the new base. */
// esp_log("ESP PCI: New I/O base is %04X\n" , dev->PCIBase);
/* We're done, so get out of the here. */
if (esp_pci_regs[4] & PCI_COMMAND_IO) {
if (dev->PCIBase != 0) {
esp_io_set(dev, dev->PCIBase, 0x80);
}
}
return;
case 0x30:
case 0x31:
case 0x32:
case 0x33:
if (!dev->has_bios)
return;
/* BIOS Base set. */
/* First, remove the old I/O. */
esp_bios_disable(dev);
/* Then let's set the PCI regs. */
esp_pci_bar[1].addr_regs[addr & 3] = val;
/* Then let's calculate the new I/O base. */
esp_pci_bar[1].addr &= 0xffff0001;
dev->BIOSBase = esp_pci_bar[1].addr & 0xffff0000;
/* Log the new base. */
// esp_log("ESP PCI: New BIOS base is %08X\n" , dev->BIOSBase);
/* We're done, so get out of the here. */
if ((esp_pci_regs[0x04] & PCI_COMMAND_MEM) && (esp_pci_bar[1].addr & 0x00000001))
esp_bios_set_addr(dev, dev->BIOSBase);
return;
case 0x3c:
esp_pci_regs[addr] = val;
dev->irq = val;
esp_log("ESP IRQ now: %i\n", val);
return;
case 0x40 ... 0x4f:
esp_pci_regs[addr] = val;
return;
default:
break;
}
}
static void
esp_pci_reset(void *priv)
{
esp_t *dev = (esp_t *) priv;
timer_disable(&dev->timer);
reset_state.bios.mapping = dev->bios.mapping;
reset_state.timer = dev->timer;
reset_state.pci_slot = dev->pci_slot;
memcpy(dev, &reset_state, sizeof(esp_t));
esp_pci_soft_reset(dev);
}
static void *
dc390_init(const device_t *info)
{
esp_t *dev = calloc(1, sizeof(esp_t));
const char *bios_rev = NULL;
uint32_t mask = 0;
uint32_t size = 0x8000;
dev->bus = scsi_get_bus();
dev->local = info->local;
dev->mca = 0;
fifo8_create(&dev->fifo, ESP_FIFO_SZ);
fifo8_create(&dev->cmdfifo, ESP_CMDFIFO_SZ);
dev->PCIBase = 0;
dev->MMIOBase = 0;
pci_add_card(PCI_ADD_NORMAL, esp_pci_read, esp_pci_write, dev, &dev->pci_slot);
esp_pci_bar[0].addr_regs[0] = 1;
esp_pci_regs[0x04] = 3;
dev->has_bios = device_get_config_int("bios");
if (dev->has_bios) {
dev->BIOSBase = 0xc8000;
if (dev->local) {
bios_rev = (char *) device_get_config_bios("bios_rev");
dev->bios_path = (char *) device_get_bios_file(info, bios_rev, 0);
if (!strcmp(bios_rev, "v3_43"))
mask = 0x4000;
else if (!strcmp(bios_rev, "v3_01_amd"))
size = 0x4000;
rom_init(&dev->bios, dev->bios_path, dev->BIOSBase, size, size - 1, mask, MEM_MAPPING_EXTERNAL);
} else
rom_init(&dev->bios, DC390_ROM, dev->BIOSBase, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);
}
/* Enable our BIOS space in PCI, if needed. */
if (dev->has_bios)
esp_pci_bar[1].addr = 0xffff0000;
else
esp_pci_bar[1].addr = 0;
if (dev->has_bios)
esp_bios_disable(dev);
if (!dev->local) {
sprintf(dev->nvr_path, "dc390_%i.nvr", device_get_instance());
/* Load the serial EEPROM. */
dc390_load_eeprom(dev);
}
esp_pci_hard_reset(dev);
for (uint8_t i = 0; i < 16; i++) {
scsi_device_reset(&scsi_devices[dev->bus][i]);
}
timer_add(&dev->timer, esp_callback, dev, 0);
scsi_bus_set_speed(dev->bus, 10000000.0);
memcpy(&reset_state, dev, sizeof(esp_t));
return dev;
}
static uint16_t
ncr53c9x_in(uint16_t port, void *priv)
{
esp_t *dev = (esp_t *) priv;
uint16_t ret = 0xffff;
port &= 0x1f;
if (port >= 0x10)
ret = esp_reg_read(dev, port - 0x10);
else {
switch (port) {
case 0x02:
ret = dev->dma_86c01.mode;
break;
case 0x0c:
if (dev->dma_86c01.mode & 0x40)
dev->dma_86c01.status |= 0x01;
else
dev->dma_86c01.status &= ~0x01;
if (dev->dma_enabled)
dev->dma_86c01.status |= 0x02;
else
dev->dma_86c01.status &= ~0x02;
ret = dev->dma_86c01.status;
break;
default:
break;
}
}
esp_log("[%04X:%08X]: NCR53c9x DMA read port = %02x, ret = %02x, local = %d.\n\n", CS, cpu_state.pc, port, ret, dev->local);
return ret;
}
static uint8_t
ncr53c9x_inb(uint16_t port, void *priv)
{
return ncr53c9x_in(port, priv);
}
static uint16_t
ncr53c9x_inw(uint16_t port, void *priv)
{
return (ncr53c9x_in(port, priv) & 0xff) | (ncr53c9x_in(port + 1, priv) << 8);
}
static void
ncr53c9x_out(uint16_t port, uint16_t val, void *priv)
{
esp_t *dev = (esp_t *) priv;
port &= 0x1f;
esp_log("[%04X:%08X]: NCR53c9x DMA write port = %02x, val = %02x.\n\n", CS, cpu_state.pc, port, val);
if (port >= 0x10)
esp_reg_write(dev, port - 0x10, val);
else {
if (port == 0x02)
dev->dma_86c01.mode = val;
}
}
static void
ncr53c9x_outb(uint16_t port, uint8_t val, void *priv)
{
ncr53c9x_out(port, val, priv);
}
static void
ncr53c9x_outw(uint16_t port, uint16_t val, void *priv)
{
ncr53c9x_out(port, val & 0xff, priv);
ncr53c9x_out(port + 1, val >> 8, priv);
}
static uint8_t
ncr53c9x_mca_read(int port, void *priv)
{
const esp_t *dev = (esp_t *) priv;
return (dev->pos_regs[port & 7]);
}
static void
ncr53c9x_mca_write(int port, uint8_t val, void *priv)
{
esp_t *dev = (esp_t *) priv;
static const uint16_t ncrmca_iobase[] = {
0, 0x240, 0x340, 0x400, 0x420, 0x3240, 0x8240, 0xa240
};
/* MCA does not write registers below 0x0100. */
if (port < 0x0102)
return;
/* Save the MCA register value. */
dev->pos_regs[port & 7] = val;
/* This is always necessary so that the old handler doesn't remain. */
if (dev->Base != 0) {
io_removehandler(dev->Base, 0x20,
ncr53c9x_inb, ncr53c9x_inw, NULL,
ncr53c9x_outb, ncr53c9x_outw, NULL, dev);
}
/* Get the new assigned I/O base address. */
dev->Base = ncrmca_iobase[(dev->pos_regs[2] & 0x0e) >> 1];
/* Save the new IRQ and DMA channel values. */
dev->irq = 3 + (2 * ((dev->pos_regs[2] & 0x30) >> 4));
if (dev->irq == 9)
dev->irq = 2;
dev->DmaChannel = dev->pos_regs[3] & 0x0f;
/*
* Get misc SCSI config stuff. For now, we are only
* interested in the configured HA target ID.
*/
dev->HostID = 6 + ((dev->pos_regs[5] & 0x20) ? 1 : 0);
/* Initialize the device if fully configured. */
if (dev->pos_regs[2] & 0x01) {
if (dev->Base != 0) {
/* Card enabled; register (new) I/O handler. */
io_sethandler(dev->Base, 0x20,
ncr53c9x_inb, ncr53c9x_inw, NULL,
ncr53c9x_outb, ncr53c9x_outw, NULL, dev);
esp_hard_reset(dev);
for (uint8_t i = 0; i < 8; i++)
scsi_device_reset(&scsi_devices[dev->bus][i]);
}
/* Say hello. */
esp_log("NCR 53c9x: I/O=%04x, IRQ=%d, DMA=%d, HOST ID %i\n",
dev->Base, dev->irq, dev->DmaChannel, dev->HostID);
}
}
static uint8_t
ncr53c9x_mca_feedb(void *priv)
{
const esp_t *dev = (esp_t *) priv;
return (dev->pos_regs[2] & 0x01);
}
static void *
ncr53c9x_mca_init(const device_t *info)
{
esp_t *dev = calloc(1, sizeof(esp_t));
dev->bus = scsi_get_bus();
dev->mca = 1;
dev->local = info->local;
fifo8_create(&dev->fifo, ESP_FIFO_SZ);
fifo8_create(&dev->cmdfifo, ESP_CMDFIFO_SZ);
dev->pos_regs[0] = 0x4f; /* MCA board ID */
dev->pos_regs[1] = 0x7f;
mca_add(ncr53c9x_mca_read, ncr53c9x_mca_write, ncr53c9x_mca_feedb, NULL, dev);
esp_hard_reset(dev);
for (uint8_t i = 0; i < 8; i++)
scsi_device_reset(&scsi_devices[dev->bus][i]);
timer_add(&dev->timer, esp_callback, dev, 0);
scsi_bus_set_speed(dev->bus, 10000000.0);
return dev;
}
static void
esp_close(void *priv)
{
esp_t *dev = (esp_t *) priv;
if (dev) {
fifo8_destroy(&dev->fifo);
fifo8_destroy(&dev->cmdfifo);
free(dev);
dev = NULL;
}
}
static const device_config_t bios_enable_config[] = {
// clang-format off
{
.name = "bios",
.description = "Enable BIOS",
.type = CONFIG_BINARY,
.default_string = NULL,
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.selection = { { 0 } },
.bios = { { 0 } }
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
static const device_config_t am53c974_bios_enable_config[] = {
// clang-format off
{
.name = "bios_rev",
.description = "BIOS Revision",
.type = CONFIG_BIOS,
.default_string = "v3_01_amd",
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.bios = {
{
.name = "Version 3.01 (AMD)",
.internal_name = "v3_01_amd",
.bios_type = BIOS_NORMAL,
.files_no = 1,
.local = 0,
.size = 16384,
.files = { AM53C974_3_01_AMD_ROM, "" }
},
{ .files_no = 0 }
},
},
{
.name = "bios",
.description = "Enable BIOS",
.type = CONFIG_BINARY,
.default_string = NULL,
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.selection = { { 0 } },
.bios = { { 0 } }
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
static const device_config_t am53c974a_bios_enable_config[] = {
// clang-format off
{
.name = "bios_rev",
.description = "BIOS Revision",
.type = CONFIG_BIOS,
.default_string = "v3_01_amd",
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.bios = {
{
.name = "Version 3.01 (AMD)",
.internal_name = "v3_01_amd",
.bios_type = BIOS_NORMAL,
.files_no = 1,
.local = 0,
.size = 16384,
.files = { AM53C974_3_01_AMD_ROM, "" }
},
{
.name = "Version 3.43 (Dawicontrol)",
.internal_name = "v3_43",
.bios_type = BIOS_NORMAL,
.files_no = 1,
.local = 0,
.size = 32768,
.files = { AM53C974_3_43_ROM, "" }
},
{
.name = "Version 4.00 (Dawicontrol)",
.internal_name = "v4_00",
.bios_type = BIOS_NORMAL,
.files_no = 1,
.local = 0,
.size = 32768,
.files = { AM53C974_4_00_ROM, "" }
},
{
.name = "Version 5.00 (Dawicontrol)",
.internal_name = "v5_00",
.bios_type = BIOS_NORMAL,
.files_no = 1,
.local = 0,
.size = 32768,
.files = { AM53C974_5_00_ROM, "" }
},
{
.name = "Version 5.11 (Dawicontrol)",
.internal_name = "v5_11",
.bios_type = BIOS_NORMAL,
.files_no = 1,
.local = 0,
.size = 32768,
.files = { AM53C974_5_11_ROM, "" }
},
{ .files_no = 0 }
},
},
{
.name = "bios",
.description = "Enable BIOS",
.type = CONFIG_BINARY,
.default_string = NULL,
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.selection = { { 0 } },
.bios = { { 0 } }
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
const device_t dc390_pci_device = {
.name = "Tekram DC-390 PCI",
.internal_name = "dc390",
.flags = DEVICE_PCI,
.local = 0,
.init = dc390_init,
.close = esp_close,
.reset = esp_pci_reset,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
.config = bios_enable_config
};
const device_t am53c974_pci_device = {
.name = "AMD 53c974 PCI",
.internal_name = "am53c974",
.flags = DEVICE_PCI,
.local = 1,
.init = dc390_init,
.close = esp_close,
.reset = esp_pci_reset,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
.config = am53c974_bios_enable_config
};
const device_t am53c974a_pci_device = {
.name = "AMD 53c974A PCI",
.internal_name = "am53c974a",
.flags = DEVICE_PCI,
.local = 2,
.init = dc390_init,
.close = esp_close,
.reset = esp_pci_reset,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
.config = am53c974a_bios_enable_config
};
const device_t ncr53c90a_mca_device = {
.name = "NCR 53c90a MCA",
.internal_name = "ncr53c90a",
.flags = DEVICE_MCA,
.local = 0,
.init = ncr53c9x_mca_init,
.close = esp_close,
.reset = NULL,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
.config = NULL
};