Commit message: esp: don't overflow cmdfifo in get_cmd()

If the guest tries to read a CDB using DMA and cmdfifo is not empty then it is
possible to overflow cmdfifo.

Since this can only occur by issuing deliberately incorrect instruction
sequences, ensure that the maximum length of the CDB transferred to cmdfifo is
limited to the available free space within cmdfifo.

Buglink: https://bugs.launchpad.net/qemu/+bug/1909247
Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Tested-by: Alexander Bulekov <alxndr@bu.edu>
Message-Id: <20210407195801.685-9-mark.cave-ayland@ilande.co.uk>
CVE: CVE-2020-35504
CWE: CWE-476
Vulnerability description: A NULL pointer dereference flaw was found in the SCSI emulation support of QEMU in versions before 6.0.0. This flaw allows a privileged guest user to crash the QEMU process on the host, resulting in a denial of service. The highest threat from this vulnerability is to system availability.

Project: qemu
File: esp.c

static uint8_t esp_fifo_pop(Fifo8 *fifo)
{
    if (fifo8_is_empty(fifo)) {
        return 0;
    }

    return fifo8_pop(fifo);
}

Project: qemu
File: esp.c

static void esp_pdma_write(ESPState *s, uint8_t val)
{
    uint32_t dmalen = esp_get_tc(s);

    if (dmalen == 0) {
        return;
    }

    if (s->do_cmd) {
        esp_fifo_push(&s->cmdfifo, val);
    } else {
        esp_fifo_push(&s->fifo, val);
    }

    dmalen--;
    esp_set_tc(s, dmalen);
}

Project: qemu
File: esp.c

static uint8_t esp_pdma_read(ESPState *s)
{
    uint8_t val;

    if (s->do_cmd) {
        val = esp_fifo_pop(&s->cmdfifo);
    } else {
        val = esp_fifo_pop(&s->fifo);
    }

    return val;
}

Project: qemu
File: esp.c

static void esp_dma_done(ESPState *s)
{
    s->rregs[ESP_RSTAT] |= STAT_TC;
    s->rregs[ESP_RINTR] |= INTR_BS;
    s->rregs[ESP_RSEQ] = 0;
    s->rregs[ESP_RFLAGS] = 0;
    esp_set_tc(s, 0);
    esp_raise_irq(s);
}

Project: qemu
File: esp.c

static void esp_do_nodma(ESPState *s)
{
    int to_device = ((s->rregs[ESP_RSTAT] & 7) == STAT_DO);
    uint32_t cmdlen;
    int len;

    if (s->do_cmd) {
        cmdlen = fifo8_num_used(&s->cmdfifo);
        trace_esp_handle_ti_cmd(cmdlen);
        s->ti_size = 0;
        if ((s->rregs[ESP_RSTAT] & 7) == STAT_CD) {
            /* No command received */
            if (s->cmdfifo_cdb_offset == fifo8_num_used(&s->cmdfifo)) {
                return;
            }

            /* Command has been received */
            s->do_cmd = 0;
            do_cmd(s);
        } else {
            /*
             * Extra message out bytes received: update cmdfifo_cdb_offset
             * and then switch to commmand phase
             */
            s->cmdfifo_cdb_offset = fifo8_num_used(&s->cmdfifo);
            s->rregs[ESP_RSTAT] = STAT_TC | STAT_CD;
            s->rregs[ESP_RSEQ] = SEQ_CD;
            s->rregs[ESP_RINTR] |= INTR_BS;
            esp_raise_irq(s);
        }
        return;
    }

    if (!s->current_req) {
        return;
    }

    if (s->async_len == 0) {
        /* Defer until data is available.  */
        return;
    }

    if (to_device) {
        len = MIN(fifo8_num_used(&s->fifo), ESP_FIFO_SZ);
        esp_fifo_pop_buf(&s->fifo, s->async_buf, len);
        s->async_buf += len;
        s->async_len -= len;
        s->ti_size += len;
    } else {
        len = MIN(s->ti_size, s->async_len);
        len = MIN(len, fifo8_num_free(&s->fifo));
        fifo8_push_all(&s->fifo, s->async_buf, len);
        s->async_buf += len;
        s->async_len -= len;
        s->ti_size -= len;
    }

    if (s->async_len == 0) {
        scsi_req_continue(s->current_req);

        if (to_device || s->ti_size == 0) {
            return;
        }
    }

    s->rregs[ESP_RINTR] |= INTR_BS;
    esp_raise_irq(s);
}

Project: qemu
File: esp.c

static void do_dma_pdma_cb(ESPState *s)
{
    int to_device = ((s->rregs[ESP_RSTAT] & 7) == STAT_DO);
    int len;
    uint32_t n;

    if (s->do_cmd) {
        s->ti_size = 0;
        s->do_cmd = 0;
        do_cmd(s);
        esp_lower_drq(s);
        return;
    }

    if (!s->current_req) {
        return;
    }

    if (to_device) {
        /* Copy FIFO data to device */
        len = MIN(s->async_len, ESP_FIFO_SZ);
        len = MIN(len, fifo8_num_used(&s->fifo));
        n = esp_fifo_pop_buf(&s->fifo, s->async_buf, len);
        s->async_buf += n;
        s->async_len -= n;
        s->ti_size += n;

        if (n < len) {
            /* Unaligned accesses can cause FIFO wraparound */
            len = len - n;
            n = esp_fifo_pop_buf(&s->fifo, s->async_buf, len);
            s->async_buf += n;
            s->async_len -= n;
            s->ti_size += n;
        }

        if (s->async_len == 0) {
            scsi_req_continue(s->current_req);
            return;
        }

        if (esp_get_tc(s) == 0) {
            esp_lower_drq(s);
            esp_dma_done(s);
        }

        return;
    } else {
        if (s->async_len == 0) {
            /* Defer until the scsi layer has completed */
            scsi_req_continue(s->current_req);
            s->data_in_ready = false;
            return;
        }

        if (esp_get_tc(s) != 0) {
            /* Copy device data to FIFO */
            len = MIN(s->async_len, esp_get_tc(s));
            len = MIN(len, fifo8_num_free(&s->fifo));
            fifo8_push_all(&s->fifo, s->async_buf, len);
            s->async_buf += len;
            s->async_len -= len;
            s->ti_size -= len;
            esp_set_tc(s, esp_get_tc(s) - len);

            if (esp_get_tc(s) == 0) {
                /* Indicate transfer to FIFO is complete */
                 s->rregs[ESP_RSTAT] |= STAT_TC;
            }
            return;
        }

        /* Partially filled a scsi buffer. Complete immediately.  */
        esp_lower_drq(s);
        esp_dma_done(s);
    }
}

Project: qemu
File: esp.c

static void sysbus_esp_pdma_write(void *opaque, hwaddr addr,
                                  uint64_t val, unsigned int size)
{
    SysBusESPState *sysbus = opaque;
    ESPState *s = ESP(&sysbus->esp);
    uint32_t dmalen;

    trace_esp_pdma_write(size);

    switch (size) {
    case 1:
        esp_pdma_write(s, val);
        break;
    case 2:
        esp_pdma_write(s, val >> 8);
        esp_pdma_write(s, val);
        break;
    }
    dmalen = esp_get_tc(s);
    if (dmalen == 0 || fifo8_num_free(&s->fifo) < 2) {
        s->pdma_cb(s);
    }
}

Project: qemu
File: esp.c

static void esp_register_types(void)
{
    type_register_static(&sysbus_esp_info);
    type_register_static(&esp_info);
}

Project: qemu
File: esp.c

static void esp_init(Object *obj)
{
    ESPState *s = ESP(obj);

    fifo8_create(&s->fifo, ESP_FIFO_SZ);
    fifo8_create(&s->cmdfifo, ESP_CMDFIFO_SZ);
}

Project: qemu
File: esp.c

uint64_t esp_reg_read(ESPState *s, uint32_t saddr)
{
    uint32_t val;

    switch (saddr) {
    case ESP_FIFO:
        if (s->dma_memory_read && s->dma_memory_write &&
                (s->rregs[ESP_RSTAT] & STAT_PIO_MASK) == 0) {
            /* Data out.  */
            qemu_log_mask(LOG_UNIMP, "esp: PIO data read not implemented\n");
            s->rregs[ESP_FIFO] = 0;
        } else {
            s->rregs[ESP_FIFO] = esp_fifo_pop(&s->fifo);
        }
        val = s->rregs[ESP_FIFO];
        break;
    case ESP_RINTR:
        /*
         * Clear sequence step, interrupt register and all status bits
         * except TC
         */
        val = s->rregs[ESP_RINTR];
        s->rregs[ESP_RINTR] = 0;
        s->rregs[ESP_RSTAT] &= ~STAT_TC;
        s->rregs[ESP_RSEQ] = SEQ_0;
        esp_lower_irq(s);
        break;
    case ESP_TCHI:
        /* Return the unique id if the value has never been written */
        if (!s->tchi_written) {
            val = s->chip_id;
        } else {
            val = s->rregs[saddr];
        }
        break;
     case ESP_RFLAGS:
        /* Bottom 5 bits indicate number of bytes in FIFO */
        val = fifo8_num_used(&s->fifo);
        break;
    default:
        val = s->rregs[saddr];
        break;
    }

    trace_esp_mem_readb(saddr, val);
    return val;
}

Project: qemu
File: esp.c

static bool esp_mem_accepts(void *opaque, hwaddr addr,
                            unsigned size, bool is_write,
                            MemTxAttrs attrs)
{
    return (size == 1) || (is_write && size == 4);
}

Project: qemu
File: esp.c

static void do_cmd(ESPState *s)
{
    uint8_t busid = esp_fifo_pop(&s->cmdfifo);
    int len;

    s->cmdfifo_cdb_offset--;

    /* Ignore extended messages for now */
    if (s->cmdfifo_cdb_offset) {
        len = MIN(s->cmdfifo_cdb_offset, fifo8_num_used(&s->cmdfifo));
        esp_fifo_pop_buf(&s->cmdfifo, NULL, len);
        s->cmdfifo_cdb_offset = 0;
    }

    do_busid_cmd(s, busid);
}

Project: qemu
File: esp.c

static void satn_stop_pdma_cb(ESPState *s)
{
    s->do_cmd = 0;
    if (!fifo8_is_empty(&s->cmdfifo)) {
        trace_esp_handle_satn_stop(fifo8_num_used(&s->cmdfifo));
        s->do_cmd = 1;
        s->cmdfifo_cdb_offset = 1;
        s->rregs[ESP_RSTAT] = STAT_TC | STAT_CD;
        s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
        s->rregs[ESP_RSEQ] = SEQ_CD;
        esp_raise_irq(s);
    }
}

Project: qemu
File: esp.c

int esp_pre_save(void *opaque)
{
    ESPState *s = ESP(object_resolve_path_component(
                      OBJECT(opaque), "esp"));

    s->mig_version_id = vmstate_esp.version_id;
    return 0;
}

Project: qemu
File: esp.c

static void sysbus_esp_realize(DeviceState *dev, Error **errp)
{
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
    SysBusESPState *sysbus = SYSBUS_ESP(dev);
    ESPState *s = ESP(&sysbus->esp);

    if (!qdev_realize(DEVICE(s), NULL, errp)) {
        return;
    }

    sysbus_init_irq(sbd, &s->irq);
    sysbus_init_irq(sbd, &s->irq_data);
    assert(sysbus->it_shift != -1);

    s->chip_id = TCHI_FAS100A;
    memory_region_init_io(&sysbus->iomem, OBJECT(sysbus), &sysbus_esp_mem_ops,
                          sysbus, "esp-regs", ESP_REGS << sysbus->it_shift);
    sysbus_init_mmio(sbd, &sysbus->iomem);
    memory_region_init_io(&sysbus->pdma, OBJECT(sysbus), &sysbus_esp_pdma_ops,
                          sysbus, "esp-pdma", 4);
    sysbus_init_mmio(sbd, &sysbus->pdma);

    qdev_init_gpio_in(dev, sysbus_esp_gpio_demux, 2);

    scsi_bus_new(&s->bus, sizeof(s->bus), dev, &esp_scsi_info, NULL);
}

Project: qemu
File: esp.c

static void handle_satn_stop(ESPState *s)
{
    int32_t cmdlen;

    if (s->dma && !s->dma_enabled) {
        s->dma_cb = handle_satn_stop;
        return;
    }
    s->pdma_cb = satn_stop_pdma_cb;
    cmdlen = get_cmd(s, 1);
    if (cmdlen > 0) {
        trace_esp_handle_satn_stop(fifo8_num_used(&s->cmdfifo));
        s->do_cmd = 1;
        s->cmdfifo_cdb_offset = 1;
        s->rregs[ESP_RSTAT] = STAT_MO;
        s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
        s->rregs[ESP_RSEQ] = SEQ_MO;
        esp_raise_irq(s);
    } else if (cmdlen == 0) {
        s->do_cmd = 1;
        /* Target present, switch to message out phase */
        s->rregs[ESP_RSEQ] = SEQ_MO;
        s->rregs[ESP_RSTAT] = STAT_MO;
    }
}

Project: qemu
File: esp.c

static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
                                     unsigned int size)
{
    SysBusESPState *sysbus = opaque;
    ESPState *s = ESP(&sysbus->esp);
    uint64_t val = 0;

    trace_esp_pdma_read(size);

    switch (size) {
    case 1:
        val = esp_pdma_read(s);
        break;
    case 2:
        val = esp_pdma_read(s);
        val = (val << 8) | esp_pdma_read(s);
        break;
    }
    if (fifo8_num_used(&s->fifo) < 2) {
        s->pdma_cb(s);
    }
    return val;
}

Project: qemu
File: esp.c

static void satn_pdma_cb(ESPState *s)
{
    s->do_cmd = 0;
    if (!fifo8_is_empty(&s->cmdfifo)) {
        s->cmdfifo_cdb_offset = 1;
        do_cmd(s);
    }
}

Project: qemu
File: esp.c

static void write_response_pdma_cb(ESPState *s)
{
    s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
    s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
    s->rregs[ESP_RSEQ] = SEQ_CD;
    esp_raise_irq(s);
}

Project: qemu
File: esp.c

static void sysbus_esp_gpio_demux(void *opaque, int irq, int level)
{
    SysBusESPState *sysbus = SYSBUS_ESP(opaque);
    ESPState *s = ESP(&sysbus->esp);

    switch (irq) {
    case 0:
        parent_esp_reset(s, irq, level);
        break;
    case 1:
        esp_dma_enable(opaque, irq, level);
        break;
    }
}

Project: qemu
File: esp.c

static void esp_set_tc(ESPState *s, uint32_t dmalen)
{
    s->rregs[ESP_TCLO] = dmalen;
    s->rregs[ESP_TCMID] = dmalen >> 8;
    s->rregs[ESP_TCHI] = dmalen >> 16;
}

Project: qemu
File: esp.c

void esp_command_complete(SCSIRequest *req, size_t resid)
{
    ESPState *s = req->hba_private;

    trace_esp_command_complete();
    if (s->ti_size != 0) {
        trace_esp_command_complete_unexpected();
    }
    s->ti_size = 0;
    s->async_len = 0;
    if (req->status) {
        trace_esp_command_complete_fail();
    }
    s->status = req->status;
    s->rregs[ESP_RSTAT] = STAT_ST;
    esp_dma_done(s);
    esp_lower_drq(s);
    if (s->current_req) {
        scsi_req_unref(s->current_req);
        s->current_req = NULL;
        s->current_dev = NULL;
    }
}

Project: qemu
File: esp.c

static void do_busid_cmd(ESPState *s, uint8_t busid)
{
    uint32_t cmdlen;
    int32_t datalen;
    int lun;
    SCSIDevice *current_lun;
    uint8_t buf[ESP_CMDFIFO_SZ];

    trace_esp_do_busid_cmd(busid);
    lun = busid & 7;
    cmdlen = fifo8_num_used(&s->cmdfifo);
    if (!cmdlen || !s->current_dev) {
        return;
    }
    esp_fifo_pop_buf(&s->cmdfifo, buf, cmdlen);

    current_lun = scsi_device_find(&s->bus, 0, s->current_dev->id, lun);
    s->current_req = scsi_req_new(current_lun, 0, lun, buf, s);
    datalen = scsi_req_enqueue(s->current_req);
    s->ti_size = datalen;
    fifo8_reset(&s->cmdfifo);
    if (datalen != 0) {
        s->rregs[ESP_RSTAT] = STAT_TC;
        s->rregs[ESP_RSEQ] = SEQ_CD;
        s->ti_cmd = 0;
        esp_set_tc(s, 0);
        if (datalen > 0) {
            /*
             * Switch to DATA IN phase but wait until initial data xfer is
             * complete before raising the command completion interrupt
             */
            s->data_in_ready = false;
            s->rregs[ESP_RSTAT] |= STAT_DI;
        } else {
            s->rregs[ESP_RSTAT] |= STAT_DO;
            s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
            esp_raise_irq(s);
            esp_lower_drq(s);
        }
        scsi_req_continue(s->current_req);
        return;
    }
}

Project: qemu
File: esp.c

static void s_without_satn_pdma_cb(ESPState *s)
{
    uint32_t len;

    s->do_cmd = 0;
    len = fifo8_num_used(&s->cmdfifo);
    if (len) {
        s->cmdfifo_cdb_offset = 0;
        do_busid_cmd(s, 0);
    }
}

Project: qemu
File: esp.c

static void sysbus_esp_mem_write(void *opaque, hwaddr addr,
                                 uint64_t val, unsigned int size)
{
    SysBusESPState *sysbus = opaque;
    ESPState *s = ESP(&sysbus->esp);
    uint32_t saddr;

    saddr = addr >> sysbus->it_shift;
    esp_reg_write(s, saddr, val);
}

Project: qemu
File: esp.c

static void esp_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    /* internal device for sysbusesp/pciespscsi, not user-creatable */
    dc->user_creatable = false;
    set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
}

Project: qemu
File: esp.c

static int esp_post_load(void *opaque, int version_id)
{
    ESPState *s = ESP(opaque);
    int len, i;

    version_id = MIN(version_id, s->mig_version_id);

    if (version_id < 5) {
        esp_set_tc(s, s->mig_dma_left);

        /* Migrate ti_buf to fifo */
        len = s->mig_ti_wptr - s->mig_ti_rptr;
        for (i = 0; i < len; i++) {
            fifo8_push(&s->fifo, s->mig_ti_buf[i]);
        }

        /* Migrate cmdbuf to cmdfifo */
        for (i = 0; i < s->mig_cmdlen; i++) {
            fifo8_push(&s->cmdfifo, s->mig_cmdbuf[i]);
        }
    }

    s->mig_version_id = vmstate_esp.version_id;
    return 0;
}

Project: qemu
File: esp.c

static void esp_soft_reset(ESPState *s)
{
    qemu_irq_lower(s->irq);
    qemu_irq_lower(s->irq_data);
    esp_hard_reset(s);
}

Project: qemu
File: esp.c

static void esp_raise_drq(ESPState *s)
{
    qemu_irq_raise(s->irq_data);
    trace_esp_raise_drq();
}

Project: qemu
File: esp.c

static void handle_ti(ESPState *s)
{
    uint32_t dmalen;

    if (s->dma && !s->dma_enabled) {
        s->dma_cb = handle_ti;
        return;
    }

    s->ti_cmd = s->rregs[ESP_CMD];
    if (s->dma) {
        dmalen = esp_get_tc(s);
        trace_esp_handle_ti(dmalen);
        s->rregs[ESP_RSTAT] &= ~STAT_TC;
        esp_do_dma(s);
    } else {
        trace_esp_handle_ti(s->ti_size);
        esp_do_nodma(s);
    }
}

Project: qemu
File: esp.c

static bool esp_is_before_version_5(void *opaque, int version_id)
{
    ESPState *s = ESP(opaque);

    version_id = MIN(version_id, s->mig_version_id);
    return version_id < 5;
}

Project: qemu
File: esp.c

static uint64_t sysbus_esp_mem_read(void *opaque, hwaddr addr,
                                    unsigned int size)
{
    SysBusESPState *sysbus = opaque;
    ESPState *s = ESP(&sysbus->esp);
    uint32_t saddr;

    saddr = addr >> sysbus->it_shift;
    return esp_reg_read(s, saddr);
}

Project: qemu
File: esp.c

static uint32_t get_cmd(ESPState *s, uint32_t maxlen)
{
    uint8_t buf[ESP_CMDFIFO_SZ];
    uint32_t dmalen, n;
    int target;

    target = s->wregs[ESP_WBUSID] & BUSID_DID;
    if (s->dma) {
        dmalen = MIN(esp_get_tc(s), maxlen);
        if (dmalen == 0) {
            return 0;
        }
        if (s->dma_memory_read) {
            s->dma_memory_read(s->dma_opaque, buf, dmalen);
            dmalen = MIN(fifo8_num_free(&s->cmdfifo), dmalen);
            fifo8_push_all(&s->cmdfifo, buf, dmalen);
        } else {
            if (esp_select(s) < 0) {
                fifo8_reset(&s->cmdfifo);
                return -1;
            }
            esp_raise_drq(s);
            fifo8_reset(&s->cmdfifo);
            return 0;
        }
    } else {
        dmalen = MIN(fifo8_num_used(&s->fifo), maxlen);
        if (dmalen == 0) {
            return 0;
        }
        n = esp_fifo_pop_buf(&s->fifo, buf, dmalen);
        if (n >= 3) {
            buf[0] = buf[2] >> 5;
        }
        n = MIN(fifo8_num_free(&s->cmdfifo), n);
        fifo8_push_all(&s->cmdfifo, buf, n);
    }
    trace_esp_get_cmd(dmalen, target);

    if (esp_select(s) < 0) {
        fifo8_reset(&s->cmdfifo);
        return -1;
    }
    return dmalen;
}

Project: qemu
File: esp.c

static void sysbus_esp_hard_reset(DeviceState *dev)
{
    SysBusESPState *sysbus = SYSBUS_ESP(dev);
    ESPState *s = ESP(&sysbus->esp);

    esp_hard_reset(s);
}

Project: qemu
File: esp.c

static void esp_finalize(Object *obj)
{
    ESPState *s = ESP(obj);

    fifo8_destroy(&s->fifo);
    fifo8_destroy(&s->cmdfifo);
}

Project: qemu
File: esp.c

static uint32_t esp_get_tc(ESPState *s)
{
    uint32_t dmalen;

    dmalen = s->rregs[ESP_TCLO];
    dmalen |= s->rregs[ESP_TCMID] << 8;
    dmalen |= s->rregs[ESP_TCHI] << 16;

    return dmalen;
}

Project: qemu
File: esp.c

static void parent_esp_reset(ESPState *s, int irq, int level)
{
    if (level) {
        esp_soft_reset(s);
    }
}

Project: qemu
File: esp.c

static uint32_t esp_get_stc(ESPState *s)
{
    uint32_t dmalen;

    dmalen = s->wregs[ESP_TCLO];
    dmalen |= s->wregs[ESP_TCMID] << 8;
    dmalen |= s->wregs[ESP_TCHI] << 16;

    return dmalen;
}

Project: qemu
File: esp.c

static void sysbus_esp_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = sysbus_esp_realize;
    dc->reset = sysbus_esp_hard_reset;
    dc->vmsd = &vmstate_sysbus_esp_scsi;
    set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
}

Project: qemu
File: esp.c

static void esp_fifo_push(Fifo8 *fifo, uint8_t val)
{
    if (fifo8_num_used(fifo) == fifo->capacity) {
        trace_esp_error_fifo_overrun();
        return;
    }

    fifo8_push(fifo, val);
}

Project: qemu
File: esp.c

void esp_reg_write(ESPState *s, uint32_t saddr, uint64_t val)
{
    trace_esp_mem_writeb(saddr, s->wregs[saddr], val);
    switch (saddr) {
    case ESP_TCHI:
        s->tchi_written = true;
        /* fall through */
    case ESP_TCLO:
    case ESP_TCMID:
        s->rregs[ESP_RSTAT] &= ~STAT_TC;
        break;
    case ESP_FIFO:
        if (s->do_cmd) {
            esp_fifo_push(&s->cmdfifo, val);
        } else {
            esp_fifo_push(&s->fifo, val);
        }

        /* Non-DMA transfers raise an interrupt after every byte */
        if (s->rregs[ESP_CMD] == CMD_TI) {
            s->rregs[ESP_RINTR] |= INTR_FC | INTR_BS;
            esp_raise_irq(s);
        }
        break;
    case ESP_CMD:
        s->rregs[saddr] = val;
        if (val & CMD_DMA) {
            s->dma = 1;
            /* Reload DMA counter.  */
            if (esp_get_stc(s) == 0) {
                esp_set_tc(s, 0x10000);
            } else {
                esp_set_tc(s, esp_get_stc(s));
            }
        } else {
            s->dma = 0;
        }
        switch (val & CMD_CMD) {
        case CMD_NOP:
            trace_esp_mem_writeb_cmd_nop(val);
            break;
        case CMD_FLUSH:
            trace_esp_mem_writeb_cmd_flush(val);
            fifo8_reset(&s->fifo);
            break;
        case CMD_RESET:
            trace_esp_mem_writeb_cmd_reset(val);
            esp_soft_reset(s);
            break;
        case CMD_BUSRESET:
            trace_esp_mem_writeb_cmd_bus_reset(val);
            if (!(s->wregs[ESP_CFG1] & CFG1_RESREPT)) {
                s->rregs[ESP_RINTR] |= INTR_RST;
                esp_raise_irq(s);
            }
            break;
        case CMD_TI:
            trace_esp_mem_writeb_cmd_ti(val);
            handle_ti(s);
            break;
        case CMD_ICCS:
            trace_esp_mem_writeb_cmd_iccs(val);
            write_response(s);
            s->rregs[ESP_RINTR] |= INTR_FC;
            s->rregs[ESP_RSTAT] |= STAT_MI;
            break;
        case CMD_MSGACC:
            trace_esp_mem_writeb_cmd_msgacc(val);
            s->rregs[ESP_RINTR] |= INTR_DC;
            s->rregs[ESP_RSEQ] = 0;
            s->rregs[ESP_RFLAGS] = 0;
            esp_raise_irq(s);
            break;
        case CMD_PAD:
            trace_esp_mem_writeb_cmd_pad(val);
            s->rregs[ESP_RSTAT] = STAT_TC;
            s->rregs[ESP_RINTR] |= INTR_FC;
            s->rregs[ESP_RSEQ] = 0;
            break;
        case CMD_SATN:
            trace_esp_mem_writeb_cmd_satn(val);
            break;
        case CMD_RSTATN:
            trace_esp_mem_writeb_cmd_rstatn(val);
            break;
        case CMD_SEL:
            trace_esp_mem_writeb_cmd_sel(val);
            handle_s_without_atn(s);
            break;
        case CMD_SELATN:
            trace_esp_mem_writeb_cmd_selatn(val);
            handle_satn(s);
            break;
        case CMD_SELATNS:
            trace_esp_mem_writeb_cmd_selatns(val);
            handle_satn_stop(s);
            break;
        case CMD_ENSEL:
            trace_esp_mem_writeb_cmd_ensel(val);
            s->rregs[ESP_RINTR] = 0;
            break;
        case CMD_DISSEL:
            trace_esp_mem_writeb_cmd_dissel(val);
            s->rregs[ESP_RINTR] = 0;
            esp_raise_irq(s);
            break;
        default:
            trace_esp_error_unhandled_command(val);
            break;
        }
        break;
    case ESP_WBUSID ... ESP_WSYNO:
        break;
    case ESP_CFG1:
    case ESP_CFG2: case ESP_CFG3:
    case ESP_RES3: case ESP_RES4:
        s->rregs[saddr] = val;
        break;
    case ESP_WCCF ... ESP_WTEST:
        break;
    default:
        trace_esp_error_invalid_write(val, saddr);
        return;
    }
    s->wregs[saddr] = val;
}

Project: qemu
File: esp.c

static void esp_lower_drq(ESPState *s)
{
    qemu_irq_lower(s->irq_data);
    trace_esp_lower_drq();
}

Project: qemu
File: esp.c

void esp_transfer_data(SCSIRequest *req, uint32_t len)
{
    ESPState *s = req->hba_private;
    int to_device = ((s->rregs[ESP_RSTAT] & 7) == STAT_DO);
    uint32_t dmalen = esp_get_tc(s);

    assert(!s->do_cmd);
    trace_esp_transfer_data(dmalen, s->ti_size);
    s->async_len = len;
    s->async_buf = scsi_req_get_buf(req);

    if (!to_device && !s->data_in_ready) {
        /*
         * Initial incoming data xfer is complete so raise command
         * completion interrupt
         */
        s->data_in_ready = true;
        s->rregs[ESP_RSTAT] |= STAT_TC;
        s->rregs[ESP_RINTR] |= INTR_BS;
        esp_raise_irq(s);

        /*
         * If data is ready to transfer and the TI command has already
         * been executed, start DMA immediately. Otherwise DMA will start
         * when host sends the TI command
         */
        if (s->ti_size && (s->rregs[ESP_CMD] == (CMD_TI | CMD_DMA))) {
            esp_do_dma(s);
        }
        return;
    }

    if (s->ti_cmd == 0) {
        /*
         * 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.
         */
        return;
    }

    if (s->ti_cmd & CMD_DMA) {
        if (dmalen) {
            esp_do_dma(s);
        } else if (s->ti_size <= 0) {
            /*
             * If this was the last part of a DMA transfer then the
             * completion interrupt is deferred to here.
             */
            esp_dma_done(s);
            esp_lower_drq(s);
        }
    } else {
        esp_do_nodma(s);
    }
}

Project: qemu
File: esp.c

static void sysbus_esp_init(Object *obj)
{
    SysBusESPState *sysbus = SYSBUS_ESP(obj);

    object_initialize_child(obj, "esp", &sysbus->esp, TYPE_ESP);
}

Project: qemu
File: esp.c

static bool esp_is_version_5(void *opaque, int version_id)
{
    ESPState *s = ESP(opaque);

    version_id = MIN(version_id, s->mig_version_id);
    return version_id == 5;
}

Project: qemu
File: esp.c

static void write_response(ESPState *s)
{
    uint8_t buf[2];

    trace_esp_write_response(s->status);

    buf[0] = s->status;
    buf[1] = 0;

    if (s->dma) {
        if (s->dma_memory_write) {
            s->dma_memory_write(s->dma_opaque, buf, 2);
            s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
            s->rregs[ESP_RINTR] |= INTR_BS | INTR_FC;
            s->rregs[ESP_RSEQ] = SEQ_CD;
        } else {
            s->pdma_cb = write_response_pdma_cb;
            esp_raise_drq(s);
            return;
        }
    } else {
        fifo8_reset(&s->fifo);
        fifo8_push_all(&s->fifo, buf, 2);
        s->rregs[ESP_RFLAGS] = 2;
    }
    esp_raise_irq(s);
}

Project: qemu
File: esp.c

static uint32_t esp_fifo_pop_buf(Fifo8 *fifo, uint8_t *dest, int maxlen)
{
    const uint8_t *buf;
    uint32_t n;

    if (maxlen == 0) {
        return 0;
    }

    buf = fifo8_pop_buf(fifo, maxlen, &n);
    if (dest) {
        memcpy(dest, buf, n);
    }

    return n;
}

Project: qemu
File: esp.c

void esp_hard_reset(ESPState *s)
{
    memset(s->rregs, 0, ESP_REGS);
    memset(s->wregs, 0, ESP_REGS);
    s->tchi_written = 0;
    s->ti_size = 0;
    fifo8_reset(&s->fifo);
    fifo8_reset(&s->cmdfifo);
    s->dma = 0;
    s->do_cmd = 0;
    s->dma_cb = NULL;

    s->rregs[ESP_CFG1] = 7;
}