/*
 * 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/nmc93cxx.h>
#include <86box/plat.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/scsi_pcscsi.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;
    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];

    uint8_t eeprom_inst;
    uint8_t eeprom_data[128];

    nmc93cxx_eeprom_t *eeprom;
} esp_t;

static esp_t *reset_state = NULL;

#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->wregs[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->wregs[ESP_CFG1] & CFG1_RESREPT));
                    for (uint8_t i = 0; i < 16; i++)
                        scsi_device_reset(&scsi_devices[dev->bus][i]);

                    if (!(dev->wregs[ESP_CFG1] & CFG1_RESREPT)) {
                        dev->rregs[ESP_RINTR] |= INTR_RST;
                        esp_log("ESP Bus Reset with IRQ\n");
                        esp_raise_irq(dev);
                    } else {
                        if (dev->mca) {
                            esp_lower_irq(dev);
                            esp_hard_reset(dev);
                        } else
                            esp_pci_soft_reset(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("PAD=%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

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 (nmc93cxx_eeprom_read(dev->eeprom))
                    return 0x22;
                else {
                    dev->eeprom->dev.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;
                nmc93cxx_eeprom_write(dev->eeprom, 1, eesk, eedi);
            } else if (addr == 0xc0)
                nmc93cxx_eeprom_write(dev->eeprom, 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;

    if (reset_state != NULL) {
        esp_io_remove(dev, dev->PCIBase, 0x80);
        esp_bios_disable(dev);
        timer_stop(&dev->timer);

        reset_state->bios.mapping     = dev->bios.mapping;
        reset_state->timer            = dev->timer;
        reset_state->pci_slot         = dev->pci_slot;

        *dev = *reset_state;

        esp_pci_soft_reset(dev);
        esp_log("PCI Reset.\n");
    } else
        esp_log("NULL.\n");
}

static void *
dc390_init(const device_t *info)
{
    esp_t *dev = calloc(1, sizeof(esp_t));
    reset_state = calloc(1, sizeof(esp_t));
    const char *bios_rev = NULL;
    uint32_t mask = 0;
    uint32_t size = 0x8000;
    nmc93cxx_eeprom_params_t params;
    char        eeprom_filename[1024] = { 0 };
    char        filename[1024] = { 0 };
    uint16_t    checksum = 0x0000;
    uint8_t     i;

    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) {
        dev->eeprom_inst = device_get_instance();

        snprintf(eeprom_filename, sizeof(eeprom_filename), "dc390_%d.nvr", dev->eeprom_inst);

        /* Load the serial EEPROM. */
        for (i = 0; i < 16; i++) {
            dev->eeprom_data[i * 2]     = 0x57;
            dev->eeprom_data[i * 2 + 1] = 0x00;
        }

        esp_log("EEPROM Defaults\n");

        dev->eeprom_data[EE_ADAPT_SCSI_ID] = 7;
        dev->eeprom_data[EE_MODE2]         = 0x0f;
        dev->eeprom_data[EE_TAG_CMD_NUM]   = 0x04;
        dev->eeprom_data[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 += ((dev->eeprom_data[i] & 0xff) | (dev->eeprom_data[i + 1] << 8));
            esp_log("Checksum calc = %04x, nvr = %02x\n", checksum, dev->eeprom_data[i]);
        }

        checksum        = 0x1234 - checksum;
        dev->eeprom_data[EE_CHKSUM1] = checksum & 0xff;
        dev->eeprom_data[EE_CHKSUM2] = checksum >> 8;

        params.nwords          = 64;
        params.default_content = (uint16_t *) dev->eeprom_data;
        params.filename        = filename;
        snprintf(filename, sizeof(filename), "nmc93cxx_eeprom_%s_%d.nvr", info->internal_name, dev->eeprom_inst);
        dev->eeprom = device_add_inst_params(&nmc93cxx_device, dev->eeprom_inst, &params);
        if (dev->eeprom == NULL) {
            free(dev);
            return NULL;
        }
    }

    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);

    *reset_state = *dev;

    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] = 0x4d; /* 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);

        if (reset_state != NULL) {
            free(reset_state);
            reset_state = NULL;
        }

        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
};
