summaryrefslogtreecommitdiffstats
path: root/hw/scsi
diff options
context:
space:
mode:
Diffstat (limited to 'hw/scsi')
-rw-r--r--hw/scsi/Kconfig60
-rw-r--r--hw/scsi/emulation.c42
-rw-r--r--hw/scsi/esp-pci.c564
-rw-r--r--hw/scsi/esp.c1515
-rw-r--r--hw/scsi/lsi53c895a.c2376
-rw-r--r--hw/scsi/megasas.c2589
-rw-r--r--hw/scsi/meson.build26
-rw-r--r--hw/scsi/mfi.h1272
-rw-r--r--hw/scsi/mpi.h1153
-rw-r--r--hw/scsi/mptconfig.c904
-rw-r--r--hw/scsi/mptendian.c205
-rw-r--r--hw/scsi/mptsas.c1455
-rw-r--r--hw/scsi/mptsas.h105
-rw-r--r--hw/scsi/scsi-bus.c1887
-rw-r--r--hw/scsi/scsi-disk.c3260
-rw-r--r--hw/scsi/scsi-generic.c829
-rw-r--r--hw/scsi/spapr_vscsi.c1301
-rw-r--r--hw/scsi/srp.h247
-rw-r--r--hw/scsi/trace-events357
-rw-r--r--hw/scsi/trace.h1
-rw-r--r--hw/scsi/vhost-scsi-common.c171
-rw-r--r--hw/scsi/vhost-scsi.c349
-rw-r--r--hw/scsi/vhost-user-scsi.c240
-rw-r--r--hw/scsi/viosrp.h217
-rw-r--r--hw/scsi/virtio-scsi-dataplane.c230
-rw-r--r--hw/scsi/virtio-scsi.c1181
-rw-r--r--hw/scsi/vmw_pvscsi.c1363
-rw-r--r--hw/scsi/vmw_pvscsi.h434
28 files changed, 24333 insertions, 0 deletions
diff --git a/hw/scsi/Kconfig b/hw/scsi/Kconfig
new file mode 100644
index 00000000..e7b34dc8
--- /dev/null
+++ b/hw/scsi/Kconfig
@@ -0,0 +1,60 @@
+config SCSI
+ bool
+
+config LSI_SCSI_PCI
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select SCSI
+
+config MPTSAS_SCSI_PCI
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select SCSI
+
+config MEGASAS_SCSI_PCI
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select SCSI
+
+config VMW_PVSCSI_SCSI_PCI
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select SCSI
+
+config ESP
+ bool
+ select SCSI
+
+config ESP_PCI
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select ESP
+ select NMC93XX_EEPROM
+
+config SPAPR_VSCSI
+ bool
+ default y
+ depends on PSERIES
+ select SCSI
+
+config VIRTIO_SCSI
+ bool
+ default y
+ depends on VIRTIO
+ select SCSI
+
+config VHOST_SCSI
+ bool
+ default y
+ depends on VIRTIO && VHOST_KERNEL
+
+config VHOST_USER_SCSI
+ bool
+ # Only PCI devices are provided for now
+ default y if VIRTIO_PCI
+ depends on VIRTIO && VHOST_USER && LINUX
diff --git a/hw/scsi/emulation.c b/hw/scsi/emulation.c
new file mode 100644
index 00000000..06d62f3c
--- /dev/null
+++ b/hw/scsi/emulation.c
@@ -0,0 +1,42 @@
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/bswap.h"
+#include "hw/scsi/emulation.h"
+
+int scsi_emulate_block_limits(uint8_t *outbuf, const SCSIBlockLimits *bl)
+{
+ /* required VPD size with unmap support */
+ memset(outbuf, 0, 0x3c);
+
+ outbuf[0] = bl->wsnz; /* wsnz */
+
+ if (bl->max_io_sectors) {
+ /* optimal transfer length granularity. This field and the optimal
+ * transfer length can't be greater than maximum transfer length.
+ */
+ stw_be_p(outbuf + 2, MIN(bl->min_io_size, bl->max_io_sectors));
+
+ /* maximum transfer length */
+ stl_be_p(outbuf + 4, bl->max_io_sectors);
+
+ /* optimal transfer length */
+ stl_be_p(outbuf + 8, MIN(bl->opt_io_size, bl->max_io_sectors));
+ } else {
+ stw_be_p(outbuf + 2, bl->min_io_size);
+ stl_be_p(outbuf + 8, bl->opt_io_size);
+ }
+
+ /* max unmap LBA count */
+ stl_be_p(outbuf + 16, bl->max_unmap_sectors);
+
+ /* max unmap descriptors */
+ stl_be_p(outbuf + 20, bl->max_unmap_descr);
+
+ /* optimal unmap granularity; alignment is zero */
+ stl_be_p(outbuf + 24, bl->unmap_sectors);
+
+ /* max write same size, make it the same as maximum transfer length */
+ stl_be_p(outbuf + 36, bl->max_io_sectors);
+
+ return 0x3c;
+}
diff --git a/hw/scsi/esp-pci.c b/hw/scsi/esp-pci.c
new file mode 100644
index 00000000..1792f84c
--- /dev/null
+++ b/hw/scsi/esp-pci.c
@@ -0,0 +1,564 @@
+/*
+ * QEMU ESP/NCR53C9x emulation
+ *
+ * Copyright (c) 2005-2006 Fabrice Bellard
+ * Copyright (c) 2012 Herve Poussineau
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/irq.h"
+#include "hw/nvram/eeprom93xx.h"
+#include "hw/scsi/esp.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define TYPE_AM53C974_DEVICE "am53c974"
+
+typedef struct PCIESPState PCIESPState;
+DECLARE_INSTANCE_CHECKER(PCIESPState, PCI_ESP,
+ TYPE_AM53C974_DEVICE)
+
+#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)
+
+struct PCIESPState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion io;
+ uint32_t dma_regs[8];
+ uint32_t sbac;
+ ESPState esp;
+};
+
+static void esp_pci_handle_idle(PCIESPState *pci, uint32_t val)
+{
+ ESPState *s = ESP(&pci->esp);
+
+ trace_esp_pci_dma_idle(val);
+ esp_dma_enable(s, 0, 0);
+}
+
+static void esp_pci_handle_blast(PCIESPState *pci, uint32_t val)
+{
+ trace_esp_pci_dma_blast(val);
+ qemu_log_mask(LOG_UNIMP, "am53c974: cmd BLAST not implemented\n");
+}
+
+static void esp_pci_handle_abort(PCIESPState *pci, uint32_t val)
+{
+ ESPState *s = ESP(&pci->esp);
+
+ trace_esp_pci_dma_abort(val);
+ if (s->current_req) {
+ scsi_req_cancel(s->current_req);
+ }
+}
+
+static void esp_pci_handle_start(PCIESPState *pci, uint32_t val)
+{
+ ESPState *s = ESP(&pci->esp);
+
+ trace_esp_pci_dma_start(val);
+
+ pci->dma_regs[DMA_WBC] = pci->dma_regs[DMA_STC];
+ pci->dma_regs[DMA_WAC] = pci->dma_regs[DMA_SPA];
+ pci->dma_regs[DMA_WMAC] = pci->dma_regs[DMA_SMDLA];
+
+ pci->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(s, 0, 1);
+}
+
+static void esp_pci_dma_write(PCIESPState *pci, uint32_t saddr, uint32_t val)
+{
+ trace_esp_pci_dma_write(saddr, pci->dma_regs[saddr], val);
+ switch (saddr) {
+ case DMA_CMD:
+ pci->dma_regs[saddr] = val;
+ switch (val & DMA_CMD_MASK) {
+ case 0x0: /* IDLE */
+ esp_pci_handle_idle(pci, val);
+ break;
+ case 0x1: /* BLAST */
+ esp_pci_handle_blast(pci, val);
+ break;
+ case 0x2: /* ABORT */
+ esp_pci_handle_abort(pci, val);
+ break;
+ case 0x3: /* START */
+ esp_pci_handle_start(pci, val);
+ break;
+ default: /* can't happen */
+ abort();
+ }
+ break;
+ case DMA_STC:
+ case DMA_SPA:
+ case DMA_SMDLA:
+ pci->dma_regs[saddr] = val;
+ break;
+ case DMA_STAT:
+ if (pci->sbac & SBAC_STATUS) {
+ /* clear some bits on write */
+ uint32_t mask = DMA_STAT_ERROR | DMA_STAT_ABORT | DMA_STAT_DONE;
+ pci->dma_regs[DMA_STAT] &= ~(val & mask);
+ }
+ break;
+ default:
+ trace_esp_pci_error_invalid_write_dma(val, saddr);
+ return;
+ }
+}
+
+static uint32_t esp_pci_dma_read(PCIESPState *pci, uint32_t saddr)
+{
+ ESPState *s = ESP(&pci->esp);
+ uint32_t val;
+
+ val = pci->dma_regs[saddr];
+ if (saddr == DMA_STAT) {
+ if (s->rregs[ESP_RSTAT] & STAT_INT) {
+ val |= DMA_STAT_SCSIINT;
+ }
+ if (!(pci->sbac & SBAC_STATUS)) {
+ pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_ERROR | DMA_STAT_ABORT |
+ DMA_STAT_DONE);
+ }
+ }
+
+ trace_esp_pci_dma_read(saddr, val);
+ return val;
+}
+
+static void esp_pci_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ PCIESPState *pci = opaque;
+ ESPState *s = ESP(&pci->esp);
+
+ if (size < 4 || addr & 3) {
+ /* need to upgrade request: we only support 4-bytes accesses */
+ uint32_t current = 0, mask;
+ int shift;
+
+ if (addr < 0x40) {
+ current = s->wregs[addr >> 2];
+ } else if (addr < 0x60) {
+ current = pci->dma_regs[(addr - 0x40) >> 2];
+ } else if (addr < 0x74) {
+ current = pci->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;
+ }
+ g_assert(size >= 4);
+
+ if (addr < 0x40) {
+ /* SCSI core reg */
+ esp_reg_write(s, addr >> 2, val);
+ } else if (addr < 0x60) {
+ /* PCI DMA CCB */
+ esp_pci_dma_write(pci, (addr - 0x40) >> 2, val);
+ } else if (addr == 0x70) {
+ /* DMA SCSI Bus and control */
+ trace_esp_pci_sbac_write(pci->sbac, val);
+ pci->sbac = val;
+ } else {
+ trace_esp_pci_error_invalid_write((int)addr);
+ }
+}
+
+static uint64_t esp_pci_io_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ PCIESPState *pci = opaque;
+ ESPState *s = ESP(&pci->esp);
+ uint32_t ret;
+
+ if (addr < 0x40) {
+ /* SCSI core reg */
+ ret = esp_reg_read(s, addr >> 2);
+ } else if (addr < 0x60) {
+ /* PCI DMA CCB */
+ ret = esp_pci_dma_read(pci, (addr - 0x40) >> 2);
+ } else if (addr == 0x70) {
+ /* DMA SCSI Bus and control */
+ trace_esp_pci_sbac_read(pci->sbac);
+ ret = pci->sbac;
+ } else {
+ /* Invalid region */
+ trace_esp_pci_error_invalid_read((int)addr);
+ ret = 0;
+ }
+
+ /* give only requested data */
+ ret >>= (addr & 3) * 8;
+ ret &= ~(~(uint64_t)0 << (8 * size));
+
+ return ret;
+}
+
+static void esp_pci_dma_memory_rw(PCIESPState *pci, uint8_t *buf, int len,
+ DMADirection dir)
+{
+ dma_addr_t addr;
+ DMADirection expected_dir;
+
+ if (pci->dma_regs[DMA_CMD] & DMA_CMD_DIR) {
+ expected_dir = DMA_DIRECTION_FROM_DEVICE;
+ } else {
+ expected_dir = DMA_DIRECTION_TO_DEVICE;
+ }
+
+ if (dir != expected_dir) {
+ trace_esp_pci_error_invalid_dma_direction();
+ return;
+ }
+
+ if (pci->dma_regs[DMA_STAT] & DMA_CMD_MDL) {
+ qemu_log_mask(LOG_UNIMP, "am53c974: MDL transfer not implemented\n");
+ }
+
+ addr = pci->dma_regs[DMA_SPA];
+ if (pci->dma_regs[DMA_WBC] < len) {
+ len = pci->dma_regs[DMA_WBC];
+ }
+
+ pci_dma_rw(PCI_DEVICE(pci), addr, buf, len, dir, MEMTXATTRS_UNSPECIFIED);
+
+ /* update status registers */
+ pci->dma_regs[DMA_WBC] -= len;
+ pci->dma_regs[DMA_WAC] += len;
+ if (pci->dma_regs[DMA_WBC] == 0) {
+ pci->dma_regs[DMA_STAT] |= DMA_STAT_DONE;
+ }
+}
+
+static void esp_pci_dma_memory_read(void *opaque, uint8_t *buf, int len)
+{
+ PCIESPState *pci = opaque;
+ esp_pci_dma_memory_rw(pci, buf, len, DMA_DIRECTION_TO_DEVICE);
+}
+
+static void esp_pci_dma_memory_write(void *opaque, uint8_t *buf, int len)
+{
+ PCIESPState *pci = opaque;
+ esp_pci_dma_memory_rw(pci, buf, len, DMA_DIRECTION_FROM_DEVICE);
+}
+
+static const MemoryRegionOps esp_pci_io_ops = {
+ .read = esp_pci_io_read,
+ .write = esp_pci_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void esp_pci_hard_reset(DeviceState *dev)
+{
+ PCIESPState *pci = PCI_ESP(dev);
+ ESPState *s = ESP(&pci->esp);
+
+ esp_hard_reset(s);
+ pci->dma_regs[DMA_CMD] &= ~(DMA_CMD_DIR | DMA_CMD_INTE_D | DMA_CMD_INTE_P
+ | DMA_CMD_MDL | DMA_CMD_DIAG | DMA_CMD_MASK);
+ pci->dma_regs[DMA_WBC] &= ~0xffff;
+ pci->dma_regs[DMA_WAC] = 0xffffffff;
+ pci->dma_regs[DMA_STAT] &= ~(DMA_STAT_BCMBLT | DMA_STAT_SCSIINT
+ | DMA_STAT_DONE | DMA_STAT_ABORT
+ | DMA_STAT_ERROR);
+ pci->dma_regs[DMA_WMAC] = 0xfffffffd;
+}
+
+static const VMStateDescription vmstate_esp_pci_scsi = {
+ .name = "pciespscsi",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .pre_save = esp_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PCIESPState),
+ VMSTATE_BUFFER_UNSAFE(dma_regs, PCIESPState, 0, 8 * sizeof(uint32_t)),
+ VMSTATE_UINT8_V(esp.mig_version_id, PCIESPState, 2),
+ VMSTATE_STRUCT(esp, PCIESPState, 0, vmstate_esp, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void esp_pci_command_complete(SCSIRequest *req, size_t resid)
+{
+ ESPState *s = req->hba_private;
+ PCIESPState *pci = container_of(s, PCIESPState, esp);
+
+ esp_command_complete(req, resid);
+ pci->dma_regs[DMA_WBC] = 0;
+ pci->dma_regs[DMA_STAT] |= DMA_STAT_DONE;
+}
+
+static const struct SCSIBusInfo esp_pci_scsi_info = {
+ .tcq = false,
+ .max_target = ESP_MAX_DEVS,
+ .max_lun = 7,
+
+ .transfer_data = esp_transfer_data,
+ .complete = esp_pci_command_complete,
+ .cancel = esp_request_cancelled,
+};
+
+static void esp_pci_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ PCIESPState *pci = PCI_ESP(dev);
+ DeviceState *d = DEVICE(dev);
+ ESPState *s = ESP(&pci->esp);
+ uint8_t *pci_conf;
+
+ if (!qdev_realize(DEVICE(s), NULL, errp)) {
+ return;
+ }
+
+ pci_conf = dev->config;
+
+ /* Interrupt pin A */
+ pci_conf[PCI_INTERRUPT_PIN] = 0x01;
+
+ s->dma_memory_read = esp_pci_dma_memory_read;
+ s->dma_memory_write = esp_pci_dma_memory_write;
+ s->dma_opaque = pci;
+ s->chip_id = TCHI_AM53C974;
+ memory_region_init_io(&pci->io, OBJECT(pci), &esp_pci_io_ops, pci,
+ "esp-io", 0x80);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->io);
+ s->irq = pci_allocate_irq(dev);
+
+ scsi_bus_init(&s->bus, sizeof(s->bus), d, &esp_pci_scsi_info);
+}
+
+static void esp_pci_scsi_exit(PCIDevice *d)
+{
+ PCIESPState *pci = PCI_ESP(d);
+ ESPState *s = ESP(&pci->esp);
+
+ qemu_free_irq(s->irq);
+}
+
+static void esp_pci_init(Object *obj)
+{
+ PCIESPState *pci = PCI_ESP(obj);
+
+ object_initialize_child(obj, "esp", &pci->esp, TYPE_ESP);
+}
+
+static void esp_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = esp_pci_scsi_realize;
+ k->exit = esp_pci_scsi_exit;
+ k->vendor_id = PCI_VENDOR_ID_AMD;
+ k->device_id = PCI_DEVICE_ID_AMD_SCSI;
+ k->revision = 0x10;
+ k->class_id = PCI_CLASS_STORAGE_SCSI;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->desc = "AMD Am53c974 PCscsi-PCI SCSI adapter";
+ dc->reset = esp_pci_hard_reset;
+ dc->vmsd = &vmstate_esp_pci_scsi;
+}
+
+static const TypeInfo esp_pci_info = {
+ .name = TYPE_AM53C974_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_init = esp_pci_init,
+ .instance_size = sizeof(PCIESPState),
+ .class_init = esp_pci_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+struct DC390State {
+ PCIESPState pci;
+ eeprom_t *eeprom;
+};
+typedef struct DC390State DC390State;
+
+#define TYPE_DC390_DEVICE "dc390"
+DECLARE_INSTANCE_CHECKER(DC390State, DC390,
+ TYPE_DC390_DEVICE)
+
+#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 uint32_t dc390_read_config(PCIDevice *dev, uint32_t addr, int l)
+{
+ DC390State *pci = DC390(dev);
+ uint32_t val;
+
+ val = pci_default_read_config(dev, addr, l);
+
+ if (addr == 0x00 && l == 1) {
+ /* First byte of address space is AND-ed with EEPROM DO line */
+ if (!eeprom93xx_read(pci->eeprom)) {
+ val &= ~0xff;
+ }
+ }
+
+ return val;
+}
+
+static void dc390_write_config(PCIDevice *dev,
+ uint32_t addr, uint32_t val, int l)
+{
+ DC390State *pci = DC390(dev);
+ if (addr == 0x80) {
+ /* EEPROM write */
+ int eesk = val & 0x80 ? 1 : 0;
+ int eedi = val & 0x40 ? 1 : 0;
+ eeprom93xx_write(pci->eeprom, 1, eesk, eedi);
+ } else if (addr == 0xc0) {
+ /* EEPROM CS low */
+ eeprom93xx_write(pci->eeprom, 0, 0, 0);
+ } else {
+ pci_default_write_config(dev, addr, val, l);
+ }
+}
+
+static void dc390_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ DC390State *pci = DC390(dev);
+ Error *err = NULL;
+ uint8_t *contents;
+ uint16_t chksum = 0;
+ int i;
+
+ /* init base class */
+ esp_pci_scsi_realize(dev, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* EEPROM */
+ pci->eeprom = eeprom93xx_new(DEVICE(dev), 64);
+
+ /* set default eeprom values */
+ contents = (uint8_t *)eeprom93xx_data(pci->eeprom);
+
+ for (i = 0; i < 16; i++) {
+ contents[i * 2] = 0x57;
+ contents[i * 2 + 1] = 0x00;
+ }
+ contents[EE_ADAPT_SCSI_ID] = 7;
+ contents[EE_MODE2] = 0x0f;
+ contents[EE_TAG_CMD_NUM] = 0x04;
+ contents[EE_ADAPT_OPTIONS] = EE_ADAPT_OPTION_F6_F8_AT_BOOT
+ | EE_ADAPT_OPTION_BOOT_FROM_CDROM
+ | EE_ADAPT_OPTION_INT13;
+
+ /* update eeprom checksum */
+ for (i = 0; i < EE_CHKSUM1; i += 2) {
+ chksum += contents[i] + (((uint16_t)contents[i + 1]) << 8);
+ }
+ chksum = 0x1234 - chksum;
+ contents[EE_CHKSUM1] = chksum & 0xff;
+ contents[EE_CHKSUM2] = chksum >> 8;
+}
+
+static void dc390_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = dc390_scsi_realize;
+ k->config_read = dc390_read_config;
+ k->config_write = dc390_write_config;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->desc = "Tekram DC-390 SCSI adapter";
+}
+
+static const TypeInfo dc390_info = {
+ .name = TYPE_DC390_DEVICE,
+ .parent = TYPE_AM53C974_DEVICE,
+ .instance_size = sizeof(DC390State),
+ .class_init = dc390_class_init,
+};
+
+static void esp_pci_register_types(void)
+{
+ type_register_static(&esp_pci_info);
+ type_register_static(&dc390_info);
+}
+
+type_init(esp_pci_register_types)
diff --git a/hw/scsi/esp.c b/hw/scsi/esp.c
new file mode 100644
index 00000000..e52188d0
--- /dev/null
+++ b/hw/scsi/esp.c
@@ -0,0 +1,1515 @@
+/*
+ * QEMU ESP/NCR53C9x emulation
+ *
+ * Copyright (c) 2005-2006 Fabrice Bellard
+ * Copyright (c) 2012 Herve Poussineau
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/scsi/esp.h"
+#include "trace.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+/*
+ * On Sparc32, this is the ESP (NCR53C90) part of chip STP2000 (Master I/O),
+ * also produced as NCR89C100. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C100.txt
+ * and
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR53C9X.txt
+ *
+ * On Macintosh Quadra it is a NCR53C96.
+ */
+
+static void esp_raise_irq(ESPState *s)
+{
+ if (!(s->rregs[ESP_RSTAT] & STAT_INT)) {
+ s->rregs[ESP_RSTAT] |= STAT_INT;
+ qemu_irq_raise(s->irq);
+ trace_esp_raise_irq();
+ }
+}
+
+static void esp_lower_irq(ESPState *s)
+{
+ if (s->rregs[ESP_RSTAT] & STAT_INT) {
+ s->rregs[ESP_RSTAT] &= ~STAT_INT;
+ qemu_irq_lower(s->irq);
+ trace_esp_lower_irq();
+ }
+}
+
+static void esp_raise_drq(ESPState *s)
+{
+ qemu_irq_raise(s->irq_data);
+ trace_esp_raise_drq();
+}
+
+static void esp_lower_drq(ESPState *s)
+{
+ qemu_irq_lower(s->irq_data);
+ trace_esp_lower_drq();
+}
+
+void esp_dma_enable(ESPState *s, int irq, int level)
+{
+ if (level) {
+ s->dma_enabled = 1;
+ trace_esp_dma_enable();
+ if (s->dma_cb) {
+ s->dma_cb(s);
+ s->dma_cb = NULL;
+ }
+ } else {
+ trace_esp_dma_disable();
+ s->dma_enabled = 0;
+ }
+}
+
+void esp_request_cancelled(SCSIRequest *req)
+{
+ ESPState *s = req->hba_private;
+
+ if (req == s->current_req) {
+ scsi_req_unref(s->current_req);
+ s->current_req = NULL;
+ s->current_dev = NULL;
+ s->async_len = 0;
+ }
+}
+
+static void esp_fifo_push(Fifo8 *fifo, uint8_t val)
+{
+ if (fifo8_num_used(fifo) == fifo->capacity) {
+ trace_esp_error_fifo_overrun();
+ return;
+ }
+
+ fifo8_push(fifo, val);
+}
+
+static uint8_t esp_fifo_pop(Fifo8 *fifo)
+{
+ if (fifo8_is_empty(fifo)) {
+ return 0;
+ }
+
+ return fifo8_pop(fifo);
+}
+
+static uint32_t esp_fifo_pop_buf(Fifo8 *fifo, uint8_t *dest, int maxlen)
+{
+ const uint8_t *buf;
+ uint32_t n;
+
+ if (maxlen == 0) {
+ return 0;
+ }
+
+ buf = fifo8_pop_buf(fifo, maxlen, &n);
+ if (dest) {
+ memcpy(dest, buf, n);
+ }
+
+ return n;
+}
+
+static uint32_t esp_get_tc(ESPState *s)
+{
+ uint32_t dmalen;
+
+ dmalen = s->rregs[ESP_TCLO];
+ dmalen |= s->rregs[ESP_TCMID] << 8;
+ dmalen |= s->rregs[ESP_TCHI] << 16;
+
+ return dmalen;
+}
+
+static void esp_set_tc(ESPState *s, uint32_t dmalen)
+{
+ s->rregs[ESP_TCLO] = dmalen;
+ s->rregs[ESP_TCMID] = dmalen >> 8;
+ s->rregs[ESP_TCHI] = dmalen >> 16;
+}
+
+static uint32_t esp_get_stc(ESPState *s)
+{
+ uint32_t dmalen;
+
+ dmalen = s->wregs[ESP_TCLO];
+ dmalen |= s->wregs[ESP_TCMID] << 8;
+ dmalen |= s->wregs[ESP_TCHI] << 16;
+
+ return dmalen;
+}
+
+static 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;
+}
+
+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);
+}
+
+static void esp_set_pdma_cb(ESPState *s, enum pdma_cb cb)
+{
+ s->pdma_cb = cb;
+}
+
+static int esp_select(ESPState *s)
+{
+ int target;
+
+ target = s->wregs[ESP_WBUSID] & BUSID_DID;
+
+ s->ti_size = 0;
+ fifo8_reset(&s->fifo);
+
+ s->current_dev = scsi_device_find(&s->bus, 0, target, 0);
+ if (!s->current_dev) {
+ /* No such drive */
+ s->rregs[ESP_RSTAT] = 0;
+ s->rregs[ESP_RINTR] = INTR_DC;
+ s->rregs[ESP_RSEQ] = SEQ_0;
+ esp_raise_irq(s);
+ return -1;
+ }
+
+ /*
+ * Note that we deliberately don't raise the IRQ here: this will be done
+ * either in do_command_phase() for DATA OUT transfers or by the deferred
+ * IRQ mechanism in esp_transfer_data() for DATA IN transfers
+ */
+ s->rregs[ESP_RINTR] |= INTR_FC;
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ return 0;
+}
+
+static uint32_t get_cmd(ESPState *s, uint32_t maxlen)
+{
+ uint8_t buf[ESP_CMDFIFO_SZ];
+ uint32_t dmalen, n;
+ int target;
+
+ if (s->current_req) {
+ /* Started a new command before the old one finished. Cancel it. */
+ scsi_req_cancel(s->current_req);
+ }
+
+ 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);
+ 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;
+}
+
+static void do_command_phase(ESPState *s)
+{
+ uint32_t cmdlen;
+ int32_t datalen;
+ SCSIDevice *current_lun;
+ uint8_t buf[ESP_CMDFIFO_SZ];
+
+ trace_esp_do_command_phase(s->lun);
+ cmdlen = fifo8_num_used(&s->cmdfifo);
+ if (!cmdlen || !s->current_dev) {
+ return;
+ }
+ esp_fifo_pop_buf(&s->cmdfifo, buf, cmdlen);
+
+ current_lun = scsi_device_find(&s->bus, 0, s->current_dev->id, s->lun);
+ s->current_req = scsi_req_new(current_lun, 0, s->lun, buf, cmdlen, s);
+ datalen = scsi_req_enqueue(s->current_req);
+ s->ti_size = datalen;
+ fifo8_reset(&s->cmdfifo);
+ 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;
+ }
+}
+
+static void do_message_phase(ESPState *s)
+{
+ if (s->cmdfifo_cdb_offset) {
+ uint8_t message = esp_fifo_pop(&s->cmdfifo);
+
+ trace_esp_do_identify(message);
+ s->lun = message & 7;
+ s->cmdfifo_cdb_offset--;
+ }
+
+ /* Ignore extended messages for now */
+ if (s->cmdfifo_cdb_offset) {
+ int len = MIN(s->cmdfifo_cdb_offset, fifo8_num_used(&s->cmdfifo));
+ esp_fifo_pop_buf(&s->cmdfifo, NULL, len);
+ s->cmdfifo_cdb_offset = 0;
+ }
+}
+
+static void do_cmd(ESPState *s)
+{
+ do_message_phase(s);
+ assert(s->cmdfifo_cdb_offset == 0);
+ do_command_phase(s);
+}
+
+static void satn_pdma_cb(ESPState *s)
+{
+ if (!esp_get_tc(s) && !fifo8_is_empty(&s->cmdfifo)) {
+ s->cmdfifo_cdb_offset = 1;
+ s->do_cmd = 0;
+ do_cmd(s);
+ }
+}
+
+static void handle_satn(ESPState *s)
+{
+ int32_t cmdlen;
+
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_satn;
+ return;
+ }
+ esp_set_pdma_cb(s, SATN_PDMA_CB);
+ cmdlen = get_cmd(s, ESP_CMDFIFO_SZ);
+ if (cmdlen > 0) {
+ s->cmdfifo_cdb_offset = 1;
+ s->do_cmd = 0;
+ do_cmd(s);
+ } else if (cmdlen == 0) {
+ s->do_cmd = 1;
+ /* Target present, but no cmd yet - switch to command phase */
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ s->rregs[ESP_RSTAT] = STAT_CD;
+ }
+}
+
+static void s_without_satn_pdma_cb(ESPState *s)
+{
+ if (!esp_get_tc(s) && !fifo8_is_empty(&s->cmdfifo)) {
+ s->cmdfifo_cdb_offset = 0;
+ s->do_cmd = 0;
+ do_cmd(s);
+ }
+}
+
+static void handle_s_without_atn(ESPState *s)
+{
+ int32_t cmdlen;
+
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_s_without_atn;
+ return;
+ }
+ esp_set_pdma_cb(s, S_WITHOUT_SATN_PDMA_CB);
+ cmdlen = get_cmd(s, ESP_CMDFIFO_SZ);
+ if (cmdlen > 0) {
+ s->cmdfifo_cdb_offset = 0;
+ s->do_cmd = 0;
+ do_cmd(s);
+ } else if (cmdlen == 0) {
+ s->do_cmd = 1;
+ /* Target present, but no cmd yet - switch to command phase */
+ s->rregs[ESP_RSEQ] = SEQ_CD;
+ s->rregs[ESP_RSTAT] = STAT_CD;
+ }
+}
+
+static void satn_stop_pdma_cb(ESPState *s)
+{
+ if (!esp_get_tc(s) && !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);
+ }
+}
+
+static void handle_satn_stop(ESPState *s)
+{
+ int32_t cmdlen;
+
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_satn_stop;
+ return;
+ }
+ esp_set_pdma_cb(s, 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;
+ }
+}
+
+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);
+}
+
+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 {
+ esp_set_pdma_cb(s, 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);
+}
+
+static void esp_dma_done(ESPState *s)
+{
+ s->rregs[ESP_RSTAT] |= STAT_TC;
+ s->rregs[ESP_RINTR] |= INTR_BS;
+ s->rregs[ESP_RFLAGS] = 0;
+ esp_set_tc(s, 0);
+ esp_raise_irq(s);
+}
+
+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) {
+ /* Ensure we have received complete command after SATN and stop */
+ if (esp_get_tc(s) || fifo8_is_empty(&s->cmdfifo)) {
+ return;
+ }
+
+ 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 command 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 (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);
+ }
+}
+
+static void esp_do_dma(ESPState *s)
+{
+ uint32_t len, cmdlen;
+ int to_device = ((s->rregs[ESP_RSTAT] & 7) == STAT_DO);
+ uint8_t buf[ESP_CMDFIFO_SZ];
+
+ len = esp_get_tc(s);
+ if (s->do_cmd) {
+ /*
+ * handle_ti_cmd() case: esp_do_dma() is called only from
+ * handle_ti_cmd() with do_cmd != NULL (see the assert())
+ */
+ cmdlen = fifo8_num_used(&s->cmdfifo);
+ trace_esp_do_dma(cmdlen, len);
+ if (s->dma_memory_read) {
+ len = MIN(len, fifo8_num_free(&s->cmdfifo));
+ s->dma_memory_read(s->dma_opaque, buf, len);
+ fifo8_push_all(&s->cmdfifo, buf, len);
+ } else {
+ esp_set_pdma_cb(s, DO_DMA_PDMA_CB);
+ esp_raise_drq(s);
+ return;
+ }
+ 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 command 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 (len > s->async_len) {
+ len = s->async_len;
+ }
+ if (to_device) {
+ if (s->dma_memory_read) {
+ s->dma_memory_read(s->dma_opaque, s->async_buf, len);
+ } else {
+ esp_set_pdma_cb(s, DO_DMA_PDMA_CB);
+ esp_raise_drq(s);
+ return;
+ }
+ } else {
+ if (s->dma_memory_write) {
+ s->dma_memory_write(s->dma_opaque, s->async_buf, len);
+ } else {
+ /* Adjust TC for any leftover data in the FIFO */
+ if (!fifo8_is_empty(&s->fifo)) {
+ esp_set_tc(s, esp_get_tc(s) - fifo8_num_used(&s->fifo));
+ }
+
+ /* Copy device data to FIFO */
+ 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;
+
+ /*
+ * MacOS toolbox uses a TI length of 16 bytes for all commands, so
+ * commands shorter than this must be padded accordingly
+ */
+ if (len < esp_get_tc(s) && esp_get_tc(s) <= ESP_FIFO_SZ) {
+ while (fifo8_num_used(&s->fifo) < ESP_FIFO_SZ) {
+ esp_fifo_push(&s->fifo, 0);
+ len++;
+ }
+ }
+
+ esp_set_tc(s, esp_get_tc(s) - len);
+ esp_set_pdma_cb(s, DO_DMA_PDMA_CB);
+ esp_raise_drq(s);
+
+ /* Indicate transfer to FIFO is complete */
+ s->rregs[ESP_RSTAT] |= STAT_TC;
+ return;
+ }
+ }
+ esp_set_tc(s, esp_get_tc(s) - len);
+ s->async_buf += len;
+ s->async_len -= len;
+ if (to_device) {
+ s->ti_size += len;
+ } else {
+ s->ti_size -= len;
+ }
+ if (s->async_len == 0) {
+ scsi_req_continue(s->current_req);
+ /*
+ * If there is still data to be read from the device then
+ * complete the DMA operation immediately. Otherwise defer
+ * until the scsi layer has completed.
+ */
+ if (to_device || esp_get_tc(s) != 0 || s->ti_size == 0) {
+ return;
+ }
+ }
+
+ /* Partially filled a scsi buffer. Complete immediately. */
+ esp_dma_done(s);
+ esp_lower_drq(s);
+}
+
+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 command 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 {
+ if (fifo8_is_empty(&s->fifo)) {
+ fifo8_push(&s->fifo, s->async_buf[0]);
+ s->async_buf++;
+ s->async_len--;
+ s->ti_size--;
+ }
+ }
+
+ if (s->async_len == 0) {
+ scsi_req_continue(s->current_req);
+ return;
+ }
+
+ s->rregs[ESP_RINTR] |= INTR_BS;
+ esp_raise_irq(s);
+}
+
+static void esp_pdma_cb(ESPState *s)
+{
+ switch (s->pdma_cb) {
+ case SATN_PDMA_CB:
+ satn_pdma_cb(s);
+ break;
+ case S_WITHOUT_SATN_PDMA_CB:
+ s_without_satn_pdma_cb(s);
+ break;
+ case SATN_STOP_PDMA_CB:
+ satn_stop_pdma_cb(s);
+ break;
+ case WRITE_RESPONSE_PDMA_CB:
+ write_response_pdma_cb(s);
+ break;
+ case DO_DMA_PDMA_CB:
+ do_dma_pdma_cb(s);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+void esp_command_complete(SCSIRequest *req, size_t resid)
+{
+ ESPState *s = req->hba_private;
+ int to_device = ((s->rregs[ESP_RSTAT] & 7) == STAT_DO);
+
+ trace_esp_command_complete();
+
+ /*
+ * Non-DMA transfers from the target will leave the last byte in
+ * the FIFO so don't reset ti_size in this case
+ */
+ if (s->dma || to_device) {
+ if (s->ti_size != 0) {
+ trace_esp_command_complete_unexpected();
+ }
+ s->ti_size = 0;
+ }
+
+ s->async_len = 0;
+ if (req->status) {
+ trace_esp_command_complete_fail();
+ }
+ s->status = req->status;
+
+ /*
+ * If the transfer is finished, switch to status phase. For non-DMA
+ * transfers from the target the last byte is still in the FIFO
+ */
+ if (s->ti_size == 0) {
+ s->rregs[ESP_RSTAT] = STAT_TC | 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;
+ }
+}
+
+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 (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_TI | 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 if (s->ti_cmd == CMD_TI) {
+ esp_do_nodma(s);
+ }
+}
+
+static void handle_ti(ESPState *s)
+{
+ uint32_t dmalen;
+
+ if (s->dma && !s->dma_enabled) {
+ s->dma_cb = handle_ti;
+ return;
+ }
+
+ 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);
+ }
+}
+
+void esp_hard_reset(ESPState *s)
+{
+ memset(s->rregs, 0, ESP_REGS);
+ memset(s->wregs, 0, ESP_REGS);
+ s->tchi_written = 0;
+ s->ti_size = 0;
+ s->async_len = 0;
+ fifo8_reset(&s->fifo);
+ fifo8_reset(&s->cmdfifo);
+ s->dma = 0;
+ s->do_cmd = 0;
+ s->dma_cb = NULL;
+
+ s->rregs[ESP_CFG1] = 7;
+}
+
+static void esp_soft_reset(ESPState *s)
+{
+ qemu_irq_lower(s->irq);
+ qemu_irq_lower(s->irq_data);
+ esp_hard_reset(s);
+}
+
+static void esp_bus_reset(ESPState *s)
+{
+ bus_cold_reset(BUS(&s->bus));
+}
+
+static void parent_esp_reset(ESPState *s, int irq, int level)
+{
+ if (level) {
+ esp_soft_reset(s);
+ }
+}
+
+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 {
+ if ((s->rregs[ESP_RSTAT] & 0x7) == STAT_DI) {
+ if (s->ti_size) {
+ esp_do_nodma(s);
+ } else {
+ /*
+ * The last byte of a non-DMA transfer has been read out
+ * of the FIFO so switch to status phase
+ */
+ s->rregs[ESP_RSTAT] = STAT_TC | STAT_ST;
+ }
+ }
+ 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;
+ /*
+ * According to the datasheet ESP_RSEQ should be cleared, but as the
+ * emulation currently defers information transfers to the next TI
+ * command leave it for now so that pedantic guests such as the old
+ * Linux 2.6 driver see the correct flags before the next SCSI phase
+ * transition.
+ *
+ * s->rregs[ESP_RSEQ] = SEQ_0;
+ */
+ 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;
+}
+
+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);
+
+ /*
+ * If any unexpected message out/command phase data is
+ * transferred using non-DMA, raise the interrupt
+ */
+ if (s->rregs[ESP_CMD] == CMD_TI) {
+ s->rregs[ESP_RINTR] |= INTR_BS;
+ esp_raise_irq(s);
+ }
+ } else {
+ esp_fifo_push(&s->fifo, val);
+ }
+ 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);
+ esp_bus_reset(s);
+ if (!(s->wregs[ESP_CFG1] & CFG1_RESREPT)) {
+ s->rregs[ESP_RINTR] |= INTR_RST;
+ esp_raise_irq(s);
+ }
+ break;
+ case CMD_TI:
+ trace_esp_mem_writeb_cmd_ti(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;
+}
+
+static bool esp_mem_accepts(void *opaque, hwaddr addr,
+ unsigned size, bool is_write,
+ MemTxAttrs attrs)
+{
+ return (size == 1) || (is_write && size == 4);
+}
+
+static bool esp_is_before_version_5(void *opaque, int version_id)
+{
+ ESPState *s = ESP(opaque);
+
+ version_id = MIN(version_id, s->mig_version_id);
+ return version_id < 5;
+}
+
+static bool esp_is_version_5(void *opaque, int version_id)
+{
+ ESPState *s = ESP(opaque);
+
+ version_id = MIN(version_id, s->mig_version_id);
+ return version_id >= 5;
+}
+
+static bool esp_is_version_6(void *opaque, int version_id)
+{
+ ESPState *s = ESP(opaque);
+
+ version_id = MIN(version_id, s->mig_version_id);
+ return version_id >= 6;
+}
+
+int esp_pre_save(void *opaque)
+{
+ ESPState *s = ESP(object_resolve_path_component(
+ OBJECT(opaque), "esp"));
+
+ s->mig_version_id = vmstate_esp.version_id;
+ return 0;
+}
+
+static int esp_post_load(void *opaque, int version_id)
+{
+ ESPState *s = ESP(opaque);
+ int len, i;
+
+ version_id = MIN(version_id, s->mig_version_id);
+
+ if (version_id < 5) {
+ esp_set_tc(s, s->mig_dma_left);
+
+ /* Migrate ti_buf to fifo */
+ len = s->mig_ti_wptr - s->mig_ti_rptr;
+ for (i = 0; i < len; i++) {
+ fifo8_push(&s->fifo, s->mig_ti_buf[i]);
+ }
+
+ /* Migrate cmdbuf to cmdfifo */
+ for (i = 0; i < s->mig_cmdlen; i++) {
+ fifo8_push(&s->cmdfifo, s->mig_cmdbuf[i]);
+ }
+ }
+
+ s->mig_version_id = vmstate_esp.version_id;
+ return 0;
+}
+
+/*
+ * PDMA (or pseudo-DMA) is only used on the Macintosh and requires the
+ * guest CPU to perform the transfers between the SCSI bus and memory
+ * itself. This is indicated by the dma_memory_read and dma_memory_write
+ * functions being NULL (in contrast to the ESP PCI device) whilst
+ * dma_enabled is still set.
+ */
+
+static bool esp_pdma_needed(void *opaque)
+{
+ ESPState *s = ESP(opaque);
+
+ return s->dma_memory_read == NULL && s->dma_memory_write == NULL &&
+ s->dma_enabled;
+}
+
+static const VMStateDescription vmstate_esp_pdma = {
+ .name = "esp/pdma",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .needed = esp_pdma_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(pdma_cb, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_esp = {
+ .name = "esp",
+ .version_id = 6,
+ .minimum_version_id = 3,
+ .post_load = esp_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(rregs, ESPState),
+ VMSTATE_BUFFER(wregs, ESPState),
+ VMSTATE_INT32(ti_size, ESPState),
+ VMSTATE_UINT32_TEST(mig_ti_rptr, ESPState, esp_is_before_version_5),
+ VMSTATE_UINT32_TEST(mig_ti_wptr, ESPState, esp_is_before_version_5),
+ VMSTATE_BUFFER_TEST(mig_ti_buf, ESPState, esp_is_before_version_5),
+ VMSTATE_UINT32(status, ESPState),
+ VMSTATE_UINT32_TEST(mig_deferred_status, ESPState,
+ esp_is_before_version_5),
+ VMSTATE_BOOL_TEST(mig_deferred_complete, ESPState,
+ esp_is_before_version_5),
+ VMSTATE_UINT32(dma, ESPState),
+ VMSTATE_STATIC_BUFFER(mig_cmdbuf, ESPState, 0,
+ esp_is_before_version_5, 0, 16),
+ VMSTATE_STATIC_BUFFER(mig_cmdbuf, ESPState, 4,
+ esp_is_before_version_5, 16,
+ sizeof(typeof_field(ESPState, mig_cmdbuf))),
+ VMSTATE_UINT32_TEST(mig_cmdlen, ESPState, esp_is_before_version_5),
+ VMSTATE_UINT32(do_cmd, ESPState),
+ VMSTATE_UINT32_TEST(mig_dma_left, ESPState, esp_is_before_version_5),
+ VMSTATE_BOOL_TEST(data_in_ready, ESPState, esp_is_version_5),
+ VMSTATE_UINT8_TEST(cmdfifo_cdb_offset, ESPState, esp_is_version_5),
+ VMSTATE_FIFO8_TEST(fifo, ESPState, esp_is_version_5),
+ VMSTATE_FIFO8_TEST(cmdfifo, ESPState, esp_is_version_5),
+ VMSTATE_UINT8_TEST(ti_cmd, ESPState, esp_is_version_5),
+ VMSTATE_UINT8_TEST(lun, ESPState, esp_is_version_6),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription * []) {
+ &vmstate_esp_pdma,
+ NULL
+ }
+};
+
+static void sysbus_esp_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = ESP(&sysbus->esp);
+ uint32_t saddr;
+
+ saddr = addr >> sysbus->it_shift;
+ esp_reg_write(s, saddr, val);
+}
+
+static uint64_t sysbus_esp_mem_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = ESP(&sysbus->esp);
+ uint32_t saddr;
+
+ saddr = addr >> sysbus->it_shift;
+ return esp_reg_read(s, saddr);
+}
+
+static const MemoryRegionOps sysbus_esp_mem_ops = {
+ .read = sysbus_esp_mem_read,
+ .write = sysbus_esp_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.accepts = esp_mem_accepts,
+};
+
+static void sysbus_esp_pdma_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = ESP(&sysbus->esp);
+
+ trace_esp_pdma_write(size);
+
+ switch (size) {
+ case 1:
+ esp_pdma_write(s, val);
+ break;
+ case 2:
+ esp_pdma_write(s, val >> 8);
+ esp_pdma_write(s, val);
+ break;
+ }
+ esp_pdma_cb(s);
+}
+
+static uint64_t sysbus_esp_pdma_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ SysBusESPState *sysbus = opaque;
+ ESPState *s = ESP(&sysbus->esp);
+ uint64_t val = 0;
+
+ trace_esp_pdma_read(size);
+
+ switch (size) {
+ case 1:
+ val = esp_pdma_read(s);
+ break;
+ case 2:
+ val = esp_pdma_read(s);
+ val = (val << 8) | esp_pdma_read(s);
+ break;
+ }
+ if (fifo8_num_used(&s->fifo) < 2) {
+ esp_pdma_cb(s);
+ }
+ return val;
+}
+
+static void *esp_load_request(QEMUFile *f, SCSIRequest *req)
+{
+ ESPState *s = container_of(req->bus, ESPState, bus);
+
+ scsi_req_ref(req);
+ s->current_req = req;
+ return s;
+}
+
+static const MemoryRegionOps sysbus_esp_pdma_ops = {
+ .read = sysbus_esp_pdma_read,
+ .write = sysbus_esp_pdma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 2,
+};
+
+static const struct SCSIBusInfo esp_scsi_info = {
+ .tcq = false,
+ .max_target = ESP_MAX_DEVS,
+ .max_lun = 7,
+
+ .load_request = esp_load_request,
+ .transfer_data = esp_transfer_data,
+ .complete = esp_command_complete,
+ .cancel = esp_request_cancelled
+};
+
+static void sysbus_esp_gpio_demux(void *opaque, int irq, int level)
+{
+ SysBusESPState *sysbus = SYSBUS_ESP(opaque);
+ ESPState *s = ESP(&sysbus->esp);
+
+ switch (irq) {
+ case 0:
+ parent_esp_reset(s, irq, level);
+ break;
+ case 1:
+ esp_dma_enable(opaque, irq, level);
+ break;
+ }
+}
+
+static void sysbus_esp_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ SysBusESPState *sysbus = SYSBUS_ESP(dev);
+ ESPState *s = ESP(&sysbus->esp);
+
+ if (!qdev_realize(DEVICE(s), NULL, errp)) {
+ return;
+ }
+
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_irq(sbd, &s->irq_data);
+ assert(sysbus->it_shift != -1);
+
+ s->chip_id = TCHI_FAS100A;
+ memory_region_init_io(&sysbus->iomem, OBJECT(sysbus), &sysbus_esp_mem_ops,
+ sysbus, "esp-regs", ESP_REGS << sysbus->it_shift);
+ sysbus_init_mmio(sbd, &sysbus->iomem);
+ memory_region_init_io(&sysbus->pdma, OBJECT(sysbus), &sysbus_esp_pdma_ops,
+ sysbus, "esp-pdma", 4);
+ sysbus_init_mmio(sbd, &sysbus->pdma);
+
+ qdev_init_gpio_in(dev, sysbus_esp_gpio_demux, 2);
+
+ scsi_bus_init(&s->bus, sizeof(s->bus), dev, &esp_scsi_info);
+}
+
+static void sysbus_esp_hard_reset(DeviceState *dev)
+{
+ SysBusESPState *sysbus = SYSBUS_ESP(dev);
+ ESPState *s = ESP(&sysbus->esp);
+
+ esp_hard_reset(s);
+}
+
+static void sysbus_esp_init(Object *obj)
+{
+ SysBusESPState *sysbus = SYSBUS_ESP(obj);
+
+ object_initialize_child(obj, "esp", &sysbus->esp, TYPE_ESP);
+}
+
+static const VMStateDescription vmstate_sysbus_esp_scsi = {
+ .name = "sysbusespscsi",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .pre_save = esp_pre_save,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_V(esp.mig_version_id, SysBusESPState, 2),
+ VMSTATE_STRUCT(esp, SysBusESPState, 0, vmstate_esp, ESPState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void sysbus_esp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sysbus_esp_realize;
+ dc->reset = sysbus_esp_hard_reset;
+ dc->vmsd = &vmstate_sysbus_esp_scsi;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo sysbus_esp_info = {
+ .name = TYPE_SYSBUS_ESP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_init = sysbus_esp_init,
+ .instance_size = sizeof(SysBusESPState),
+ .class_init = sysbus_esp_class_init,
+};
+
+static void esp_finalize(Object *obj)
+{
+ ESPState *s = ESP(obj);
+
+ fifo8_destroy(&s->fifo);
+ fifo8_destroy(&s->cmdfifo);
+}
+
+static void esp_init(Object *obj)
+{
+ ESPState *s = ESP(obj);
+
+ fifo8_create(&s->fifo, ESP_FIFO_SZ);
+ fifo8_create(&s->cmdfifo, ESP_CMDFIFO_SZ);
+}
+
+static void esp_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ /* internal device for sysbusesp/pciespscsi, not user-creatable */
+ dc->user_creatable = false;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo esp_info = {
+ .name = TYPE_ESP,
+ .parent = TYPE_DEVICE,
+ .instance_init = esp_init,
+ .instance_finalize = esp_finalize,
+ .instance_size = sizeof(ESPState),
+ .class_init = esp_class_init,
+};
+
+static void esp_register_types(void)
+{
+ type_register_static(&sysbus_esp_info);
+ type_register_static(&esp_info);
+}
+
+type_init(esp_register_types)
diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c
new file mode 100644
index 00000000..50979640
--- /dev/null
+++ b/hw/scsi/lsi53c895a.c
@@ -0,0 +1,2376 @@
+/*
+ * QEMU LSI53C895A SCSI Host Bus Adapter emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+/* Note:
+ * LSI53C810 emulation is incorrect, in the sense that it supports
+ * features added in later evolutions. This should not be a problem,
+ * as well-behaved operating systems will not try to use them.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/irq.h"
+#include "hw/pci/pci.h"
+#include "hw/scsi/scsi.h"
+#include "migration/vmstate.h"
+#include "sysemu/dma.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "qom/object.h"
+
+static const char *names[] = {
+ "SCNTL0", "SCNTL1", "SCNTL2", "SCNTL3", "SCID", "SXFER", "SDID", "GPREG",
+ "SFBR", "SOCL", "SSID", "SBCL", "DSTAT", "SSTAT0", "SSTAT1", "SSTAT2",
+ "DSA0", "DSA1", "DSA2", "DSA3", "ISTAT", "0x15", "0x16", "0x17",
+ "CTEST0", "CTEST1", "CTEST2", "CTEST3", "TEMP0", "TEMP1", "TEMP2", "TEMP3",
+ "DFIFO", "CTEST4", "CTEST5", "CTEST6", "DBC0", "DBC1", "DBC2", "DCMD",
+ "DNAD0", "DNAD1", "DNAD2", "DNAD3", "DSP0", "DSP1", "DSP2", "DSP3",
+ "DSPS0", "DSPS1", "DSPS2", "DSPS3", "SCRATCHA0", "SCRATCHA1", "SCRATCHA2", "SCRATCHA3",
+ "DMODE", "DIEN", "SBR", "DCNTL", "ADDER0", "ADDER1", "ADDER2", "ADDER3",
+ "SIEN0", "SIEN1", "SIST0", "SIST1", "SLPAR", "0x45", "MACNTL", "GPCNTL",
+ "STIME0", "STIME1", "RESPID", "0x4b", "STEST0", "STEST1", "STEST2", "STEST3",
+ "SIDL", "0x51", "0x52", "0x53", "SODL", "0x55", "0x56", "0x57",
+ "SBDL", "0x59", "0x5a", "0x5b", "SCRATCHB0", "SCRATCHB1", "SCRATCHB2", "SCRATCHB3",
+};
+
+#define LSI_MAX_DEVS 7
+
+#define LSI_SCNTL0_TRG 0x01
+#define LSI_SCNTL0_AAP 0x02
+#define LSI_SCNTL0_EPC 0x08
+#define LSI_SCNTL0_WATN 0x10
+#define LSI_SCNTL0_START 0x20
+
+#define LSI_SCNTL1_SST 0x01
+#define LSI_SCNTL1_IARB 0x02
+#define LSI_SCNTL1_AESP 0x04
+#define LSI_SCNTL1_RST 0x08
+#define LSI_SCNTL1_CON 0x10
+#define LSI_SCNTL1_DHP 0x20
+#define LSI_SCNTL1_ADB 0x40
+#define LSI_SCNTL1_EXC 0x80
+
+#define LSI_SCNTL2_WSR 0x01
+#define LSI_SCNTL2_VUE0 0x02
+#define LSI_SCNTL2_VUE1 0x04
+#define LSI_SCNTL2_WSS 0x08
+#define LSI_SCNTL2_SLPHBEN 0x10
+#define LSI_SCNTL2_SLPMD 0x20
+#define LSI_SCNTL2_CHM 0x40
+#define LSI_SCNTL2_SDU 0x80
+
+#define LSI_ISTAT0_DIP 0x01
+#define LSI_ISTAT0_SIP 0x02
+#define LSI_ISTAT0_INTF 0x04
+#define LSI_ISTAT0_CON 0x08
+#define LSI_ISTAT0_SEM 0x10
+#define LSI_ISTAT0_SIGP 0x20
+#define LSI_ISTAT0_SRST 0x40
+#define LSI_ISTAT0_ABRT 0x80
+
+#define LSI_ISTAT1_SI 0x01
+#define LSI_ISTAT1_SRUN 0x02
+#define LSI_ISTAT1_FLSH 0x04
+
+#define LSI_SSTAT0_SDP0 0x01
+#define LSI_SSTAT0_RST 0x02
+#define LSI_SSTAT0_WOA 0x04
+#define LSI_SSTAT0_LOA 0x08
+#define LSI_SSTAT0_AIP 0x10
+#define LSI_SSTAT0_OLF 0x20
+#define LSI_SSTAT0_ORF 0x40
+#define LSI_SSTAT0_ILF 0x80
+
+#define LSI_SIST0_PAR 0x01
+#define LSI_SIST0_RST 0x02
+#define LSI_SIST0_UDC 0x04
+#define LSI_SIST0_SGE 0x08
+#define LSI_SIST0_RSL 0x10
+#define LSI_SIST0_SEL 0x20
+#define LSI_SIST0_CMP 0x40
+#define LSI_SIST0_MA 0x80
+
+#define LSI_SIST1_HTH 0x01
+#define LSI_SIST1_GEN 0x02
+#define LSI_SIST1_STO 0x04
+#define LSI_SIST1_SBMC 0x10
+
+#define LSI_SOCL_IO 0x01
+#define LSI_SOCL_CD 0x02
+#define LSI_SOCL_MSG 0x04
+#define LSI_SOCL_ATN 0x08
+#define LSI_SOCL_SEL 0x10
+#define LSI_SOCL_BSY 0x20
+#define LSI_SOCL_ACK 0x40
+#define LSI_SOCL_REQ 0x80
+
+#define LSI_DSTAT_IID 0x01
+#define LSI_DSTAT_SIR 0x04
+#define LSI_DSTAT_SSI 0x08
+#define LSI_DSTAT_ABRT 0x10
+#define LSI_DSTAT_BF 0x20
+#define LSI_DSTAT_MDPE 0x40
+#define LSI_DSTAT_DFE 0x80
+
+#define LSI_DCNTL_COM 0x01
+#define LSI_DCNTL_IRQD 0x02
+#define LSI_DCNTL_STD 0x04
+#define LSI_DCNTL_IRQM 0x08
+#define LSI_DCNTL_SSM 0x10
+#define LSI_DCNTL_PFEN 0x20
+#define LSI_DCNTL_PFF 0x40
+#define LSI_DCNTL_CLSE 0x80
+
+#define LSI_DMODE_MAN 0x01
+#define LSI_DMODE_BOF 0x02
+#define LSI_DMODE_ERMP 0x04
+#define LSI_DMODE_ERL 0x08
+#define LSI_DMODE_DIOM 0x10
+#define LSI_DMODE_SIOM 0x20
+
+#define LSI_CTEST2_DACK 0x01
+#define LSI_CTEST2_DREQ 0x02
+#define LSI_CTEST2_TEOP 0x04
+#define LSI_CTEST2_PCICIE 0x08
+#define LSI_CTEST2_CM 0x10
+#define LSI_CTEST2_CIO 0x20
+#define LSI_CTEST2_SIGP 0x40
+#define LSI_CTEST2_DDIR 0x80
+
+#define LSI_CTEST5_BL2 0x04
+#define LSI_CTEST5_DDIR 0x08
+#define LSI_CTEST5_MASR 0x10
+#define LSI_CTEST5_DFSN 0x20
+#define LSI_CTEST5_BBCK 0x40
+#define LSI_CTEST5_ADCK 0x80
+
+#define LSI_CCNTL0_DILS 0x01
+#define LSI_CCNTL0_DISFC 0x10
+#define LSI_CCNTL0_ENNDJ 0x20
+#define LSI_CCNTL0_PMJCTL 0x40
+#define LSI_CCNTL0_ENPMJ 0x80
+
+#define LSI_CCNTL1_EN64DBMV 0x01
+#define LSI_CCNTL1_EN64TIBMV 0x02
+#define LSI_CCNTL1_64TIMOD 0x04
+#define LSI_CCNTL1_DDAC 0x08
+#define LSI_CCNTL1_ZMOD 0x80
+
+#define LSI_SBCL_ATN 0x08
+#define LSI_SBCL_BSY 0x20
+#define LSI_SBCL_ACK 0x40
+#define LSI_SBCL_REQ 0x80
+
+/* Enable Response to Reselection */
+#define LSI_SCID_RRE 0x60
+
+#define LSI_CCNTL1_40BIT (LSI_CCNTL1_EN64TIBMV|LSI_CCNTL1_64TIMOD)
+
+#define PHASE_DO 0
+#define PHASE_DI 1
+#define PHASE_CMD 2
+#define PHASE_ST 3
+#define PHASE_MO 6
+#define PHASE_MI 7
+#define PHASE_MASK 7
+
+/* Maximum length of MSG IN data. */
+#define LSI_MAX_MSGIN_LEN 8
+
+/* Flag set if this is a tagged command. */
+#define LSI_TAG_VALID (1 << 16)
+
+/* Maximum instructions to process. */
+#define LSI_MAX_INSN 10000
+
+typedef struct lsi_request {
+ SCSIRequest *req;
+ uint32_t tag;
+ uint32_t dma_len;
+ uint8_t *dma_buf;
+ uint32_t pending;
+ int out;
+ QTAILQ_ENTRY(lsi_request) next;
+} lsi_request;
+
+enum {
+ LSI_NOWAIT, /* SCRIPTS are running or stopped */
+ LSI_WAIT_RESELECT, /* Wait Reselect instruction has been issued */
+ LSI_DMA_SCRIPTS, /* processing DMA from lsi_execute_script */
+ LSI_DMA_IN_PROGRESS, /* DMA operation is in progress */
+};
+
+enum {
+ LSI_MSG_ACTION_COMMAND = 0,
+ LSI_MSG_ACTION_DISCONNECT = 1,
+ LSI_MSG_ACTION_DOUT = 2,
+ LSI_MSG_ACTION_DIN = 3,
+};
+
+struct LSIState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ qemu_irq ext_irq;
+ MemoryRegion mmio_io;
+ MemoryRegion ram_io;
+ MemoryRegion io_io;
+ AddressSpace pci_io_as;
+
+ int carry; /* ??? Should this be an a visible register somewhere? */
+ int status;
+ int msg_action;
+ int msg_len;
+ uint8_t msg[LSI_MAX_MSGIN_LEN];
+ int waiting;
+ SCSIBus bus;
+ int current_lun;
+ /* The tag is a combination of the device ID and the SCSI tag. */
+ uint32_t select_tag;
+ int command_complete;
+ QTAILQ_HEAD(, lsi_request) queue;
+ lsi_request *current;
+
+ uint32_t dsa;
+ uint32_t temp;
+ uint32_t dnad;
+ uint32_t dbc;
+ uint8_t istat0;
+ uint8_t istat1;
+ uint8_t dcmd;
+ uint8_t dstat;
+ uint8_t dien;
+ uint8_t sist0;
+ uint8_t sist1;
+ uint8_t sien0;
+ uint8_t sien1;
+ uint8_t mbox0;
+ uint8_t mbox1;
+ uint8_t dfifo;
+ uint8_t ctest2;
+ uint8_t ctest3;
+ uint8_t ctest4;
+ uint8_t ctest5;
+ uint8_t ccntl0;
+ uint8_t ccntl1;
+ uint32_t dsp;
+ uint32_t dsps;
+ uint8_t dmode;
+ uint8_t dcntl;
+ uint8_t scntl0;
+ uint8_t scntl1;
+ uint8_t scntl2;
+ uint8_t scntl3;
+ uint8_t sstat0;
+ uint8_t sstat1;
+ uint8_t scid;
+ uint8_t sxfer;
+ uint8_t socl;
+ uint8_t sdid;
+ uint8_t ssid;
+ uint8_t sfbr;
+ uint8_t sbcl;
+ uint8_t stest1;
+ uint8_t stest2;
+ uint8_t stest3;
+ uint8_t sidl;
+ uint8_t stime0;
+ uint8_t respid0;
+ uint8_t respid1;
+ uint32_t mmrs;
+ uint32_t mmws;
+ uint32_t sfs;
+ uint32_t drs;
+ uint32_t sbms;
+ uint32_t dbms;
+ uint32_t dnad64;
+ uint32_t pmjad1;
+ uint32_t pmjad2;
+ uint32_t rbc;
+ uint32_t ua;
+ uint32_t ia;
+ uint32_t sbc;
+ uint32_t csbc;
+ uint32_t scratch[18]; /* SCRATCHA-SCRATCHR */
+ uint8_t sbr;
+ uint32_t adder;
+
+ uint8_t script_ram[2048 * sizeof(uint32_t)];
+};
+
+#define TYPE_LSI53C810 "lsi53c810"
+#define TYPE_LSI53C895A "lsi53c895a"
+
+OBJECT_DECLARE_SIMPLE_TYPE(LSIState, LSI53C895A)
+
+static const char *scsi_phases[] = {
+ "DOUT",
+ "DIN",
+ "CMD",
+ "STATUS",
+ "RSVOUT",
+ "RSVIN",
+ "MSGOUT",
+ "MSGIN"
+};
+
+static const char *scsi_phase_name(int phase)
+{
+ return scsi_phases[phase & PHASE_MASK];
+}
+
+static inline int lsi_irq_on_rsl(LSIState *s)
+{
+ return (s->sien0 & LSI_SIST0_RSL) && (s->scid & LSI_SCID_RRE);
+}
+
+static lsi_request *get_pending_req(LSIState *s)
+{
+ lsi_request *p;
+
+ QTAILQ_FOREACH(p, &s->queue, next) {
+ if (p->pending) {
+ return p;
+ }
+ }
+ return NULL;
+}
+
+static void lsi_soft_reset(LSIState *s)
+{
+ trace_lsi_reset();
+ s->carry = 0;
+
+ s->msg_action = LSI_MSG_ACTION_COMMAND;
+ s->msg_len = 0;
+ s->waiting = LSI_NOWAIT;
+ s->dsa = 0;
+ s->dnad = 0;
+ s->dbc = 0;
+ s->temp = 0;
+ memset(s->scratch, 0, sizeof(s->scratch));
+ s->istat0 = 0;
+ s->istat1 = 0;
+ s->dcmd = 0x40;
+ s->dstat = 0;
+ s->dien = 0;
+ s->sist0 = 0;
+ s->sist1 = 0;
+ s->sien0 = 0;
+ s->sien1 = 0;
+ s->mbox0 = 0;
+ s->mbox1 = 0;
+ s->dfifo = 0;
+ s->ctest2 = LSI_CTEST2_DACK;
+ s->ctest3 = 0;
+ s->ctest4 = 0;
+ s->ctest5 = 0;
+ s->ccntl0 = 0;
+ s->ccntl1 = 0;
+ s->dsp = 0;
+ s->dsps = 0;
+ s->dmode = 0;
+ s->dcntl = 0;
+ s->scntl0 = 0xc0;
+ s->scntl1 = 0;
+ s->scntl2 = 0;
+ s->scntl3 = 0;
+ s->sstat0 = 0;
+ s->sstat1 = 0;
+ s->scid = 7;
+ s->sxfer = 0;
+ s->socl = 0;
+ s->sdid = 0;
+ s->ssid = 0;
+ s->sbcl = 0;
+ s->stest1 = 0;
+ s->stest2 = 0;
+ s->stest3 = 0;
+ s->sidl = 0;
+ s->stime0 = 0;
+ s->respid0 = 0x80;
+ s->respid1 = 0;
+ s->mmrs = 0;
+ s->mmws = 0;
+ s->sfs = 0;
+ s->drs = 0;
+ s->sbms = 0;
+ s->dbms = 0;
+ s->dnad64 = 0;
+ s->pmjad1 = 0;
+ s->pmjad2 = 0;
+ s->rbc = 0;
+ s->ua = 0;
+ s->ia = 0;
+ s->sbc = 0;
+ s->csbc = 0;
+ s->sbr = 0;
+ assert(QTAILQ_EMPTY(&s->queue));
+ assert(!s->current);
+}
+
+static int lsi_dma_40bit(LSIState *s)
+{
+ if ((s->ccntl1 & LSI_CCNTL1_40BIT) == LSI_CCNTL1_40BIT)
+ return 1;
+ return 0;
+}
+
+static int lsi_dma_ti64bit(LSIState *s)
+{
+ if ((s->ccntl1 & LSI_CCNTL1_EN64TIBMV) == LSI_CCNTL1_EN64TIBMV)
+ return 1;
+ return 0;
+}
+
+static int lsi_dma_64bit(LSIState *s)
+{
+ if ((s->ccntl1 & LSI_CCNTL1_EN64DBMV) == LSI_CCNTL1_EN64DBMV)
+ return 1;
+ return 0;
+}
+
+static uint8_t lsi_reg_readb(LSIState *s, int offset);
+static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val);
+static void lsi_execute_script(LSIState *s);
+static void lsi_reselect(LSIState *s, lsi_request *p);
+
+static inline void lsi_mem_read(LSIState *s, dma_addr_t addr,
+ void *buf, dma_addr_t len)
+{
+ if (s->dmode & LSI_DMODE_SIOM) {
+ address_space_read(&s->pci_io_as, addr, MEMTXATTRS_UNSPECIFIED,
+ buf, len);
+ } else {
+ pci_dma_read(PCI_DEVICE(s), addr, buf, len);
+ }
+}
+
+static inline void lsi_mem_write(LSIState *s, dma_addr_t addr,
+ const void *buf, dma_addr_t len)
+{
+ if (s->dmode & LSI_DMODE_DIOM) {
+ address_space_write(&s->pci_io_as, addr, MEMTXATTRS_UNSPECIFIED,
+ buf, len);
+ } else {
+ pci_dma_write(PCI_DEVICE(s), addr, buf, len);
+ }
+}
+
+static inline uint32_t read_dword(LSIState *s, uint32_t addr)
+{
+ uint32_t buf;
+
+ pci_dma_read(PCI_DEVICE(s), addr, &buf, 4);
+ return cpu_to_le32(buf);
+}
+
+static void lsi_stop_script(LSIState *s)
+{
+ s->istat1 &= ~LSI_ISTAT1_SRUN;
+}
+
+static void lsi_set_irq(LSIState *s, int level)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ if (s->ext_irq) {
+ qemu_set_irq(s->ext_irq, level);
+ } else {
+ pci_set_irq(d, level);
+ }
+}
+
+static void lsi_update_irq(LSIState *s)
+{
+ int level;
+ static int last_level;
+
+ /* It's unclear whether the DIP/SIP bits should be cleared when the
+ Interrupt Status Registers are cleared or when istat0 is read.
+ We currently do the formwer, which seems to work. */
+ level = 0;
+ if (s->dstat) {
+ if (s->dstat & s->dien)
+ level = 1;
+ s->istat0 |= LSI_ISTAT0_DIP;
+ } else {
+ s->istat0 &= ~LSI_ISTAT0_DIP;
+ }
+
+ if (s->sist0 || s->sist1) {
+ if ((s->sist0 & s->sien0) || (s->sist1 & s->sien1))
+ level = 1;
+ s->istat0 |= LSI_ISTAT0_SIP;
+ } else {
+ s->istat0 &= ~LSI_ISTAT0_SIP;
+ }
+ if (s->istat0 & LSI_ISTAT0_INTF)
+ level = 1;
+
+ if (level != last_level) {
+ trace_lsi_update_irq(level, s->dstat, s->sist1, s->sist0);
+ last_level = level;
+ }
+ lsi_set_irq(s, level);
+
+ if (!s->current && !level && lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON)) {
+ lsi_request *p;
+
+ trace_lsi_update_irq_disconnected();
+ p = get_pending_req(s);
+ if (p) {
+ lsi_reselect(s, p);
+ }
+ }
+}
+
+/* Stop SCRIPTS execution and raise a SCSI interrupt. */
+static void lsi_script_scsi_interrupt(LSIState *s, int stat0, int stat1)
+{
+ uint32_t mask0;
+ uint32_t mask1;
+
+ trace_lsi_script_scsi_interrupt(stat1, stat0, s->sist1, s->sist0);
+ s->sist0 |= stat0;
+ s->sist1 |= stat1;
+ /* Stop processor on fatal or unmasked interrupt. As a special hack
+ we don't stop processing when raising STO. Instead continue
+ execution and stop at the next insn that accesses the SCSI bus. */
+ mask0 = s->sien0 | ~(LSI_SIST0_CMP | LSI_SIST0_SEL | LSI_SIST0_RSL);
+ mask1 = s->sien1 | ~(LSI_SIST1_GEN | LSI_SIST1_HTH);
+ mask1 &= ~LSI_SIST1_STO;
+ if (s->sist0 & mask0 || s->sist1 & mask1) {
+ lsi_stop_script(s);
+ }
+ lsi_update_irq(s);
+}
+
+/* Stop SCRIPTS execution and raise a DMA interrupt. */
+static void lsi_script_dma_interrupt(LSIState *s, int stat)
+{
+ trace_lsi_script_dma_interrupt(stat, s->dstat);
+ s->dstat |= stat;
+ lsi_update_irq(s);
+ lsi_stop_script(s);
+}
+
+static inline void lsi_set_phase(LSIState *s, int phase)
+{
+ s->sbcl &= ~PHASE_MASK;
+ s->sbcl |= phase | LSI_SBCL_REQ;
+ s->sstat1 = (s->sstat1 & ~PHASE_MASK) | phase;
+}
+
+static void lsi_bad_phase(LSIState *s, int out, int new_phase)
+{
+ /* Trigger a phase mismatch. */
+ if (s->ccntl0 & LSI_CCNTL0_ENPMJ) {
+ if ((s->ccntl0 & LSI_CCNTL0_PMJCTL)) {
+ s->dsp = out ? s->pmjad1 : s->pmjad2;
+ } else {
+ s->dsp = (s->scntl2 & LSI_SCNTL2_WSR ? s->pmjad2 : s->pmjad1);
+ }
+ trace_lsi_bad_phase_jump(s->dsp);
+ } else {
+ trace_lsi_bad_phase_interrupt();
+ lsi_script_scsi_interrupt(s, LSI_SIST0_MA, 0);
+ lsi_stop_script(s);
+ }
+ lsi_set_phase(s, new_phase);
+}
+
+
+/* Resume SCRIPTS execution after a DMA operation. */
+static void lsi_resume_script(LSIState *s)
+{
+ if (s->waiting != 2) {
+ s->waiting = LSI_NOWAIT;
+ lsi_execute_script(s);
+ } else {
+ s->waiting = LSI_NOWAIT;
+ }
+}
+
+static void lsi_disconnect(LSIState *s)
+{
+ s->scntl1 &= ~LSI_SCNTL1_CON;
+ s->sstat1 &= ~PHASE_MASK;
+ s->sbcl = 0;
+}
+
+static void lsi_bad_selection(LSIState *s, uint32_t id)
+{
+ trace_lsi_bad_selection(id);
+ lsi_script_scsi_interrupt(s, 0, LSI_SIST1_STO);
+ lsi_disconnect(s);
+}
+
+/* Initiate a SCSI layer data transfer. */
+static void lsi_do_dma(LSIState *s, int out)
+{
+ uint32_t count;
+ dma_addr_t addr;
+ SCSIDevice *dev;
+
+ if (!s->current || !s->current->dma_len) {
+ /* Wait until data is available. */
+ trace_lsi_do_dma_unavailable();
+ return;
+ }
+
+ dev = s->current->req->dev;
+ assert(dev);
+
+ count = s->dbc;
+ if (count > s->current->dma_len)
+ count = s->current->dma_len;
+
+ addr = s->dnad;
+ /* both 40 and Table Indirect 64-bit DMAs store upper bits in dnad64 */
+ if (lsi_dma_40bit(s) || lsi_dma_ti64bit(s))
+ addr |= ((uint64_t)s->dnad64 << 32);
+ else if (s->dbms)
+ addr |= ((uint64_t)s->dbms << 32);
+ else if (s->sbms)
+ addr |= ((uint64_t)s->sbms << 32);
+
+ trace_lsi_do_dma(addr, count);
+ s->csbc += count;
+ s->dnad += count;
+ s->dbc -= count;
+ if (s->current->dma_buf == NULL) {
+ s->current->dma_buf = scsi_req_get_buf(s->current->req);
+ }
+ /* ??? Set SFBR to first data byte. */
+ if (out) {
+ lsi_mem_read(s, addr, s->current->dma_buf, count);
+ } else {
+ lsi_mem_write(s, addr, s->current->dma_buf, count);
+ }
+ s->current->dma_len -= count;
+ if (s->current->dma_len == 0) {
+ s->current->dma_buf = NULL;
+ scsi_req_continue(s->current->req);
+ } else {
+ s->current->dma_buf += count;
+ lsi_resume_script(s);
+ }
+}
+
+
+/* Add a command to the queue. */
+static void lsi_queue_command(LSIState *s)
+{
+ lsi_request *p = s->current;
+
+ trace_lsi_queue_command(p->tag);
+ assert(s->current != NULL);
+ assert(s->current->dma_len == 0);
+ QTAILQ_INSERT_TAIL(&s->queue, s->current, next);
+ s->current = NULL;
+
+ p->pending = 0;
+ p->out = (s->sstat1 & PHASE_MASK) == PHASE_DO;
+}
+
+/* Queue a byte for a MSG IN phase. */
+static void lsi_add_msg_byte(LSIState *s, uint8_t data)
+{
+ if (s->msg_len >= LSI_MAX_MSGIN_LEN) {
+ trace_lsi_add_msg_byte_error();
+ } else {
+ trace_lsi_add_msg_byte(data);
+ s->msg[s->msg_len++] = data;
+ }
+}
+
+/* Perform reselection to continue a command. */
+static void lsi_reselect(LSIState *s, lsi_request *p)
+{
+ int id;
+
+ assert(s->current == NULL);
+ QTAILQ_REMOVE(&s->queue, p, next);
+ s->current = p;
+
+ id = (p->tag >> 8) & 0xf;
+ s->ssid = id | 0x80;
+ /* LSI53C700 Family Compatibility, see LSI53C895A 4-73 */
+ if (!(s->dcntl & LSI_DCNTL_COM)) {
+ s->sfbr = 1 << (id & 0x7);
+ }
+ trace_lsi_reselect(id);
+ s->scntl1 |= LSI_SCNTL1_CON;
+ lsi_set_phase(s, PHASE_MI);
+ s->msg_action = p->out ? LSI_MSG_ACTION_DOUT : LSI_MSG_ACTION_DIN;
+ s->current->dma_len = p->pending;
+ lsi_add_msg_byte(s, 0x80);
+ if (s->current->tag & LSI_TAG_VALID) {
+ lsi_add_msg_byte(s, 0x20);
+ lsi_add_msg_byte(s, p->tag & 0xff);
+ }
+
+ if (lsi_irq_on_rsl(s)) {
+ lsi_script_scsi_interrupt(s, LSI_SIST0_RSL, 0);
+ }
+}
+
+static lsi_request *lsi_find_by_tag(LSIState *s, uint32_t tag)
+{
+ lsi_request *p;
+
+ QTAILQ_FOREACH(p, &s->queue, next) {
+ if (p->tag == tag) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+static void lsi_request_free(LSIState *s, lsi_request *p)
+{
+ if (p == s->current) {
+ s->current = NULL;
+ } else {
+ QTAILQ_REMOVE(&s->queue, p, next);
+ }
+ g_free(p);
+}
+
+static void lsi_request_cancelled(SCSIRequest *req)
+{
+ LSIState *s = LSI53C895A(req->bus->qbus.parent);
+ lsi_request *p = req->hba_private;
+
+ req->hba_private = NULL;
+ lsi_request_free(s, p);
+ scsi_req_unref(req);
+}
+
+/* Record that data is available for a queued command. Returns zero if
+ the device was reselected, nonzero if the IO is deferred. */
+static int lsi_queue_req(LSIState *s, SCSIRequest *req, uint32_t len)
+{
+ lsi_request *p = req->hba_private;
+
+ if (p->pending) {
+ trace_lsi_queue_req_error(p);
+ }
+ p->pending = len;
+ /* Reselect if waiting for it, or if reselection triggers an IRQ
+ and the bus is free.
+ Since no interrupt stacking is implemented in the emulation, it
+ is also required that there are no pending interrupts waiting
+ for service from the device driver. */
+ if (s->waiting == LSI_WAIT_RESELECT ||
+ (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON) &&
+ !(s->istat0 & (LSI_ISTAT0_SIP | LSI_ISTAT0_DIP)))) {
+ /* Reselect device. */
+ lsi_reselect(s, p);
+ return 0;
+ } else {
+ trace_lsi_queue_req(p->tag);
+ p->pending = len;
+ return 1;
+ }
+}
+
+ /* Callback to indicate that the SCSI layer has completed a command. */
+static void lsi_command_complete(SCSIRequest *req, size_t resid)
+{
+ LSIState *s = LSI53C895A(req->bus->qbus.parent);
+ int out;
+
+ out = (s->sstat1 & PHASE_MASK) == PHASE_DO;
+ trace_lsi_command_complete(req->status);
+ s->status = req->status;
+ s->command_complete = 2;
+ if (s->waiting && s->dbc != 0) {
+ /* Raise phase mismatch for short transfers. */
+ lsi_bad_phase(s, out, PHASE_ST);
+ } else {
+ lsi_set_phase(s, PHASE_ST);
+ }
+
+ if (req->hba_private == s->current) {
+ req->hba_private = NULL;
+ lsi_request_free(s, s->current);
+ scsi_req_unref(req);
+ }
+ lsi_resume_script(s);
+}
+
+ /* Callback to indicate that the SCSI layer has completed a transfer. */
+static void lsi_transfer_data(SCSIRequest *req, uint32_t len)
+{
+ LSIState *s = LSI53C895A(req->bus->qbus.parent);
+ int out;
+
+ assert(req->hba_private);
+ if (s->waiting == LSI_WAIT_RESELECT || req->hba_private != s->current ||
+ (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON))) {
+ if (lsi_queue_req(s, req, len)) {
+ return;
+ }
+ }
+
+ out = (s->sstat1 & PHASE_MASK) == PHASE_DO;
+
+ /* host adapter (re)connected */
+ trace_lsi_transfer_data(req->tag, len);
+ s->current->dma_len = len;
+ s->command_complete = 1;
+ if (s->waiting) {
+ if (s->waiting == LSI_WAIT_RESELECT || s->dbc == 0) {
+ lsi_resume_script(s);
+ } else {
+ lsi_do_dma(s, out);
+ }
+ }
+}
+
+static void lsi_do_command(LSIState *s)
+{
+ SCSIDevice *dev;
+ uint8_t buf[16];
+ uint32_t id;
+ int n;
+
+ trace_lsi_do_command(s->dbc);
+ if (s->dbc > 16)
+ s->dbc = 16;
+ pci_dma_read(PCI_DEVICE(s), s->dnad, buf, s->dbc);
+ s->sfbr = buf[0];
+ s->command_complete = 0;
+
+ id = (s->select_tag >> 8) & 0xf;
+ dev = scsi_device_find(&s->bus, 0, id, s->current_lun);
+ if (!dev) {
+ lsi_bad_selection(s, id);
+ return;
+ }
+
+ assert(s->current == NULL);
+ s->current = g_new0(lsi_request, 1);
+ s->current->tag = s->select_tag;
+ s->current->req = scsi_req_new(dev, s->current->tag, s->current_lun, buf,
+ s->dbc, s->current);
+
+ n = scsi_req_enqueue(s->current->req);
+ if (n) {
+ if (n > 0) {
+ lsi_set_phase(s, PHASE_DI);
+ } else if (n < 0) {
+ lsi_set_phase(s, PHASE_DO);
+ }
+ scsi_req_continue(s->current->req);
+ }
+ if (!s->command_complete) {
+ if (n) {
+ /* Command did not complete immediately so disconnect. */
+ lsi_add_msg_byte(s, 2); /* SAVE DATA POINTER */
+ lsi_add_msg_byte(s, 4); /* DISCONNECT */
+ /* wait data */
+ lsi_set_phase(s, PHASE_MI);
+ s->msg_action = LSI_MSG_ACTION_DISCONNECT;
+ lsi_queue_command(s);
+ } else {
+ /* wait command complete */
+ lsi_set_phase(s, PHASE_DI);
+ }
+ }
+}
+
+static void lsi_do_status(LSIState *s)
+{
+ uint8_t status;
+ trace_lsi_do_status(s->dbc, s->status);
+ if (s->dbc != 1) {
+ trace_lsi_do_status_error();
+ }
+ s->dbc = 1;
+ status = s->status;
+ s->sfbr = status;
+ pci_dma_write(PCI_DEVICE(s), s->dnad, &status, 1);
+ lsi_set_phase(s, PHASE_MI);
+ s->msg_action = LSI_MSG_ACTION_DISCONNECT;
+ lsi_add_msg_byte(s, 0); /* COMMAND COMPLETE */
+}
+
+static void lsi_do_msgin(LSIState *s)
+{
+ uint8_t len;
+ trace_lsi_do_msgin(s->dbc, s->msg_len);
+ s->sfbr = s->msg[0];
+ len = s->msg_len;
+ assert(len > 0 && len <= LSI_MAX_MSGIN_LEN);
+ if (len > s->dbc)
+ len = s->dbc;
+ pci_dma_write(PCI_DEVICE(s), s->dnad, s->msg, len);
+ /* Linux drivers rely on the last byte being in the SIDL. */
+ s->sidl = s->msg[len - 1];
+ s->msg_len -= len;
+ if (s->msg_len) {
+ memmove(s->msg, s->msg + len, s->msg_len);
+ } else {
+ /* ??? Check if ATN (not yet implemented) is asserted and maybe
+ switch to PHASE_MO. */
+ switch (s->msg_action) {
+ case LSI_MSG_ACTION_COMMAND:
+ lsi_set_phase(s, PHASE_CMD);
+ break;
+ case LSI_MSG_ACTION_DISCONNECT:
+ lsi_disconnect(s);
+ break;
+ case LSI_MSG_ACTION_DOUT:
+ lsi_set_phase(s, PHASE_DO);
+ break;
+ case LSI_MSG_ACTION_DIN:
+ lsi_set_phase(s, PHASE_DI);
+ break;
+ default:
+ abort();
+ }
+ }
+}
+
+/* Read the next byte during a MSGOUT phase. */
+static uint8_t lsi_get_msgbyte(LSIState *s)
+{
+ uint8_t data;
+ pci_dma_read(PCI_DEVICE(s), s->dnad, &data, 1);
+ s->dnad++;
+ s->dbc--;
+ return data;
+}
+
+/* Skip the next n bytes during a MSGOUT phase. */
+static void lsi_skip_msgbytes(LSIState *s, unsigned int n)
+{
+ s->dnad += n;
+ s->dbc -= n;
+}
+
+static void lsi_do_msgout(LSIState *s)
+{
+ uint8_t msg;
+ int len;
+ uint32_t current_tag;
+ lsi_request *current_req, *p, *p_next;
+
+ if (s->current) {
+ current_tag = s->current->tag;
+ current_req = s->current;
+ } else {
+ current_tag = s->select_tag;
+ current_req = lsi_find_by_tag(s, current_tag);
+ }
+
+ trace_lsi_do_msgout(s->dbc);
+ while (s->dbc) {
+ msg = lsi_get_msgbyte(s);
+ s->sfbr = msg;
+
+ switch (msg) {
+ case 0x04:
+ trace_lsi_do_msgout_disconnect();
+ lsi_disconnect(s);
+ break;
+ case 0x08:
+ trace_lsi_do_msgout_noop();
+ lsi_set_phase(s, PHASE_CMD);
+ break;
+ case 0x01:
+ len = lsi_get_msgbyte(s);
+ msg = lsi_get_msgbyte(s);
+ (void)len; /* avoid a warning about unused variable*/
+ trace_lsi_do_msgout_extended(msg, len);
+ switch (msg) {
+ case 1:
+ trace_lsi_do_msgout_ignored("SDTR");
+ lsi_skip_msgbytes(s, 2);
+ break;
+ case 3:
+ trace_lsi_do_msgout_ignored("WDTR");
+ lsi_skip_msgbytes(s, 1);
+ break;
+ case 4:
+ trace_lsi_do_msgout_ignored("PPR");
+ lsi_skip_msgbytes(s, 5);
+ break;
+ default:
+ goto bad;
+ }
+ break;
+ case 0x20: /* SIMPLE queue */
+ s->select_tag |= lsi_get_msgbyte(s) | LSI_TAG_VALID;
+ trace_lsi_do_msgout_simplequeue(s->select_tag & 0xff);
+ break;
+ case 0x21: /* HEAD of queue */
+ qemu_log_mask(LOG_UNIMP, "lsi_scsi: HEAD queue not implemented\n");
+ s->select_tag |= lsi_get_msgbyte(s) | LSI_TAG_VALID;
+ break;
+ case 0x22: /* ORDERED queue */
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: ORDERED queue not implemented\n");
+ s->select_tag |= lsi_get_msgbyte(s) | LSI_TAG_VALID;
+ break;
+ case 0x0d:
+ /* The ABORT TAG message clears the current I/O process only. */
+ trace_lsi_do_msgout_abort(current_tag);
+ if (current_req && current_req->req) {
+ scsi_req_cancel(current_req->req);
+ current_req = NULL;
+ }
+ lsi_disconnect(s);
+ break;
+ case 0x06:
+ case 0x0e:
+ case 0x0c:
+ /* The ABORT message clears all I/O processes for the selecting
+ initiator on the specified logical unit of the target. */
+ if (msg == 0x06) {
+ trace_lsi_do_msgout_abort(current_tag);
+ }
+ /* The CLEAR QUEUE message clears all I/O processes for all
+ initiators on the specified logical unit of the target. */
+ if (msg == 0x0e) {
+ trace_lsi_do_msgout_clearqueue(current_tag);
+ }
+ /* The BUS DEVICE RESET message clears all I/O processes for all
+ initiators on all logical units of the target. */
+ if (msg == 0x0c) {
+ trace_lsi_do_msgout_busdevicereset(current_tag);
+ }
+
+ /* clear the current I/O process */
+ if (s->current) {
+ scsi_req_cancel(s->current->req);
+ current_req = NULL;
+ }
+
+ /* As the current implemented devices scsi_disk and scsi_generic
+ only support one LUN, we don't need to keep track of LUNs.
+ Clearing I/O processes for other initiators could be possible
+ for scsi_generic by sending a SG_SCSI_RESET to the /dev/sgX
+ device, but this is currently not implemented (and seems not
+ to be really necessary). So let's simply clear all queued
+ commands for the current device: */
+ QTAILQ_FOREACH_SAFE(p, &s->queue, next, p_next) {
+ if ((p->tag & 0x0000ff00) == (current_tag & 0x0000ff00)) {
+ scsi_req_cancel(p->req);
+ }
+ }
+
+ lsi_disconnect(s);
+ break;
+ default:
+ if ((msg & 0x80) == 0) {
+ goto bad;
+ }
+ s->current_lun = msg & 7;
+ trace_lsi_do_msgout_select(s->current_lun);
+ lsi_set_phase(s, PHASE_CMD);
+ break;
+ }
+ }
+ return;
+bad:
+ qemu_log_mask(LOG_UNIMP, "Unimplemented message 0x%02x\n", msg);
+ lsi_set_phase(s, PHASE_MI);
+ lsi_add_msg_byte(s, 7); /* MESSAGE REJECT */
+ s->msg_action = LSI_MSG_ACTION_COMMAND;
+}
+
+#define LSI_BUF_SIZE 4096
+static void lsi_memcpy(LSIState *s, uint32_t dest, uint32_t src, int count)
+{
+ int n;
+ uint8_t buf[LSI_BUF_SIZE];
+
+ trace_lsi_memcpy(dest, src, count);
+ while (count) {
+ n = (count > LSI_BUF_SIZE) ? LSI_BUF_SIZE : count;
+ lsi_mem_read(s, src, buf, n);
+ lsi_mem_write(s, dest, buf, n);
+ src += n;
+ dest += n;
+ count -= n;
+ }
+}
+
+static void lsi_wait_reselect(LSIState *s)
+{
+ lsi_request *p;
+
+ trace_lsi_wait_reselect();
+
+ if (s->current) {
+ return;
+ }
+ p = get_pending_req(s);
+ if (p) {
+ lsi_reselect(s, p);
+ }
+ if (s->current == NULL) {
+ s->waiting = LSI_WAIT_RESELECT;
+ }
+}
+
+static void lsi_execute_script(LSIState *s)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ uint32_t insn;
+ uint32_t addr, addr_high;
+ int opcode;
+ int insn_processed = 0;
+
+ s->istat1 |= LSI_ISTAT1_SRUN;
+again:
+ if (++insn_processed > LSI_MAX_INSN) {
+ /* Some windows drivers make the device spin waiting for a memory
+ location to change. If we have been executed a lot of code then
+ assume this is the case and force an unexpected device disconnect.
+ This is apparently sufficient to beat the drivers into submission.
+ */
+ if (!(s->sien0 & LSI_SIST0_UDC)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "lsi_scsi: inf. loop with UDC masked");
+ }
+ lsi_script_scsi_interrupt(s, LSI_SIST0_UDC, 0);
+ lsi_disconnect(s);
+ trace_lsi_execute_script_stop();
+ return;
+ }
+ insn = read_dword(s, s->dsp);
+ if (!insn) {
+ /* If we receive an empty opcode increment the DSP by 4 bytes
+ instead of 8 and execute the next opcode at that location */
+ s->dsp += 4;
+ goto again;
+ }
+ addr = read_dword(s, s->dsp + 4);
+ addr_high = 0;
+ trace_lsi_execute_script(s->dsp, insn, addr);
+ s->dsps = addr;
+ s->dcmd = insn >> 24;
+ s->dsp += 8;
+ switch (insn >> 30) {
+ case 0: /* Block move. */
+ if (s->sist1 & LSI_SIST1_STO) {
+ trace_lsi_execute_script_blockmove_delayed();
+ lsi_stop_script(s);
+ break;
+ }
+ s->dbc = insn & 0xffffff;
+ s->rbc = s->dbc;
+ /* ??? Set ESA. */
+ s->ia = s->dsp - 8;
+ if (insn & (1 << 29)) {
+ /* Indirect addressing. */
+ addr = read_dword(s, addr);
+ } else if (insn & (1 << 28)) {
+ uint32_t buf[2];
+ int32_t offset;
+ /* Table indirect addressing. */
+
+ /* 32-bit Table indirect */
+ offset = sextract32(addr, 0, 24);
+ pci_dma_read(pci_dev, s->dsa + offset, buf, 8);
+ /* byte count is stored in bits 0:23 only */
+ s->dbc = cpu_to_le32(buf[0]) & 0xffffff;
+ s->rbc = s->dbc;
+ addr = cpu_to_le32(buf[1]);
+
+ /* 40-bit DMA, upper addr bits [39:32] stored in first DWORD of
+ * table, bits [31:24] */
+ if (lsi_dma_40bit(s))
+ addr_high = cpu_to_le32(buf[0]) >> 24;
+ else if (lsi_dma_ti64bit(s)) {
+ int selector = (cpu_to_le32(buf[0]) >> 24) & 0x1f;
+ switch (selector) {
+ case 0 ... 0x0f:
+ /* offset index into scratch registers since
+ * TI64 mode can use registers C to R */
+ addr_high = s->scratch[2 + selector];
+ break;
+ case 0x10:
+ addr_high = s->mmrs;
+ break;
+ case 0x11:
+ addr_high = s->mmws;
+ break;
+ case 0x12:
+ addr_high = s->sfs;
+ break;
+ case 0x13:
+ addr_high = s->drs;
+ break;
+ case 0x14:
+ addr_high = s->sbms;
+ break;
+ case 0x15:
+ addr_high = s->dbms;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "lsi_scsi: Illegal selector specified (0x%x > 0x15) "
+ "for 64-bit DMA block move", selector);
+ break;
+ }
+ }
+ } else if (lsi_dma_64bit(s)) {
+ /* fetch a 3rd dword if 64-bit direct move is enabled and
+ only if we're not doing table indirect or indirect addressing */
+ s->dbms = read_dword(s, s->dsp);
+ s->dsp += 4;
+ s->ia = s->dsp - 12;
+ }
+ if ((s->sstat1 & PHASE_MASK) != ((insn >> 24) & 7)) {
+ trace_lsi_execute_script_blockmove_badphase(
+ scsi_phase_name(s->sstat1),
+ scsi_phase_name(insn >> 24));
+ lsi_script_scsi_interrupt(s, LSI_SIST0_MA, 0);
+ break;
+ }
+ s->dnad = addr;
+ s->dnad64 = addr_high;
+ switch (s->sstat1 & 0x7) {
+ case PHASE_DO:
+ s->waiting = LSI_DMA_SCRIPTS;
+ lsi_do_dma(s, 1);
+ if (s->waiting)
+ s->waiting = LSI_DMA_IN_PROGRESS;
+ break;
+ case PHASE_DI:
+ s->waiting = LSI_DMA_SCRIPTS;
+ lsi_do_dma(s, 0);
+ if (s->waiting)
+ s->waiting = LSI_DMA_IN_PROGRESS;
+ break;
+ case PHASE_CMD:
+ lsi_do_command(s);
+ break;
+ case PHASE_ST:
+ lsi_do_status(s);
+ break;
+ case PHASE_MO:
+ lsi_do_msgout(s);
+ break;
+ case PHASE_MI:
+ lsi_do_msgin(s);
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "lsi_scsi: Unimplemented phase %s\n",
+ scsi_phase_name(s->sstat1));
+ }
+ s->dfifo = s->dbc & 0xff;
+ s->ctest5 = (s->ctest5 & 0xfc) | ((s->dbc >> 8) & 3);
+ s->sbc = s->dbc;
+ s->rbc -= s->dbc;
+ s->ua = addr + s->dbc;
+ break;
+
+ case 1: /* IO or Read/Write instruction. */
+ opcode = (insn >> 27) & 7;
+ if (opcode < 5) {
+ uint32_t id;
+
+ if (insn & (1 << 25)) {
+ id = read_dword(s, s->dsa + sextract32(insn, 0, 24));
+ } else {
+ id = insn;
+ }
+ id = (id >> 16) & 0xf;
+ if (insn & (1 << 26)) {
+ addr = s->dsp + sextract32(addr, 0, 24);
+ }
+ s->dnad = addr;
+ switch (opcode) {
+ case 0: /* Select */
+ s->sdid = id;
+ if (s->scntl1 & LSI_SCNTL1_CON) {
+ trace_lsi_execute_script_io_alreadyreselected();
+ s->dsp = s->dnad;
+ break;
+ }
+ s->sstat0 |= LSI_SSTAT0_WOA;
+ s->scntl1 &= ~LSI_SCNTL1_IARB;
+ if (!scsi_device_find(&s->bus, 0, id, 0)) {
+ lsi_bad_selection(s, id);
+ break;
+ }
+ trace_lsi_execute_script_io_selected(id,
+ insn & (1 << 3) ? " ATN" : "");
+ /* ??? Linux drivers compain when this is set. Maybe
+ it only applies in low-level mode (unimplemented).
+ lsi_script_scsi_interrupt(s, LSI_SIST0_CMP, 0); */
+ s->select_tag = id << 8;
+ s->scntl1 |= LSI_SCNTL1_CON;
+ if (insn & (1 << 3)) {
+ s->socl |= LSI_SOCL_ATN;
+ s->sbcl |= LSI_SBCL_ATN;
+ }
+ s->sbcl |= LSI_SBCL_BSY;
+ lsi_set_phase(s, PHASE_MO);
+ s->waiting = LSI_NOWAIT;
+ break;
+ case 1: /* Disconnect */
+ trace_lsi_execute_script_io_disconnect();
+ s->scntl1 &= ~LSI_SCNTL1_CON;
+ /* FIXME: this is not entirely correct; the target need not ask
+ * for reselection until it has to send data, while here we force a
+ * reselection as soon as the bus is free. The correct flow would
+ * reselect before lsi_transfer_data and disconnect as soon as
+ * DMA ends.
+ */
+ if (!s->current) {
+ lsi_request *p = get_pending_req(s);
+ if (p) {
+ lsi_reselect(s, p);
+ }
+ }
+ break;
+ case 2: /* Wait Reselect */
+ if (s->istat0 & LSI_ISTAT0_SIGP) {
+ s->dsp = s->dnad;
+ } else if (!lsi_irq_on_rsl(s)) {
+ lsi_wait_reselect(s);
+ }
+ break;
+ case 3: /* Set */
+ trace_lsi_execute_script_io_set(
+ insn & (1 << 3) ? " ATN" : "",
+ insn & (1 << 6) ? " ACK" : "",
+ insn & (1 << 9) ? " TM" : "",
+ insn & (1 << 10) ? " CC" : "");
+ if (insn & (1 << 3)) {
+ s->socl |= LSI_SOCL_ATN;
+ s->sbcl |= LSI_SBCL_ATN;
+ lsi_set_phase(s, PHASE_MO);
+ }
+
+ if (insn & (1 << 6)) {
+ s->sbcl |= LSI_SBCL_ACK;
+ }
+
+ if (insn & (1 << 9)) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: Target mode not implemented\n");
+ }
+ if (insn & (1 << 10))
+ s->carry = 1;
+ break;
+ case 4: /* Clear */
+ trace_lsi_execute_script_io_clear(
+ insn & (1 << 3) ? " ATN" : "",
+ insn & (1 << 6) ? " ACK" : "",
+ insn & (1 << 9) ? " TM" : "",
+ insn & (1 << 10) ? " CC" : "");
+ if (insn & (1 << 3)) {
+ s->socl &= ~LSI_SOCL_ATN;
+ s->sbcl &= ~LSI_SBCL_ATN;
+ }
+
+ if (insn & (1 << 6)) {
+ s->sbcl &= ~LSI_SBCL_ACK;
+ }
+
+ if (insn & (1 << 10))
+ s->carry = 0;
+ break;
+ }
+ } else {
+ uint8_t op0;
+ uint8_t op1;
+ uint8_t data8;
+ int reg;
+ int operator;
+
+ static const char *opcode_names[3] =
+ {"Write", "Read", "Read-Modify-Write"};
+ static const char *operator_names[8] =
+ {"MOV", "SHL", "OR", "XOR", "AND", "SHR", "ADD", "ADC"};
+
+ reg = ((insn >> 16) & 0x7f) | (insn & 0x80);
+ data8 = (insn >> 8) & 0xff;
+ opcode = (insn >> 27) & 7;
+ operator = (insn >> 24) & 7;
+ trace_lsi_execute_script_io_opcode(
+ opcode_names[opcode - 5], reg,
+ operator_names[operator], data8, s->sfbr,
+ (insn & (1 << 23)) ? " SFBR" : "");
+ op0 = op1 = 0;
+ switch (opcode) {
+ case 5: /* From SFBR */
+ op0 = s->sfbr;
+ op1 = data8;
+ break;
+ case 6: /* To SFBR */
+ if (operator)
+ op0 = lsi_reg_readb(s, reg);
+ op1 = data8;
+ break;
+ case 7: /* Read-modify-write */
+ if (operator)
+ op0 = lsi_reg_readb(s, reg);
+ if (insn & (1 << 23)) {
+ op1 = s->sfbr;
+ } else {
+ op1 = data8;
+ }
+ break;
+ }
+
+ switch (operator) {
+ case 0: /* move */
+ op0 = op1;
+ break;
+ case 1: /* Shift left */
+ op1 = op0 >> 7;
+ op0 = (op0 << 1) | s->carry;
+ s->carry = op1;
+ break;
+ case 2: /* OR */
+ op0 |= op1;
+ break;
+ case 3: /* XOR */
+ op0 ^= op1;
+ break;
+ case 4: /* AND */
+ op0 &= op1;
+ break;
+ case 5: /* SHR */
+ op1 = op0 & 1;
+ op0 = (op0 >> 1) | (s->carry << 7);
+ s->carry = op1;
+ break;
+ case 6: /* ADD */
+ op0 += op1;
+ s->carry = op0 < op1;
+ break;
+ case 7: /* ADC */
+ op0 += op1 + s->carry;
+ if (s->carry)
+ s->carry = op0 <= op1;
+ else
+ s->carry = op0 < op1;
+ break;
+ }
+
+ switch (opcode) {
+ case 5: /* From SFBR */
+ case 7: /* Read-modify-write */
+ lsi_reg_writeb(s, reg, op0);
+ break;
+ case 6: /* To SFBR */
+ s->sfbr = op0;
+ break;
+ }
+ }
+ break;
+
+ case 2: /* Transfer Control. */
+ {
+ int cond;
+ int jmp;
+
+ if ((insn & 0x002e0000) == 0) {
+ trace_lsi_execute_script_tc_nop();
+ break;
+ }
+ if (s->sist1 & LSI_SIST1_STO) {
+ trace_lsi_execute_script_tc_delayedselect_timeout();
+ lsi_stop_script(s);
+ break;
+ }
+ cond = jmp = (insn & (1 << 19)) != 0;
+ if (cond == jmp && (insn & (1 << 21))) {
+ trace_lsi_execute_script_tc_compc(s->carry == jmp);
+ cond = s->carry != 0;
+ }
+ if (cond == jmp && (insn & (1 << 17))) {
+ trace_lsi_execute_script_tc_compp(scsi_phase_name(s->sstat1),
+ jmp ? '=' : '!', scsi_phase_name(insn >> 24));
+ cond = (s->sstat1 & PHASE_MASK) == ((insn >> 24) & 7);
+ }
+ if (cond == jmp && (insn & (1 << 18))) {
+ uint8_t mask;
+
+ mask = (~insn >> 8) & 0xff;
+ trace_lsi_execute_script_tc_compd(
+ s->sfbr, mask, jmp ? '=' : '!', insn & mask);
+ cond = (s->sfbr & mask) == (insn & mask);
+ }
+ if (cond == jmp) {
+ if (insn & (1 << 23)) {
+ /* Relative address. */
+ addr = s->dsp + sextract32(addr, 0, 24);
+ }
+ switch ((insn >> 27) & 7) {
+ case 0: /* Jump */
+ trace_lsi_execute_script_tc_jump(addr);
+ s->adder = addr;
+ s->dsp = addr;
+ break;
+ case 1: /* Call */
+ trace_lsi_execute_script_tc_call(addr);
+ s->temp = s->dsp;
+ s->dsp = addr;
+ break;
+ case 2: /* Return */
+ trace_lsi_execute_script_tc_return(s->temp);
+ s->dsp = s->temp;
+ break;
+ case 3: /* Interrupt */
+ trace_lsi_execute_script_tc_interrupt(s->dsps);
+ if ((insn & (1 << 20)) != 0) {
+ s->istat0 |= LSI_ISTAT0_INTF;
+ lsi_update_irq(s);
+ } else {
+ lsi_script_dma_interrupt(s, LSI_DSTAT_SIR);
+ }
+ break;
+ default:
+ trace_lsi_execute_script_tc_illegal();
+ lsi_script_dma_interrupt(s, LSI_DSTAT_IID);
+ break;
+ }
+ } else {
+ trace_lsi_execute_script_tc_cc_failed();
+ }
+ }
+ break;
+
+ case 3:
+ if ((insn & (1 << 29)) == 0) {
+ /* Memory move. */
+ uint32_t dest;
+ /* ??? The docs imply the destination address is loaded into
+ the TEMP register. However the Linux drivers rely on
+ the value being presrved. */
+ dest = read_dword(s, s->dsp);
+ s->dsp += 4;
+ lsi_memcpy(s, dest, addr, insn & 0xffffff);
+ } else {
+ uint8_t data[7];
+ int reg;
+ int n;
+ int i;
+
+ if (insn & (1 << 28)) {
+ addr = s->dsa + sextract32(addr, 0, 24);
+ }
+ n = (insn & 7);
+ reg = (insn >> 16) & 0xff;
+ if (insn & (1 << 24)) {
+ pci_dma_read(pci_dev, addr, data, n);
+ trace_lsi_execute_script_mm_load(reg, n, addr, *(int *)data);
+ for (i = 0; i < n; i++) {
+ lsi_reg_writeb(s, reg + i, data[i]);
+ }
+ } else {
+ trace_lsi_execute_script_mm_store(reg, n, addr);
+ for (i = 0; i < n; i++) {
+ data[i] = lsi_reg_readb(s, reg + i);
+ }
+ pci_dma_write(pci_dev, addr, data, n);
+ }
+ }
+ }
+ if (s->istat1 & LSI_ISTAT1_SRUN && s->waiting == LSI_NOWAIT) {
+ if (s->dcntl & LSI_DCNTL_SSM) {
+ lsi_script_dma_interrupt(s, LSI_DSTAT_SSI);
+ } else {
+ goto again;
+ }
+ }
+ trace_lsi_execute_script_stop();
+}
+
+static uint8_t lsi_reg_readb(LSIState *s, int offset)
+{
+ uint8_t ret;
+
+#define CASE_GET_REG24(name, addr) \
+ case addr: ret = s->name & 0xff; break; \
+ case addr + 1: ret = (s->name >> 8) & 0xff; break; \
+ case addr + 2: ret = (s->name >> 16) & 0xff; break;
+
+#define CASE_GET_REG32(name, addr) \
+ case addr: ret = s->name & 0xff; break; \
+ case addr + 1: ret = (s->name >> 8) & 0xff; break; \
+ case addr + 2: ret = (s->name >> 16) & 0xff; break; \
+ case addr + 3: ret = (s->name >> 24) & 0xff; break;
+
+ switch (offset) {
+ case 0x00: /* SCNTL0 */
+ ret = s->scntl0;
+ break;
+ case 0x01: /* SCNTL1 */
+ ret = s->scntl1;
+ break;
+ case 0x02: /* SCNTL2 */
+ ret = s->scntl2;
+ break;
+ case 0x03: /* SCNTL3 */
+ ret = s->scntl3;
+ break;
+ case 0x04: /* SCID */
+ ret = s->scid;
+ break;
+ case 0x05: /* SXFER */
+ ret = s->sxfer;
+ break;
+ case 0x06: /* SDID */
+ ret = s->sdid;
+ break;
+ case 0x07: /* GPREG0 */
+ ret = 0x7f;
+ break;
+ case 0x08: /* Revision ID */
+ ret = 0x00;
+ break;
+ case 0x09: /* SOCL */
+ ret = s->socl;
+ break;
+ case 0xa: /* SSID */
+ ret = s->ssid;
+ break;
+ case 0xb: /* SBCL */
+ ret = s->sbcl;
+ break;
+ case 0xc: /* DSTAT */
+ ret = s->dstat | LSI_DSTAT_DFE;
+ if ((s->istat0 & LSI_ISTAT0_INTF) == 0)
+ s->dstat = 0;
+ lsi_update_irq(s);
+ break;
+ case 0x0d: /* SSTAT0 */
+ ret = s->sstat0;
+ break;
+ case 0x0e: /* SSTAT1 */
+ ret = s->sstat1;
+ break;
+ case 0x0f: /* SSTAT2 */
+ ret = s->scntl1 & LSI_SCNTL1_CON ? 0 : 2;
+ break;
+ CASE_GET_REG32(dsa, 0x10)
+ case 0x14: /* ISTAT0 */
+ ret = s->istat0;
+ break;
+ case 0x15: /* ISTAT1 */
+ ret = s->istat1;
+ break;
+ case 0x16: /* MBOX0 */
+ ret = s->mbox0;
+ break;
+ case 0x17: /* MBOX1 */
+ ret = s->mbox1;
+ break;
+ case 0x18: /* CTEST0 */
+ ret = 0xff;
+ break;
+ case 0x19: /* CTEST1 */
+ ret = 0;
+ break;
+ case 0x1a: /* CTEST2 */
+ ret = s->ctest2 | LSI_CTEST2_DACK | LSI_CTEST2_CM;
+ if (s->istat0 & LSI_ISTAT0_SIGP) {
+ s->istat0 &= ~LSI_ISTAT0_SIGP;
+ ret |= LSI_CTEST2_SIGP;
+ }
+ break;
+ case 0x1b: /* CTEST3 */
+ ret = s->ctest3;
+ break;
+ CASE_GET_REG32(temp, 0x1c)
+ case 0x20: /* DFIFO */
+ ret = s->dfifo;
+ break;
+ case 0x21: /* CTEST4 */
+ ret = s->ctest4;
+ break;
+ case 0x22: /* CTEST5 */
+ ret = s->ctest5;
+ break;
+ case 0x23: /* CTEST6 */
+ ret = 0;
+ break;
+ CASE_GET_REG24(dbc, 0x24)
+ case 0x27: /* DCMD */
+ ret = s->dcmd;
+ break;
+ CASE_GET_REG32(dnad, 0x28)
+ CASE_GET_REG32(dsp, 0x2c)
+ CASE_GET_REG32(dsps, 0x30)
+ CASE_GET_REG32(scratch[0], 0x34)
+ case 0x38: /* DMODE */
+ ret = s->dmode;
+ break;
+ case 0x39: /* DIEN */
+ ret = s->dien;
+ break;
+ case 0x3a: /* SBR */
+ ret = s->sbr;
+ break;
+ case 0x3b: /* DCNTL */
+ ret = s->dcntl;
+ break;
+ /* ADDER Output (Debug of relative jump address) */
+ CASE_GET_REG32(adder, 0x3c)
+ case 0x40: /* SIEN0 */
+ ret = s->sien0;
+ break;
+ case 0x41: /* SIEN1 */
+ ret = s->sien1;
+ break;
+ case 0x42: /* SIST0 */
+ ret = s->sist0;
+ s->sist0 = 0;
+ lsi_update_irq(s);
+ break;
+ case 0x43: /* SIST1 */
+ ret = s->sist1;
+ s->sist1 = 0;
+ lsi_update_irq(s);
+ break;
+ case 0x46: /* MACNTL */
+ ret = 0x0f;
+ break;
+ case 0x47: /* GPCNTL0 */
+ ret = 0x0f;
+ break;
+ case 0x48: /* STIME0 */
+ ret = s->stime0;
+ break;
+ case 0x4a: /* RESPID0 */
+ ret = s->respid0;
+ break;
+ case 0x4b: /* RESPID1 */
+ ret = s->respid1;
+ break;
+ case 0x4d: /* STEST1 */
+ ret = s->stest1;
+ break;
+ case 0x4e: /* STEST2 */
+ ret = s->stest2;
+ break;
+ case 0x4f: /* STEST3 */
+ ret = s->stest3;
+ break;
+ case 0x50: /* SIDL */
+ /* This is needed by the linux drivers. We currently only update it
+ during the MSG IN phase. */
+ ret = s->sidl;
+ break;
+ case 0x52: /* STEST4 */
+ ret = 0xe0;
+ break;
+ case 0x56: /* CCNTL0 */
+ ret = s->ccntl0;
+ break;
+ case 0x57: /* CCNTL1 */
+ ret = s->ccntl1;
+ break;
+ case 0x58: /* SBDL */
+ /* Some drivers peek at the data bus during the MSG IN phase. */
+ if ((s->sstat1 & PHASE_MASK) == PHASE_MI) {
+ assert(s->msg_len > 0);
+ return s->msg[0];
+ }
+ ret = 0;
+ break;
+ case 0x59: /* SBDL high */
+ ret = 0;
+ break;
+ CASE_GET_REG32(mmrs, 0xa0)
+ CASE_GET_REG32(mmws, 0xa4)
+ CASE_GET_REG32(sfs, 0xa8)
+ CASE_GET_REG32(drs, 0xac)
+ CASE_GET_REG32(sbms, 0xb0)
+ CASE_GET_REG32(dbms, 0xb4)
+ CASE_GET_REG32(dnad64, 0xb8)
+ CASE_GET_REG32(pmjad1, 0xc0)
+ CASE_GET_REG32(pmjad2, 0xc4)
+ CASE_GET_REG32(rbc, 0xc8)
+ CASE_GET_REG32(ua, 0xcc)
+ CASE_GET_REG32(ia, 0xd4)
+ CASE_GET_REG32(sbc, 0xd8)
+ CASE_GET_REG32(csbc, 0xdc)
+ case 0x5c ... 0x9f:
+ {
+ int n;
+ int shift;
+ n = (offset - 0x58) >> 2;
+ shift = (offset & 3) * 8;
+ ret = (s->scratch[n] >> shift) & 0xff;
+ break;
+ }
+ default:
+ {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "lsi_scsi: invalid read from reg %s %x\n",
+ offset < ARRAY_SIZE(names) ? names[offset] : "???",
+ offset);
+ ret = 0xff;
+ break;
+ }
+ }
+#undef CASE_GET_REG24
+#undef CASE_GET_REG32
+
+ trace_lsi_reg_read(offset < ARRAY_SIZE(names) ? names[offset] : "???",
+ offset, ret);
+
+ return ret;
+}
+
+static void lsi_reg_writeb(LSIState *s, int offset, uint8_t val)
+{
+#define CASE_SET_REG24(name, addr) \
+ case addr : s->name &= 0xffffff00; s->name |= val; break; \
+ case addr + 1: s->name &= 0xffff00ff; s->name |= val << 8; break; \
+ case addr + 2: s->name &= 0xff00ffff; s->name |= val << 16; break;
+
+#define CASE_SET_REG32(name, addr) \
+ case addr : s->name &= 0xffffff00; s->name |= val; break; \
+ case addr + 1: s->name &= 0xffff00ff; s->name |= val << 8; break; \
+ case addr + 2: s->name &= 0xff00ffff; s->name |= val << 16; break; \
+ case addr + 3: s->name &= 0x00ffffff; s->name |= val << 24; break;
+
+ trace_lsi_reg_write(offset < ARRAY_SIZE(names) ? names[offset] : "???",
+ offset, val);
+
+ switch (offset) {
+ case 0x00: /* SCNTL0 */
+ s->scntl0 = val;
+ if (val & LSI_SCNTL0_START) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: Start sequence not implemented\n");
+ }
+ break;
+ case 0x01: /* SCNTL1 */
+ s->scntl1 = val & ~LSI_SCNTL1_SST;
+ if (val & LSI_SCNTL1_IARB) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: Immediate Arbritration not implemented\n");
+ }
+ if (val & LSI_SCNTL1_RST) {
+ if (!(s->sstat0 & LSI_SSTAT0_RST)) {
+ bus_cold_reset(BUS(&s->bus));
+ s->sstat0 |= LSI_SSTAT0_RST;
+ lsi_script_scsi_interrupt(s, LSI_SIST0_RST, 0);
+ }
+ } else {
+ s->sstat0 &= ~LSI_SSTAT0_RST;
+ }
+ break;
+ case 0x02: /* SCNTL2 */
+ val &= ~(LSI_SCNTL2_WSR | LSI_SCNTL2_WSS);
+ s->scntl2 = val;
+ break;
+ case 0x03: /* SCNTL3 */
+ s->scntl3 = val;
+ break;
+ case 0x04: /* SCID */
+ s->scid = val;
+ break;
+ case 0x05: /* SXFER */
+ s->sxfer = val;
+ break;
+ case 0x06: /* SDID */
+ if ((s->ssid & 0x80) && (val & 0xf) != (s->ssid & 0xf)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "lsi_scsi: Destination ID does not match SSID\n");
+ }
+ s->sdid = val & 0xf;
+ break;
+ case 0x07: /* GPREG0 */
+ break;
+ case 0x08: /* SFBR */
+ /* The CPU is not allowed to write to this register. However the
+ SCRIPTS register move instructions are. */
+ s->sfbr = val;
+ break;
+ case 0x0a: case 0x0b:
+ /* Openserver writes to these readonly registers on startup */
+ return;
+ case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+ /* Linux writes to these readonly registers on startup. */
+ return;
+ CASE_SET_REG32(dsa, 0x10)
+ case 0x14: /* ISTAT0 */
+ s->istat0 = (s->istat0 & 0x0f) | (val & 0xf0);
+ if (val & LSI_ISTAT0_ABRT) {
+ lsi_script_dma_interrupt(s, LSI_DSTAT_ABRT);
+ }
+ if (val & LSI_ISTAT0_INTF) {
+ s->istat0 &= ~LSI_ISTAT0_INTF;
+ lsi_update_irq(s);
+ }
+ if (s->waiting == LSI_WAIT_RESELECT && val & LSI_ISTAT0_SIGP) {
+ trace_lsi_awoken();
+ s->waiting = LSI_NOWAIT;
+ s->dsp = s->dnad;
+ lsi_execute_script(s);
+ }
+ if (val & LSI_ISTAT0_SRST) {
+ device_cold_reset(DEVICE(s));
+ }
+ break;
+ case 0x16: /* MBOX0 */
+ s->mbox0 = val;
+ break;
+ case 0x17: /* MBOX1 */
+ s->mbox1 = val;
+ break;
+ case 0x18: /* CTEST0 */
+ /* nothing to do */
+ break;
+ case 0x1a: /* CTEST2 */
+ s->ctest2 = val & LSI_CTEST2_PCICIE;
+ break;
+ case 0x1b: /* CTEST3 */
+ s->ctest3 = val & 0x0f;
+ break;
+ CASE_SET_REG32(temp, 0x1c)
+ case 0x21: /* CTEST4 */
+ if (val & 7) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: Unimplemented CTEST4-FBL 0x%x\n", val);
+ }
+ s->ctest4 = val;
+ break;
+ case 0x22: /* CTEST5 */
+ if (val & (LSI_CTEST5_ADCK | LSI_CTEST5_BBCK)) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: CTEST5 DMA increment not implemented\n");
+ }
+ s->ctest5 = val;
+ break;
+ CASE_SET_REG24(dbc, 0x24)
+ CASE_SET_REG32(dnad, 0x28)
+ case 0x2c: /* DSP[0:7] */
+ s->dsp &= 0xffffff00;
+ s->dsp |= val;
+ break;
+ case 0x2d: /* DSP[8:15] */
+ s->dsp &= 0xffff00ff;
+ s->dsp |= val << 8;
+ break;
+ case 0x2e: /* DSP[16:23] */
+ s->dsp &= 0xff00ffff;
+ s->dsp |= val << 16;
+ break;
+ case 0x2f: /* DSP[24:31] */
+ s->dsp &= 0x00ffffff;
+ s->dsp |= val << 24;
+ /*
+ * FIXME: if s->waiting != LSI_NOWAIT, this will only execute one
+ * instruction. Is this correct?
+ */
+ if ((s->dmode & LSI_DMODE_MAN) == 0
+ && (s->istat1 & LSI_ISTAT1_SRUN) == 0)
+ lsi_execute_script(s);
+ break;
+ CASE_SET_REG32(dsps, 0x30)
+ CASE_SET_REG32(scratch[0], 0x34)
+ case 0x38: /* DMODE */
+ s->dmode = val;
+ break;
+ case 0x39: /* DIEN */
+ s->dien = val;
+ lsi_update_irq(s);
+ break;
+ case 0x3a: /* SBR */
+ s->sbr = val;
+ break;
+ case 0x3b: /* DCNTL */
+ s->dcntl = val & ~(LSI_DCNTL_PFF | LSI_DCNTL_STD);
+ /*
+ * FIXME: if s->waiting != LSI_NOWAIT, this will only execute one
+ * instruction. Is this correct?
+ */
+ if ((val & LSI_DCNTL_STD) && (s->istat1 & LSI_ISTAT1_SRUN) == 0)
+ lsi_execute_script(s);
+ break;
+ case 0x40: /* SIEN0 */
+ s->sien0 = val;
+ lsi_update_irq(s);
+ break;
+ case 0x41: /* SIEN1 */
+ s->sien1 = val;
+ lsi_update_irq(s);
+ break;
+ case 0x47: /* GPCNTL0 */
+ break;
+ case 0x48: /* STIME0 */
+ s->stime0 = val;
+ break;
+ case 0x49: /* STIME1 */
+ if (val & 0xf) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: General purpose timer not implemented\n");
+ /* ??? Raising the interrupt immediately seems to be sufficient
+ to keep the FreeBSD driver happy. */
+ lsi_script_scsi_interrupt(s, 0, LSI_SIST1_GEN);
+ }
+ break;
+ case 0x4a: /* RESPID0 */
+ s->respid0 = val;
+ break;
+ case 0x4b: /* RESPID1 */
+ s->respid1 = val;
+ break;
+ case 0x4d: /* STEST1 */
+ s->stest1 = val;
+ break;
+ case 0x4e: /* STEST2 */
+ if (val & 1) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: Low level mode not implemented\n");
+ }
+ s->stest2 = val;
+ break;
+ case 0x4f: /* STEST3 */
+ if (val & 0x41) {
+ qemu_log_mask(LOG_UNIMP,
+ "lsi_scsi: SCSI FIFO test mode not implemented\n");
+ }
+ s->stest3 = val;
+ break;
+ case 0x56: /* CCNTL0 */
+ s->ccntl0 = val;
+ break;
+ case 0x57: /* CCNTL1 */
+ s->ccntl1 = val;
+ break;
+ CASE_SET_REG32(mmrs, 0xa0)
+ CASE_SET_REG32(mmws, 0xa4)
+ CASE_SET_REG32(sfs, 0xa8)
+ CASE_SET_REG32(drs, 0xac)
+ CASE_SET_REG32(sbms, 0xb0)
+ CASE_SET_REG32(dbms, 0xb4)
+ CASE_SET_REG32(dnad64, 0xb8)
+ CASE_SET_REG32(pmjad1, 0xc0)
+ CASE_SET_REG32(pmjad2, 0xc4)
+ CASE_SET_REG32(rbc, 0xc8)
+ CASE_SET_REG32(ua, 0xcc)
+ CASE_SET_REG32(ia, 0xd4)
+ CASE_SET_REG32(sbc, 0xd8)
+ CASE_SET_REG32(csbc, 0xdc)
+ default:
+ if (offset >= 0x5c && offset < 0xa0) {
+ int n;
+ int shift;
+ n = (offset - 0x58) >> 2;
+ shift = (offset & 3) * 8;
+ s->scratch[n] = deposit32(s->scratch[n], shift, 8, val);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "lsi_scsi: invalid write to reg %s %x (0x%02x)\n",
+ offset < ARRAY_SIZE(names) ? names[offset] : "???",
+ offset, val);
+ }
+ }
+#undef CASE_SET_REG24
+#undef CASE_SET_REG32
+}
+
+static void lsi_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LSIState *s = opaque;
+
+ lsi_reg_writeb(s, addr & 0xff, val);
+}
+
+static uint64_t lsi_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LSIState *s = opaque;
+ return lsi_reg_readb(s, addr & 0xff);
+}
+
+static const MemoryRegionOps lsi_mmio_ops = {
+ .read = lsi_mmio_read,
+ .write = lsi_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void lsi_ram_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LSIState *s = opaque;
+ stn_le_p(s->script_ram + addr, size, val);
+}
+
+static uint64_t lsi_ram_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LSIState *s = opaque;
+ return ldn_le_p(s->script_ram + addr, size);
+}
+
+static const MemoryRegionOps lsi_ram_ops = {
+ .read = lsi_ram_read,
+ .write = lsi_ram_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t lsi_io_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LSIState *s = opaque;
+ return lsi_reg_readb(s, addr & 0xff);
+}
+
+static void lsi_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ LSIState *s = opaque;
+ lsi_reg_writeb(s, addr & 0xff, val);
+}
+
+static const MemoryRegionOps lsi_io_ops = {
+ .read = lsi_io_read,
+ .write = lsi_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void lsi_scsi_reset(DeviceState *dev)
+{
+ LSIState *s = LSI53C895A(dev);
+
+ lsi_soft_reset(s);
+}
+
+static int lsi_pre_save(void *opaque)
+{
+ LSIState *s = opaque;
+
+ if (s->current) {
+ assert(s->current->dma_buf == NULL);
+ assert(s->current->dma_len == 0);
+ }
+ assert(QTAILQ_EMPTY(&s->queue));
+
+ return 0;
+}
+
+static int lsi_post_load(void *opaque, int version_id)
+{
+ LSIState *s = opaque;
+
+ if (s->msg_len < 0 || s->msg_len > LSI_MAX_MSGIN_LEN) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lsi_scsi = {
+ .name = "lsiscsi",
+ .version_id = 1,
+ .minimum_version_id = 0,
+ .pre_save = lsi_pre_save,
+ .post_load = lsi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, LSIState),
+
+ VMSTATE_INT32(carry, LSIState),
+ VMSTATE_INT32(status, LSIState),
+ VMSTATE_INT32(msg_action, LSIState),
+ VMSTATE_INT32(msg_len, LSIState),
+ VMSTATE_BUFFER(msg, LSIState),
+ VMSTATE_INT32(waiting, LSIState),
+
+ VMSTATE_UINT32(dsa, LSIState),
+ VMSTATE_UINT32(temp, LSIState),
+ VMSTATE_UINT32(dnad, LSIState),
+ VMSTATE_UINT32(dbc, LSIState),
+ VMSTATE_UINT8(istat0, LSIState),
+ VMSTATE_UINT8(istat1, LSIState),
+ VMSTATE_UINT8(dcmd, LSIState),
+ VMSTATE_UINT8(dstat, LSIState),
+ VMSTATE_UINT8(dien, LSIState),
+ VMSTATE_UINT8(sist0, LSIState),
+ VMSTATE_UINT8(sist1, LSIState),
+ VMSTATE_UINT8(sien0, LSIState),
+ VMSTATE_UINT8(sien1, LSIState),
+ VMSTATE_UINT8(mbox0, LSIState),
+ VMSTATE_UINT8(mbox1, LSIState),
+ VMSTATE_UINT8(dfifo, LSIState),
+ VMSTATE_UINT8(ctest2, LSIState),
+ VMSTATE_UINT8(ctest3, LSIState),
+ VMSTATE_UINT8(ctest4, LSIState),
+ VMSTATE_UINT8(ctest5, LSIState),
+ VMSTATE_UINT8(ccntl0, LSIState),
+ VMSTATE_UINT8(ccntl1, LSIState),
+ VMSTATE_UINT32(dsp, LSIState),
+ VMSTATE_UINT32(dsps, LSIState),
+ VMSTATE_UINT8(dmode, LSIState),
+ VMSTATE_UINT8(dcntl, LSIState),
+ VMSTATE_UINT8(scntl0, LSIState),
+ VMSTATE_UINT8(scntl1, LSIState),
+ VMSTATE_UINT8(scntl2, LSIState),
+ VMSTATE_UINT8(scntl3, LSIState),
+ VMSTATE_UINT8(sstat0, LSIState),
+ VMSTATE_UINT8(sstat1, LSIState),
+ VMSTATE_UINT8(scid, LSIState),
+ VMSTATE_UINT8(sxfer, LSIState),
+ VMSTATE_UINT8(socl, LSIState),
+ VMSTATE_UINT8(sdid, LSIState),
+ VMSTATE_UINT8(ssid, LSIState),
+ VMSTATE_UINT8(sfbr, LSIState),
+ VMSTATE_UINT8(stest1, LSIState),
+ VMSTATE_UINT8(stest2, LSIState),
+ VMSTATE_UINT8(stest3, LSIState),
+ VMSTATE_UINT8(sidl, LSIState),
+ VMSTATE_UINT8(stime0, LSIState),
+ VMSTATE_UINT8(respid0, LSIState),
+ VMSTATE_UINT8(respid1, LSIState),
+ VMSTATE_UINT8_V(sbcl, LSIState, 1),
+ VMSTATE_UINT32(mmrs, LSIState),
+ VMSTATE_UINT32(mmws, LSIState),
+ VMSTATE_UINT32(sfs, LSIState),
+ VMSTATE_UINT32(drs, LSIState),
+ VMSTATE_UINT32(sbms, LSIState),
+ VMSTATE_UINT32(dbms, LSIState),
+ VMSTATE_UINT32(dnad64, LSIState),
+ VMSTATE_UINT32(pmjad1, LSIState),
+ VMSTATE_UINT32(pmjad2, LSIState),
+ VMSTATE_UINT32(rbc, LSIState),
+ VMSTATE_UINT32(ua, LSIState),
+ VMSTATE_UINT32(ia, LSIState),
+ VMSTATE_UINT32(sbc, LSIState),
+ VMSTATE_UINT32(csbc, LSIState),
+ VMSTATE_BUFFER_UNSAFE(scratch, LSIState, 0, 18 * sizeof(uint32_t)),
+ VMSTATE_UINT8(sbr, LSIState),
+
+ VMSTATE_BUFFER_UNSAFE(script_ram, LSIState, 0, 8192),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const struct SCSIBusInfo lsi_scsi_info = {
+ .tcq = true,
+ .max_target = LSI_MAX_DEVS,
+ .max_lun = 0, /* LUN support is buggy */
+
+ .transfer_data = lsi_transfer_data,
+ .complete = lsi_command_complete,
+ .cancel = lsi_request_cancelled
+};
+
+static void lsi_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ LSIState *s = LSI53C895A(dev);
+ DeviceState *d = DEVICE(dev);
+ uint8_t *pci_conf;
+
+ pci_conf = dev->config;
+
+ /* PCI latency timer = 255 */
+ pci_conf[PCI_LATENCY_TIMER] = 0xff;
+ /* Interrupt pin A */
+ pci_conf[PCI_INTERRUPT_PIN] = 0x01;
+
+ memory_region_init_io(&s->mmio_io, OBJECT(s), &lsi_mmio_ops, s,
+ "lsi-mmio", 0x400);
+ memory_region_init_io(&s->ram_io, OBJECT(s), &lsi_ram_ops, s,
+ "lsi-ram", 0x2000);
+ memory_region_init_io(&s->io_io, OBJECT(s), &lsi_io_ops, s,
+ "lsi-io", 256);
+
+ address_space_init(&s->pci_io_as, pci_address_space_io(dev), "lsi-pci-io");
+ qdev_init_gpio_out(d, &s->ext_irq, 1);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_io);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio_io);
+ pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->ram_io);
+ QTAILQ_INIT(&s->queue);
+
+ scsi_bus_init(&s->bus, sizeof(s->bus), d, &lsi_scsi_info);
+}
+
+static void lsi_scsi_exit(PCIDevice *dev)
+{
+ LSIState *s = LSI53C895A(dev);
+
+ address_space_destroy(&s->pci_io_as);
+}
+
+static void lsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = lsi_scsi_realize;
+ k->exit = lsi_scsi_exit;
+ k->vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ k->device_id = PCI_DEVICE_ID_LSI_53C895A;
+ k->class_id = PCI_CLASS_STORAGE_SCSI;
+ k->subsystem_id = 0x1000;
+ dc->reset = lsi_scsi_reset;
+ dc->vmsd = &vmstate_lsi_scsi;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo lsi_info = {
+ .name = TYPE_LSI53C895A,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(LSIState),
+ .class_init = lsi_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void lsi53c810_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->device_id = PCI_DEVICE_ID_LSI_53C810;
+}
+
+static const TypeInfo lsi53c810_info = {
+ .name = TYPE_LSI53C810,
+ .parent = TYPE_LSI53C895A,
+ .class_init = lsi53c810_class_init,
+};
+
+static void lsi53c895a_register_types(void)
+{
+ type_register_static(&lsi_info);
+ type_register_static(&lsi53c810_info);
+}
+
+type_init(lsi53c895a_register_types)
+
+void lsi53c8xx_handle_legacy_cmdline(DeviceState *lsi_dev)
+{
+ LSIState *s = LSI53C895A(lsi_dev);
+
+ scsi_bus_legacy_handle_cmdline(&s->bus);
+}
diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c
new file mode 100644
index 00000000..9cbbb161
--- /dev/null
+++ b/hw/scsi/megasas.c
@@ -0,0 +1,2589 @@
+/*
+ * QEMU MegaRAID SAS 8708EM2 Host Bus Adapter emulation
+ * Based on the linux driver code at drivers/scsi/megaraid
+ *
+ * Copyright (c) 2009-2012 Hannes Reinecke, SUSE Labs
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "sysemu/dma.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/rtc.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/msix.h"
+#include "qemu/iov.h"
+#include "qemu/module.h"
+#include "qemu/hw-version.h"
+#include "hw/scsi/scsi.h"
+#include "scsi/constants.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "mfi.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+#define MEGASAS_VERSION_GEN1 "1.70"
+#define MEGASAS_VERSION_GEN2 "1.80"
+#define MEGASAS_MAX_FRAMES 2048 /* Firmware limit at 65535 */
+#define MEGASAS_DEFAULT_FRAMES 1000 /* Windows requires this */
+#define MEGASAS_GEN2_DEFAULT_FRAMES 1008 /* Windows requires this */
+#define MEGASAS_MAX_SGE 128 /* Firmware limit */
+#define MEGASAS_DEFAULT_SGE 80
+#define MEGASAS_MAX_SECTORS 0xFFFF /* No real limit */
+#define MEGASAS_MAX_ARRAYS 128
+
+#define MEGASAS_HBA_SERIAL "QEMU123456"
+#define NAA_LOCALLY_ASSIGNED_ID 0x3ULL
+#define IEEE_COMPANY_LOCALLY_ASSIGNED 0x525400
+
+#define MEGASAS_FLAG_USE_JBOD 0
+#define MEGASAS_MASK_USE_JBOD (1 << MEGASAS_FLAG_USE_JBOD)
+#define MEGASAS_FLAG_USE_QUEUE64 1
+#define MEGASAS_MASK_USE_QUEUE64 (1 << MEGASAS_FLAG_USE_QUEUE64)
+
+typedef struct MegasasCmd {
+ uint32_t index;
+ uint16_t flags;
+ uint16_t count;
+ uint64_t context;
+
+ hwaddr pa;
+ hwaddr pa_size;
+ uint32_t dcmd_opcode;
+ union mfi_frame *frame;
+ SCSIRequest *req;
+ QEMUSGList qsg;
+ void *iov_buf;
+ size_t iov_size;
+ size_t iov_offset;
+ struct MegasasState *state;
+} MegasasCmd;
+
+struct MegasasState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion mmio_io;
+ MemoryRegion port_io;
+ MemoryRegion queue_io;
+ uint32_t frame_hi;
+
+ uint32_t fw_state;
+ uint32_t fw_sge;
+ uint32_t fw_cmds;
+ uint32_t flags;
+ uint32_t fw_luns;
+ uint32_t intr_mask;
+ uint32_t doorbell;
+ uint32_t busy;
+ uint32_t diag;
+ uint32_t adp_reset;
+ OnOffAuto msi;
+ OnOffAuto msix;
+
+ MegasasCmd *event_cmd;
+ uint16_t event_locale;
+ int event_class;
+ uint32_t event_count;
+ uint32_t shutdown_event;
+ uint32_t boot_event;
+
+ uint64_t sas_addr;
+ char *hba_serial;
+
+ uint64_t reply_queue_pa;
+ void *reply_queue;
+ uint16_t reply_queue_len;
+ uint32_t reply_queue_head;
+ uint32_t reply_queue_tail;
+ uint64_t consumer_pa;
+ uint64_t producer_pa;
+
+ MegasasCmd frames[MEGASAS_MAX_FRAMES];
+ DECLARE_BITMAP(frame_map, MEGASAS_MAX_FRAMES);
+ SCSIBus bus;
+};
+typedef struct MegasasState MegasasState;
+
+struct MegasasBaseClass {
+ PCIDeviceClass parent_class;
+ const char *product_name;
+ const char *product_version;
+ int mmio_bar;
+ int ioport_bar;
+ int osts;
+};
+typedef struct MegasasBaseClass MegasasBaseClass;
+
+#define TYPE_MEGASAS_BASE "megasas-base"
+#define TYPE_MEGASAS_GEN1 "megasas"
+#define TYPE_MEGASAS_GEN2 "megasas-gen2"
+
+DECLARE_OBJ_CHECKERS(MegasasState, MegasasBaseClass,
+ MEGASAS, TYPE_MEGASAS_BASE)
+
+
+#define MEGASAS_INTR_DISABLED_MASK 0xFFFFFFFF
+
+static bool megasas_intr_enabled(MegasasState *s)
+{
+ if ((s->intr_mask & MEGASAS_INTR_DISABLED_MASK) !=
+ MEGASAS_INTR_DISABLED_MASK) {
+ return true;
+ }
+ return false;
+}
+
+static bool megasas_use_queue64(MegasasState *s)
+{
+ return s->flags & MEGASAS_MASK_USE_QUEUE64;
+}
+
+static bool megasas_use_msix(MegasasState *s)
+{
+ return s->msix != ON_OFF_AUTO_OFF;
+}
+
+static bool megasas_is_jbod(MegasasState *s)
+{
+ return s->flags & MEGASAS_MASK_USE_JBOD;
+}
+
+static void megasas_frame_set_cmd_status(MegasasState *s,
+ unsigned long frame, uint8_t v)
+{
+ PCIDevice *pci = &s->parent_obj;
+ stb_pci_dma(pci, frame + offsetof(struct mfi_frame_header, cmd_status),
+ v, MEMTXATTRS_UNSPECIFIED);
+}
+
+static void megasas_frame_set_scsi_status(MegasasState *s,
+ unsigned long frame, uint8_t v)
+{
+ PCIDevice *pci = &s->parent_obj;
+ stb_pci_dma(pci, frame + offsetof(struct mfi_frame_header, scsi_status),
+ v, MEMTXATTRS_UNSPECIFIED);
+}
+
+static inline const char *mfi_frame_desc(unsigned int cmd)
+{
+ static const char *mfi_frame_descs[] = {
+ "MFI init", "LD Read", "LD Write", "LD SCSI", "PD SCSI",
+ "MFI Doorbell", "MFI Abort", "MFI SMP", "MFI Stop"
+ };
+
+ if (cmd < ARRAY_SIZE(mfi_frame_descs)) {
+ return mfi_frame_descs[cmd];
+ }
+
+ return "Unknown";
+}
+
+/*
+ * Context is considered opaque, but the HBA firmware is running
+ * in little endian mode. So convert it to little endian, too.
+ */
+static uint64_t megasas_frame_get_context(MegasasState *s,
+ unsigned long frame)
+{
+ PCIDevice *pci = &s->parent_obj;
+ uint64_t val;
+
+ ldq_le_pci_dma(pci, frame + offsetof(struct mfi_frame_header, context),
+ &val, MEMTXATTRS_UNSPECIFIED);
+
+ return val;
+}
+
+static bool megasas_frame_is_ieee_sgl(MegasasCmd *cmd)
+{
+ return cmd->flags & MFI_FRAME_IEEE_SGL;
+}
+
+static bool megasas_frame_is_sgl64(MegasasCmd *cmd)
+{
+ return cmd->flags & MFI_FRAME_SGL64;
+}
+
+static bool megasas_frame_is_sense64(MegasasCmd *cmd)
+{
+ return cmd->flags & MFI_FRAME_SENSE64;
+}
+
+static uint64_t megasas_sgl_get_addr(MegasasCmd *cmd,
+ union mfi_sgl *sgl)
+{
+ uint64_t addr;
+
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ addr = le64_to_cpu(sgl->sg_skinny->addr);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ addr = le64_to_cpu(sgl->sg64->addr);
+ } else {
+ addr = le32_to_cpu(sgl->sg32->addr);
+ }
+ return addr;
+}
+
+static uint32_t megasas_sgl_get_len(MegasasCmd *cmd,
+ union mfi_sgl *sgl)
+{
+ uint32_t len;
+
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ len = le32_to_cpu(sgl->sg_skinny->len);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ len = le32_to_cpu(sgl->sg64->len);
+ } else {
+ len = le32_to_cpu(sgl->sg32->len);
+ }
+ return len;
+}
+
+static union mfi_sgl *megasas_sgl_next(MegasasCmd *cmd,
+ union mfi_sgl *sgl)
+{
+ uint8_t *next = (uint8_t *)sgl;
+
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ next += sizeof(struct mfi_sg_skinny);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ next += sizeof(struct mfi_sg64);
+ } else {
+ next += sizeof(struct mfi_sg32);
+ }
+
+ if (next >= (uint8_t *)cmd->frame + cmd->pa_size) {
+ return NULL;
+ }
+ return (union mfi_sgl *)next;
+}
+
+static void megasas_soft_reset(MegasasState *s);
+
+static int megasas_map_sgl(MegasasState *s, MegasasCmd *cmd, union mfi_sgl *sgl)
+{
+ int i;
+ int iov_count = 0;
+ size_t iov_size = 0;
+
+ cmd->flags = le16_to_cpu(cmd->frame->header.flags);
+ iov_count = cmd->frame->header.sge_count;
+ if (!iov_count || iov_count > MEGASAS_MAX_SGE) {
+ trace_megasas_iovec_sgl_overflow(cmd->index, iov_count,
+ MEGASAS_MAX_SGE);
+ return -1;
+ }
+ pci_dma_sglist_init(&cmd->qsg, PCI_DEVICE(s), iov_count);
+ for (i = 0; i < iov_count; i++) {
+ dma_addr_t iov_pa, iov_size_p;
+
+ if (!sgl) {
+ trace_megasas_iovec_sgl_underflow(cmd->index, i);
+ goto unmap;
+ }
+ iov_pa = megasas_sgl_get_addr(cmd, sgl);
+ iov_size_p = megasas_sgl_get_len(cmd, sgl);
+ if (!iov_pa || !iov_size_p) {
+ trace_megasas_iovec_sgl_invalid(cmd->index, i,
+ iov_pa, iov_size_p);
+ goto unmap;
+ }
+ qemu_sglist_add(&cmd->qsg, iov_pa, iov_size_p);
+ sgl = megasas_sgl_next(cmd, sgl);
+ iov_size += (size_t)iov_size_p;
+ }
+ if (cmd->iov_size > iov_size) {
+ trace_megasas_iovec_overflow(cmd->index, iov_size, cmd->iov_size);
+ goto unmap;
+ } else if (cmd->iov_size < iov_size) {
+ trace_megasas_iovec_underflow(cmd->index, iov_size, cmd->iov_size);
+ }
+ cmd->iov_offset = 0;
+ return 0;
+unmap:
+ qemu_sglist_destroy(&cmd->qsg);
+ return -1;
+}
+
+/*
+ * passthrough sense and io sense are at the same offset
+ */
+static int megasas_build_sense(MegasasCmd *cmd, uint8_t *sense_ptr,
+ uint8_t sense_len)
+{
+ PCIDevice *pcid = PCI_DEVICE(cmd->state);
+ uint32_t pa_hi = 0, pa_lo;
+ hwaddr pa;
+ int frame_sense_len;
+
+ frame_sense_len = cmd->frame->header.sense_len;
+ if (sense_len > frame_sense_len) {
+ sense_len = frame_sense_len;
+ }
+ if (sense_len) {
+ pa_lo = le32_to_cpu(cmd->frame->pass.sense_addr_lo);
+ if (megasas_frame_is_sense64(cmd)) {
+ pa_hi = le32_to_cpu(cmd->frame->pass.sense_addr_hi);
+ }
+ pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ pci_dma_write(pcid, pa, sense_ptr, sense_len);
+ cmd->frame->header.sense_len = sense_len;
+ }
+ return sense_len;
+}
+
+static void megasas_write_sense(MegasasCmd *cmd, SCSISense sense)
+{
+ uint8_t sense_buf[SCSI_SENSE_BUF_SIZE];
+ uint8_t sense_len = 18;
+
+ memset(sense_buf, 0, sense_len);
+ sense_buf[0] = 0xf0;
+ sense_buf[2] = sense.key;
+ sense_buf[7] = 10;
+ sense_buf[12] = sense.asc;
+ sense_buf[13] = sense.ascq;
+ megasas_build_sense(cmd, sense_buf, sense_len);
+}
+
+static void megasas_copy_sense(MegasasCmd *cmd)
+{
+ uint8_t sense_buf[SCSI_SENSE_BUF_SIZE];
+ uint8_t sense_len;
+
+ sense_len = scsi_req_get_sense(cmd->req, sense_buf,
+ SCSI_SENSE_BUF_SIZE);
+ megasas_build_sense(cmd, sense_buf, sense_len);
+}
+
+/*
+ * Format an INQUIRY CDB
+ */
+static int megasas_setup_inquiry(uint8_t *cdb, int pg, int len)
+{
+ memset(cdb, 0, 6);
+ cdb[0] = INQUIRY;
+ if (pg > 0) {
+ cdb[1] = 0x1;
+ cdb[2] = pg;
+ }
+ stw_be_p(&cdb[3], len);
+ return len;
+}
+
+/*
+ * Encode lba and len into a READ_16/WRITE_16 CDB
+ */
+static void megasas_encode_lba(uint8_t *cdb, uint64_t lba,
+ uint32_t len, bool is_write)
+{
+ memset(cdb, 0x0, 16);
+ if (is_write) {
+ cdb[0] = WRITE_16;
+ } else {
+ cdb[0] = READ_16;
+ }
+ stq_be_p(&cdb[2], lba);
+ stl_be_p(&cdb[2 + 8], len);
+}
+
+/*
+ * Utility functions
+ */
+static uint64_t megasas_fw_time(void)
+{
+ struct tm curtime;
+
+ qemu_get_timedate(&curtime, 0);
+ return ((uint64_t)curtime.tm_sec & 0xff) << 48 |
+ ((uint64_t)curtime.tm_min & 0xff) << 40 |
+ ((uint64_t)curtime.tm_hour & 0xff) << 32 |
+ ((uint64_t)curtime.tm_mday & 0xff) << 24 |
+ ((uint64_t)curtime.tm_mon & 0xff) << 16 |
+ ((uint64_t)(curtime.tm_year + 1900) & 0xffff);
+}
+
+/*
+ * Default disk sata address
+ * 0x1221 is the magic number as
+ * present in real hardware,
+ * so use it here, too.
+ */
+static uint64_t megasas_get_sata_addr(uint16_t id)
+{
+ uint64_t addr = (0x1221ULL << 48);
+ return addr | ((uint64_t)id << 24);
+}
+
+/*
+ * Frame handling
+ */
+static int megasas_next_index(MegasasState *s, int index, int limit)
+{
+ index++;
+ if (index == limit) {
+ index = 0;
+ }
+ return index;
+}
+
+static MegasasCmd *megasas_lookup_frame(MegasasState *s,
+ hwaddr frame)
+{
+ MegasasCmd *cmd = NULL;
+ int num = 0, index;
+
+ index = s->reply_queue_head;
+
+ while (num < s->fw_cmds && index < MEGASAS_MAX_FRAMES) {
+ if (s->frames[index].pa && s->frames[index].pa == frame) {
+ cmd = &s->frames[index];
+ break;
+ }
+ index = megasas_next_index(s, index, s->fw_cmds);
+ num++;
+ }
+
+ return cmd;
+}
+
+static void megasas_unmap_frame(MegasasState *s, MegasasCmd *cmd)
+{
+ PCIDevice *p = PCI_DEVICE(s);
+
+ if (cmd->pa_size) {
+ pci_dma_unmap(p, cmd->frame, cmd->pa_size, 0, 0);
+ }
+ cmd->frame = NULL;
+ cmd->pa = 0;
+ cmd->pa_size = 0;
+ qemu_sglist_destroy(&cmd->qsg);
+ clear_bit(cmd->index, s->frame_map);
+}
+
+/*
+ * This absolutely needs to be locked if
+ * qemu ever goes multithreaded.
+ */
+static MegasasCmd *megasas_enqueue_frame(MegasasState *s,
+ hwaddr frame, uint64_t context, int count)
+{
+ PCIDevice *pcid = PCI_DEVICE(s);
+ MegasasCmd *cmd = NULL;
+ int frame_size = MEGASAS_MAX_SGE * sizeof(union mfi_sgl);
+ hwaddr frame_size_p = frame_size;
+ unsigned long index;
+
+ index = 0;
+ while (index < s->fw_cmds) {
+ index = find_next_zero_bit(s->frame_map, s->fw_cmds, index);
+ if (!s->frames[index].pa)
+ break;
+ /* Busy frame found */
+ trace_megasas_qf_mapped(index);
+ }
+ if (index >= s->fw_cmds) {
+ /* All frames busy */
+ trace_megasas_qf_busy(frame);
+ return NULL;
+ }
+ cmd = &s->frames[index];
+ set_bit(index, s->frame_map);
+ trace_megasas_qf_new(index, frame);
+
+ cmd->pa = frame;
+ /* Map all possible frames */
+ cmd->frame = pci_dma_map(pcid, frame, &frame_size_p, 0);
+ if (!cmd->frame || frame_size_p != frame_size) {
+ trace_megasas_qf_map_failed(cmd->index, (unsigned long)frame);
+ if (cmd->frame) {
+ megasas_unmap_frame(s, cmd);
+ }
+ s->event_count++;
+ return NULL;
+ }
+ cmd->pa_size = frame_size_p;
+ cmd->context = context;
+ if (!megasas_use_queue64(s)) {
+ cmd->context &= (uint64_t)0xFFFFFFFF;
+ }
+ cmd->count = count;
+ cmd->dcmd_opcode = -1;
+ s->busy++;
+
+ if (s->consumer_pa) {
+ ldl_le_pci_dma(pcid, s->consumer_pa, &s->reply_queue_tail,
+ MEMTXATTRS_UNSPECIFIED);
+ }
+ trace_megasas_qf_enqueue(cmd->index, cmd->count, cmd->context,
+ s->reply_queue_head, s->reply_queue_tail, s->busy);
+
+ return cmd;
+}
+
+static void megasas_complete_frame(MegasasState *s, uint64_t context)
+{
+ const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ int tail, queue_offset;
+
+ /* Decrement busy count */
+ s->busy--;
+ if (s->reply_queue_pa) {
+ /*
+ * Put command on the reply queue.
+ * Context is opaque, but emulation is running in
+ * little endian. So convert it.
+ */
+ if (megasas_use_queue64(s)) {
+ queue_offset = s->reply_queue_head * sizeof(uint64_t);
+ stq_le_pci_dma(pci_dev, s->reply_queue_pa + queue_offset,
+ context, attrs);
+ } else {
+ queue_offset = s->reply_queue_head * sizeof(uint32_t);
+ stl_le_pci_dma(pci_dev, s->reply_queue_pa + queue_offset,
+ context, attrs);
+ }
+ ldl_le_pci_dma(pci_dev, s->consumer_pa, &s->reply_queue_tail, attrs);
+ trace_megasas_qf_complete(context, s->reply_queue_head,
+ s->reply_queue_tail, s->busy);
+ }
+
+ if (megasas_intr_enabled(s)) {
+ /* Update reply queue pointer */
+ ldl_le_pci_dma(pci_dev, s->consumer_pa, &s->reply_queue_tail, attrs);
+ tail = s->reply_queue_head;
+ s->reply_queue_head = megasas_next_index(s, tail, s->fw_cmds);
+ trace_megasas_qf_update(s->reply_queue_head, s->reply_queue_tail,
+ s->busy);
+ stl_le_pci_dma(pci_dev, s->producer_pa, s->reply_queue_head, attrs);
+ /* Notify HBA */
+ if (msix_enabled(pci_dev)) {
+ trace_megasas_msix_raise(0);
+ msix_notify(pci_dev, 0);
+ } else if (msi_enabled(pci_dev)) {
+ trace_megasas_msi_raise(0);
+ msi_notify(pci_dev, 0);
+ } else {
+ s->doorbell++;
+ if (s->doorbell == 1) {
+ trace_megasas_irq_raise();
+ pci_irq_assert(pci_dev);
+ }
+ }
+ } else {
+ trace_megasas_qf_complete_noirq(context);
+ }
+}
+
+static void megasas_complete_command(MegasasCmd *cmd)
+{
+ cmd->iov_size = 0;
+ cmd->iov_offset = 0;
+
+ cmd->req->hba_private = NULL;
+ scsi_req_unref(cmd->req);
+ cmd->req = NULL;
+
+ megasas_unmap_frame(cmd->state, cmd);
+ megasas_complete_frame(cmd->state, cmd->context);
+}
+
+static void megasas_reset_frames(MegasasState *s)
+{
+ int i;
+ MegasasCmd *cmd;
+
+ for (i = 0; i < s->fw_cmds; i++) {
+ cmd = &s->frames[i];
+ if (cmd->pa) {
+ megasas_unmap_frame(s, cmd);
+ }
+ }
+ bitmap_zero(s->frame_map, MEGASAS_MAX_FRAMES);
+}
+
+static void megasas_abort_command(MegasasCmd *cmd)
+{
+ /* Never abort internal commands. */
+ if (cmd->dcmd_opcode != -1) {
+ return;
+ }
+ if (cmd->req != NULL) {
+ scsi_req_cancel(cmd->req);
+ }
+}
+
+static int megasas_init_firmware(MegasasState *s, MegasasCmd *cmd)
+{
+ const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
+ PCIDevice *pcid = PCI_DEVICE(s);
+ uint32_t pa_hi, pa_lo;
+ hwaddr iq_pa, initq_size = sizeof(struct mfi_init_qinfo);
+ struct mfi_init_qinfo *initq = NULL;
+ uint32_t flags;
+ int ret = MFI_STAT_OK;
+
+ if (s->reply_queue_pa) {
+ trace_megasas_initq_mapped(s->reply_queue_pa);
+ goto out;
+ }
+ pa_lo = le32_to_cpu(cmd->frame->init.qinfo_new_addr_lo);
+ pa_hi = le32_to_cpu(cmd->frame->init.qinfo_new_addr_hi);
+ iq_pa = (((uint64_t) pa_hi << 32) | pa_lo);
+ trace_megasas_init_firmware((uint64_t)iq_pa);
+ initq = pci_dma_map(pcid, iq_pa, &initq_size, 0);
+ if (!initq || initq_size != sizeof(*initq)) {
+ trace_megasas_initq_map_failed(cmd->index);
+ s->event_count++;
+ ret = MFI_STAT_MEMORY_NOT_AVAILABLE;
+ goto out;
+ }
+ s->reply_queue_len = le32_to_cpu(initq->rq_entries) & 0xFFFF;
+ if (s->reply_queue_len > s->fw_cmds) {
+ trace_megasas_initq_mismatch(s->reply_queue_len, s->fw_cmds);
+ s->event_count++;
+ ret = MFI_STAT_INVALID_PARAMETER;
+ goto out;
+ }
+ pa_lo = le32_to_cpu(initq->rq_addr_lo);
+ pa_hi = le32_to_cpu(initq->rq_addr_hi);
+ s->reply_queue_pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ pa_lo = le32_to_cpu(initq->ci_addr_lo);
+ pa_hi = le32_to_cpu(initq->ci_addr_hi);
+ s->consumer_pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ pa_lo = le32_to_cpu(initq->pi_addr_lo);
+ pa_hi = le32_to_cpu(initq->pi_addr_hi);
+ s->producer_pa = ((uint64_t) pa_hi << 32) | pa_lo;
+ ldl_le_pci_dma(pcid, s->producer_pa, &s->reply_queue_head, attrs);
+ s->reply_queue_head %= MEGASAS_MAX_FRAMES;
+ ldl_le_pci_dma(pcid, s->consumer_pa, &s->reply_queue_tail, attrs);
+ s->reply_queue_tail %= MEGASAS_MAX_FRAMES;
+ flags = le32_to_cpu(initq->flags);
+ if (flags & MFI_QUEUE_FLAG_CONTEXT64) {
+ s->flags |= MEGASAS_MASK_USE_QUEUE64;
+ }
+ trace_megasas_init_queue((unsigned long)s->reply_queue_pa,
+ s->reply_queue_len, s->reply_queue_head,
+ s->reply_queue_tail, flags);
+ megasas_reset_frames(s);
+ s->fw_state = MFI_FWSTATE_OPERATIONAL;
+out:
+ if (initq) {
+ pci_dma_unmap(pcid, initq, initq_size, 0, 0);
+ }
+ return ret;
+}
+
+static int megasas_map_dcmd(MegasasState *s, MegasasCmd *cmd)
+{
+ dma_addr_t iov_pa, iov_size;
+ int iov_count;
+
+ cmd->flags = le16_to_cpu(cmd->frame->header.flags);
+ iov_count = cmd->frame->header.sge_count;
+ if (!iov_count) {
+ trace_megasas_dcmd_zero_sge(cmd->index);
+ cmd->iov_size = 0;
+ return 0;
+ } else if (iov_count > 1) {
+ trace_megasas_dcmd_invalid_sge(cmd->index, iov_count);
+ cmd->iov_size = 0;
+ return -EINVAL;
+ }
+ iov_pa = megasas_sgl_get_addr(cmd, &cmd->frame->dcmd.sgl);
+ iov_size = megasas_sgl_get_len(cmd, &cmd->frame->dcmd.sgl);
+ pci_dma_sglist_init(&cmd->qsg, PCI_DEVICE(s), 1);
+ qemu_sglist_add(&cmd->qsg, iov_pa, iov_size);
+ cmd->iov_size = iov_size;
+ return 0;
+}
+
+static void megasas_finish_dcmd(MegasasCmd *cmd, uint32_t iov_size)
+{
+ trace_megasas_finish_dcmd(cmd->index, iov_size);
+
+ if (iov_size > cmd->iov_size) {
+ if (megasas_frame_is_ieee_sgl(cmd)) {
+ cmd->frame->dcmd.sgl.sg_skinny->len = cpu_to_le32(iov_size);
+ } else if (megasas_frame_is_sgl64(cmd)) {
+ cmd->frame->dcmd.sgl.sg64->len = cpu_to_le32(iov_size);
+ } else {
+ cmd->frame->dcmd.sgl.sg32->len = cpu_to_le32(iov_size);
+ }
+ }
+}
+
+static int megasas_ctrl_get_info(MegasasState *s, MegasasCmd *cmd)
+{
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ PCIDeviceClass *pci_class = PCI_DEVICE_GET_CLASS(pci_dev);
+ MegasasBaseClass *base_class = MEGASAS_GET_CLASS(s);
+ struct mfi_ctrl_info info;
+ size_t dcmd_size = sizeof(info);
+ BusChild *kid;
+ int num_pd_disks = 0;
+ dma_addr_t residual;
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ info.pci.vendor = cpu_to_le16(pci_class->vendor_id);
+ info.pci.device = cpu_to_le16(pci_class->device_id);
+ info.pci.subvendor = cpu_to_le16(pci_class->subsystem_vendor_id);
+ info.pci.subdevice = cpu_to_le16(pci_class->subsystem_id);
+
+ /*
+ * For some reason the firmware supports
+ * only up to 8 device ports.
+ * Despite supporting a far larger number
+ * of devices for the physical devices.
+ * So just display the first 8 devices
+ * in the device port list, independent
+ * of how many logical devices are actually
+ * present.
+ */
+ info.host.type = MFI_INFO_HOST_PCIE;
+ info.device.type = MFI_INFO_DEV_SAS3G;
+ info.device.port_count = 8;
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = SCSI_DEVICE(kid->child);
+ uint16_t pd_id;
+
+ if (num_pd_disks < 8) {
+ pd_id = ((sdev->id & 0xFF) << 8) | (sdev->lun & 0xFF);
+ info.device.port_addr[num_pd_disks] =
+ cpu_to_le64(megasas_get_sata_addr(pd_id));
+ }
+ num_pd_disks++;
+ }
+
+ memcpy(info.product_name, base_class->product_name, 24);
+ snprintf(info.serial_number, 32, "%s", s->hba_serial);
+ snprintf(info.package_version, 0x60, "%s-QEMU", qemu_hw_version());
+ memcpy(info.image_component[0].name, "APP", 3);
+ snprintf(info.image_component[0].version, 10, "%s-QEMU",
+ base_class->product_version);
+ memcpy(info.image_component[0].build_date, "Apr 1 2014", 11);
+ memcpy(info.image_component[0].build_time, "12:34:56", 8);
+ info.image_component_count = 1;
+ if (pci_dev->has_rom) {
+ uint8_t biosver[32];
+ uint8_t *ptr;
+
+ ptr = memory_region_get_ram_ptr(&pci_dev->rom);
+ memcpy(biosver, ptr + 0x41, 31);
+ biosver[31] = 0;
+ memcpy(info.image_component[1].name, "BIOS", 4);
+ memcpy(info.image_component[1].version, biosver,
+ strlen((const char *)biosver));
+ info.image_component_count++;
+ }
+ info.current_fw_time = cpu_to_le32(megasas_fw_time());
+ info.max_arms = 32;
+ info.max_spans = 8;
+ info.max_arrays = MEGASAS_MAX_ARRAYS;
+ info.max_lds = MFI_MAX_LD;
+ info.max_cmds = cpu_to_le16(s->fw_cmds);
+ info.max_sg_elements = cpu_to_le16(s->fw_sge);
+ info.max_request_size = cpu_to_le32(MEGASAS_MAX_SECTORS);
+ if (!megasas_is_jbod(s))
+ info.lds_present = cpu_to_le16(num_pd_disks);
+ info.pd_present = cpu_to_le16(num_pd_disks);
+ info.pd_disks_present = cpu_to_le16(num_pd_disks);
+ info.hw_present = cpu_to_le32(MFI_INFO_HW_NVRAM |
+ MFI_INFO_HW_MEM |
+ MFI_INFO_HW_FLASH);
+ info.memory_size = cpu_to_le16(512);
+ info.nvram_size = cpu_to_le16(32);
+ info.flash_size = cpu_to_le16(16);
+ info.raid_levels = cpu_to_le32(MFI_INFO_RAID_0);
+ info.adapter_ops = cpu_to_le32(MFI_INFO_AOPS_RBLD_RATE |
+ MFI_INFO_AOPS_SELF_DIAGNOSTIC |
+ MFI_INFO_AOPS_MIXED_ARRAY);
+ info.ld_ops = cpu_to_le32(MFI_INFO_LDOPS_DISK_CACHE_POLICY |
+ MFI_INFO_LDOPS_ACCESS_POLICY |
+ MFI_INFO_LDOPS_IO_POLICY |
+ MFI_INFO_LDOPS_WRITE_POLICY |
+ MFI_INFO_LDOPS_READ_POLICY);
+ info.max_strips_per_io = cpu_to_le16(s->fw_sge);
+ info.stripe_sz_ops.min = 3;
+ info.stripe_sz_ops.max = ctz32(MEGASAS_MAX_SECTORS + 1);
+ info.properties.pred_fail_poll_interval = cpu_to_le16(300);
+ info.properties.intr_throttle_cnt = cpu_to_le16(16);
+ info.properties.intr_throttle_timeout = cpu_to_le16(50);
+ info.properties.rebuild_rate = 30;
+ info.properties.patrol_read_rate = 30;
+ info.properties.bgi_rate = 30;
+ info.properties.cc_rate = 30;
+ info.properties.recon_rate = 30;
+ info.properties.cache_flush_interval = 4;
+ info.properties.spinup_drv_cnt = 2;
+ info.properties.spinup_delay = 6;
+ info.properties.ecc_bucket_size = 15;
+ info.properties.ecc_bucket_leak_rate = cpu_to_le16(1440);
+ info.properties.expose_encl_devices = 1;
+ info.properties.OnOffProperties = cpu_to_le32(MFI_CTRL_PROP_EnableJBOD);
+ info.pd_ops = cpu_to_le32(MFI_INFO_PDOPS_FORCE_ONLINE |
+ MFI_INFO_PDOPS_FORCE_OFFLINE);
+ info.pd_mix_support = cpu_to_le32(MFI_INFO_PDMIX_SAS |
+ MFI_INFO_PDMIX_SATA |
+ MFI_INFO_PDMIX_LD);
+
+ dma_buf_read(&info, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_mfc_get_defaults(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_defaults info;
+ size_t dcmd_size = sizeof(struct mfi_defaults);
+ dma_addr_t residual;
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ info.sas_addr = cpu_to_le64(s->sas_addr);
+ info.stripe_size = 3;
+ info.flush_time = 4;
+ info.background_rate = 30;
+ info.allow_mix_in_enclosure = 1;
+ info.allow_mix_in_ld = 1;
+ info.direct_pd_mapping = 1;
+ /* Enable for BIOS support */
+ info.bios_enumerate_lds = 1;
+ info.disable_ctrl_r = 1;
+ info.expose_enclosure_devices = 1;
+ info.disable_preboot_cli = 1;
+ info.cluster_disable = 1;
+
+ dma_buf_read(&info, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_get_bios_info(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_bios_data info;
+ size_t dcmd_size = sizeof(info);
+ dma_addr_t residual;
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ info.continue_on_error = 1;
+ info.verbose = 1;
+ if (megasas_is_jbod(s)) {
+ info.expose_all_drives = 1;
+ }
+
+ dma_buf_read(&info, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_get_fw_time(MegasasState *s, MegasasCmd *cmd)
+{
+ uint64_t fw_time;
+ size_t dcmd_size = sizeof(fw_time);
+ dma_addr_t residual;
+
+ fw_time = cpu_to_le64(megasas_fw_time());
+
+ dma_buf_read(&fw_time, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_set_fw_time(MegasasState *s, MegasasCmd *cmd)
+{
+ uint64_t fw_time;
+
+ /* This is a dummy; setting of firmware time is not allowed */
+ memcpy(&fw_time, cmd->frame->dcmd.mbox, sizeof(fw_time));
+
+ trace_megasas_dcmd_set_fw_time(cmd->index, fw_time);
+ fw_time = cpu_to_le64(megasas_fw_time());
+ return MFI_STAT_OK;
+}
+
+static int megasas_event_info(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_evt_log_state info;
+ size_t dcmd_size = sizeof(info);
+ dma_addr_t residual;
+
+ memset(&info, 0, dcmd_size);
+
+ info.newest_seq_num = cpu_to_le32(s->event_count);
+ info.shutdown_seq_num = cpu_to_le32(s->shutdown_event);
+ info.boot_seq_num = cpu_to_le32(s->boot_event);
+
+ dma_buf_read(&info, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_event_wait(MegasasState *s, MegasasCmd *cmd)
+{
+ union mfi_evt event;
+
+ if (cmd->iov_size < sizeof(struct mfi_evt_detail)) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ sizeof(struct mfi_evt_detail));
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ s->event_count = cpu_to_le32(cmd->frame->dcmd.mbox[0]);
+ event.word = cpu_to_le32(cmd->frame->dcmd.mbox[4]);
+ s->event_locale = event.members.locale;
+ s->event_class = event.members.class;
+ s->event_cmd = cmd;
+ /* Decrease busy count; event frame doesn't count here */
+ s->busy--;
+ cmd->iov_size = sizeof(struct mfi_evt_detail);
+ return MFI_STAT_INVALID_STATUS;
+}
+
+static int megasas_dcmd_pd_get_list(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_pd_list info;
+ size_t dcmd_size = sizeof(info);
+ BusChild *kid;
+ uint32_t offset, dcmd_limit, num_pd_disks = 0, max_pd_disks;
+ dma_addr_t residual;
+
+ memset(&info, 0, dcmd_size);
+ offset = 8;
+ dcmd_limit = offset + sizeof(struct mfi_pd_address);
+ if (cmd->iov_size < dcmd_limit) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_limit);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ max_pd_disks = (cmd->iov_size - offset) / sizeof(struct mfi_pd_address);
+ if (max_pd_disks > MFI_MAX_SYS_PDS) {
+ max_pd_disks = MFI_MAX_SYS_PDS;
+ }
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = SCSI_DEVICE(kid->child);
+ uint16_t pd_id;
+
+ if (num_pd_disks >= max_pd_disks)
+ break;
+
+ pd_id = ((sdev->id & 0xFF) << 8) | (sdev->lun & 0xFF);
+ info.addr[num_pd_disks].device_id = cpu_to_le16(pd_id);
+ info.addr[num_pd_disks].encl_device_id = 0xFFFF;
+ info.addr[num_pd_disks].encl_index = 0;
+ info.addr[num_pd_disks].slot_number = sdev->id & 0xFF;
+ info.addr[num_pd_disks].scsi_dev_type = sdev->type;
+ info.addr[num_pd_disks].connect_port_bitmap = 0x1;
+ info.addr[num_pd_disks].sas_addr[0] =
+ cpu_to_le64(megasas_get_sata_addr(pd_id));
+ num_pd_disks++;
+ offset += sizeof(struct mfi_pd_address);
+ }
+ trace_megasas_dcmd_pd_get_list(cmd->index, num_pd_disks,
+ max_pd_disks, offset);
+
+ info.size = cpu_to_le32(offset);
+ info.count = cpu_to_le32(num_pd_disks);
+
+ dma_buf_read(&info, offset, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_pd_list_query(MegasasState *s, MegasasCmd *cmd)
+{
+ uint16_t flags;
+
+ /* mbox0 contains flags */
+ flags = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_pd_list_query(cmd->index, flags);
+ if (flags == MR_PD_QUERY_TYPE_ALL ||
+ megasas_is_jbod(s)) {
+ return megasas_dcmd_pd_get_list(s, cmd);
+ }
+
+ return MFI_STAT_OK;
+}
+
+static int megasas_pd_get_info_submit(SCSIDevice *sdev, int lun,
+ MegasasCmd *cmd)
+{
+ struct mfi_pd_info *info = cmd->iov_buf;
+ size_t dcmd_size = sizeof(struct mfi_pd_info);
+ uint64_t pd_size;
+ uint16_t pd_id = ((sdev->id & 0xFF) << 8) | (lun & 0xFF);
+ uint8_t cmdbuf[6];
+ size_t len;
+ dma_addr_t residual;
+
+ if (!cmd->iov_buf) {
+ cmd->iov_buf = g_malloc0(dcmd_size);
+ info = cmd->iov_buf;
+ info->inquiry_data[0] = 0x7f; /* Force PQual 0x3, PType 0x1f */
+ info->vpd_page83[0] = 0x7f;
+ megasas_setup_inquiry(cmdbuf, 0, sizeof(info->inquiry_data));
+ cmd->req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, sizeof(cmdbuf), cmd);
+ if (!cmd->req) {
+ trace_megasas_dcmd_req_alloc_failed(cmd->index,
+ "PD get info std inquiry");
+ g_free(cmd->iov_buf);
+ cmd->iov_buf = NULL;
+ return MFI_STAT_FLASH_ALLOC_FAIL;
+ }
+ trace_megasas_dcmd_internal_submit(cmd->index,
+ "PD get info std inquiry", lun);
+ len = scsi_req_enqueue(cmd->req);
+ if (len > 0) {
+ cmd->iov_size = len;
+ scsi_req_continue(cmd->req);
+ }
+ return MFI_STAT_INVALID_STATUS;
+ } else if (info->inquiry_data[0] != 0x7f && info->vpd_page83[0] == 0x7f) {
+ megasas_setup_inquiry(cmdbuf, 0x83, sizeof(info->vpd_page83));
+ cmd->req = scsi_req_new(sdev, cmd->index, lun, cmdbuf, sizeof(cmdbuf), cmd);
+ if (!cmd->req) {
+ trace_megasas_dcmd_req_alloc_failed(cmd->index,
+ "PD get info vpd inquiry");
+ return MFI_STAT_FLASH_ALLOC_FAIL;
+ }
+ trace_megasas_dcmd_internal_submit(cmd->index,
+ "PD get info vpd inquiry", lun);
+ len = scsi_req_enqueue(cmd->req);
+ if (len > 0) {
+ cmd->iov_size = len;
+ scsi_req_continue(cmd->req);
+ }
+ return MFI_STAT_INVALID_STATUS;
+ }
+ /* Finished, set FW state */
+ if ((info->inquiry_data[0] >> 5) == 0) {
+ if (megasas_is_jbod(cmd->state)) {
+ info->fw_state = cpu_to_le16(MFI_PD_STATE_SYSTEM);
+ } else {
+ info->fw_state = cpu_to_le16(MFI_PD_STATE_ONLINE);
+ }
+ } else {
+ info->fw_state = cpu_to_le16(MFI_PD_STATE_OFFLINE);
+ }
+
+ info->ref.v.device_id = cpu_to_le16(pd_id);
+ info->state.ddf.pd_type = cpu_to_le16(MFI_PD_DDF_TYPE_IN_VD|
+ MFI_PD_DDF_TYPE_INTF_SAS);
+ blk_get_geometry(sdev->conf.blk, &pd_size);
+ info->raw_size = cpu_to_le64(pd_size);
+ info->non_coerced_size = cpu_to_le64(pd_size);
+ info->coerced_size = cpu_to_le64(pd_size);
+ info->encl_device_id = 0xFFFF;
+ info->slot_number = (sdev->id & 0xFF);
+ info->path_info.count = 1;
+ info->path_info.sas_addr[0] =
+ cpu_to_le64(megasas_get_sata_addr(pd_id));
+ info->connected_port_bitmap = 0x1;
+ info->device_speed = 1;
+ info->link_speed = 1;
+ dma_buf_read(cmd->iov_buf, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ g_free(cmd->iov_buf);
+ cmd->iov_size = dcmd_size - residual;
+ cmd->iov_buf = NULL;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_pd_get_info(MegasasState *s, MegasasCmd *cmd)
+{
+ size_t dcmd_size = sizeof(struct mfi_pd_info);
+ uint16_t pd_id;
+ uint8_t target_id, lun_id;
+ SCSIDevice *sdev = NULL;
+ int retval = MFI_STAT_DEVICE_NOT_FOUND;
+
+ if (cmd->iov_size < dcmd_size) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ /* mbox0 has the ID */
+ pd_id = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ target_id = (pd_id >> 8) & 0xFF;
+ lun_id = pd_id & 0xFF;
+ sdev = scsi_device_find(&s->bus, 0, target_id, lun_id);
+ trace_megasas_dcmd_pd_get_info(cmd->index, pd_id);
+
+ if (sdev) {
+ /* Submit inquiry */
+ retval = megasas_pd_get_info_submit(sdev, pd_id, cmd);
+ }
+
+ return retval;
+}
+
+static int megasas_dcmd_ld_get_list(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ld_list info;
+ size_t dcmd_size = sizeof(info);
+ dma_addr_t residual;
+ uint32_t num_ld_disks = 0, max_ld_disks;
+ uint64_t ld_size;
+ BusChild *kid;
+
+ memset(&info, 0, dcmd_size);
+ if (cmd->iov_size > dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ max_ld_disks = (cmd->iov_size - 8) / 16;
+ if (megasas_is_jbod(s)) {
+ max_ld_disks = 0;
+ }
+ if (max_ld_disks > MFI_MAX_LD) {
+ max_ld_disks = MFI_MAX_LD;
+ }
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = SCSI_DEVICE(kid->child);
+
+ if (num_ld_disks >= max_ld_disks) {
+ break;
+ }
+ /* Logical device size is in blocks */
+ blk_get_geometry(sdev->conf.blk, &ld_size);
+ info.ld_list[num_ld_disks].ld.v.target_id = sdev->id;
+ info.ld_list[num_ld_disks].state = MFI_LD_STATE_OPTIMAL;
+ info.ld_list[num_ld_disks].size = cpu_to_le64(ld_size);
+ num_ld_disks++;
+ }
+ info.ld_count = cpu_to_le32(num_ld_disks);
+ trace_megasas_dcmd_ld_get_list(cmd->index, num_ld_disks, max_ld_disks);
+
+ dma_buf_read(&info, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size = dcmd_size - residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_ld_list_query(MegasasState *s, MegasasCmd *cmd)
+{
+ uint16_t flags;
+ struct mfi_ld_targetid_list info;
+ size_t dcmd_size = sizeof(info);
+ dma_addr_t residual;
+ uint32_t num_ld_disks = 0, max_ld_disks = s->fw_luns;
+ BusChild *kid;
+
+ /* mbox0 contains flags */
+ flags = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_ld_list_query(cmd->index, flags);
+ if (flags != MR_LD_QUERY_TYPE_ALL &&
+ flags != MR_LD_QUERY_TYPE_EXPOSED_TO_HOST) {
+ max_ld_disks = 0;
+ }
+
+ memset(&info, 0, dcmd_size);
+ if (cmd->iov_size < 12) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ dcmd_size = sizeof(uint32_t) * 2 + 3;
+ max_ld_disks = cmd->iov_size - dcmd_size;
+ if (megasas_is_jbod(s)) {
+ max_ld_disks = 0;
+ }
+ if (max_ld_disks > MFI_MAX_LD) {
+ max_ld_disks = MFI_MAX_LD;
+ }
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = SCSI_DEVICE(kid->child);
+
+ if (num_ld_disks >= max_ld_disks) {
+ break;
+ }
+ info.targetid[num_ld_disks] = sdev->lun;
+ num_ld_disks++;
+ dcmd_size++;
+ }
+ info.ld_count = cpu_to_le32(num_ld_disks);
+ info.size = dcmd_size;
+ trace_megasas_dcmd_ld_get_list(cmd->index, num_ld_disks, max_ld_disks);
+
+ dma_buf_read(&info, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size = dcmd_size - residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_ld_get_info_submit(SCSIDevice *sdev, int lun,
+ MegasasCmd *cmd)
+{
+ struct mfi_ld_info *info = cmd->iov_buf;
+ size_t dcmd_size = sizeof(struct mfi_ld_info);
+ uint8_t cdb[6];
+ ssize_t len;
+ dma_addr_t residual;
+ uint16_t sdev_id = ((sdev->id & 0xFF) << 8) | (lun & 0xFF);
+ uint64_t ld_size;
+
+ if (!cmd->iov_buf) {
+ cmd->iov_buf = g_malloc0(dcmd_size);
+ info = cmd->iov_buf;
+ megasas_setup_inquiry(cdb, 0x83, sizeof(info->vpd_page83));
+ cmd->req = scsi_req_new(sdev, cmd->index, lun, cdb, sizeof(cdb), cmd);
+ if (!cmd->req) {
+ trace_megasas_dcmd_req_alloc_failed(cmd->index,
+ "LD get info vpd inquiry");
+ g_free(cmd->iov_buf);
+ cmd->iov_buf = NULL;
+ return MFI_STAT_FLASH_ALLOC_FAIL;
+ }
+ trace_megasas_dcmd_internal_submit(cmd->index,
+ "LD get info vpd inquiry", lun);
+ len = scsi_req_enqueue(cmd->req);
+ if (len > 0) {
+ cmd->iov_size = len;
+ scsi_req_continue(cmd->req);
+ }
+ return MFI_STAT_INVALID_STATUS;
+ }
+
+ info->ld_config.params.state = MFI_LD_STATE_OPTIMAL;
+ info->ld_config.properties.ld.v.target_id = lun;
+ info->ld_config.params.stripe_size = 3;
+ info->ld_config.params.num_drives = 1;
+ info->ld_config.params.is_consistent = 1;
+ /* Logical device size is in blocks */
+ blk_get_geometry(sdev->conf.blk, &ld_size);
+ info->size = cpu_to_le64(ld_size);
+ memset(info->ld_config.span, 0, sizeof(info->ld_config.span));
+ info->ld_config.span[0].start_block = 0;
+ info->ld_config.span[0].num_blocks = info->size;
+ info->ld_config.span[0].array_ref = cpu_to_le16(sdev_id);
+
+ dma_buf_read(cmd->iov_buf, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ g_free(cmd->iov_buf);
+ cmd->iov_size = dcmd_size - residual;
+ cmd->iov_buf = NULL;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_ld_get_info(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ld_info info;
+ size_t dcmd_size = sizeof(info);
+ uint16_t ld_id;
+ uint32_t max_ld_disks = s->fw_luns;
+ SCSIDevice *sdev = NULL;
+ int retval = MFI_STAT_DEVICE_NOT_FOUND;
+
+ if (cmd->iov_size < dcmd_size) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ /* mbox0 has the ID */
+ ld_id = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_ld_get_info(cmd->index, ld_id);
+
+ if (megasas_is_jbod(s)) {
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+
+ if (ld_id < max_ld_disks) {
+ sdev = scsi_device_find(&s->bus, 0, ld_id, 0);
+ }
+
+ if (sdev) {
+ retval = megasas_ld_get_info_submit(sdev, ld_id, cmd);
+ }
+
+ return retval;
+}
+
+static int megasas_dcmd_cfg_read(MegasasState *s, MegasasCmd *cmd)
+{
+ uint8_t data[4096] = { 0 };
+ struct mfi_config_data *info;
+ int num_pd_disks = 0, array_offset, ld_offset;
+ BusChild *kid;
+ dma_addr_t residual;
+
+ if (cmd->iov_size > 4096) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ num_pd_disks++;
+ }
+ info = (struct mfi_config_data *)&data;
+ /*
+ * Array mapping:
+ * - One array per SCSI device
+ * - One logical drive per SCSI device
+ * spanning the entire device
+ */
+ info->array_count = num_pd_disks;
+ info->array_size = sizeof(struct mfi_array) * num_pd_disks;
+ info->log_drv_count = num_pd_disks;
+ info->log_drv_size = sizeof(struct mfi_ld_config) * num_pd_disks;
+ info->spares_count = 0;
+ info->spares_size = sizeof(struct mfi_spare);
+ info->size = sizeof(struct mfi_config_data) + info->array_size +
+ info->log_drv_size;
+ if (info->size > 4096) {
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+
+ array_offset = sizeof(struct mfi_config_data);
+ ld_offset = array_offset + sizeof(struct mfi_array) * num_pd_disks;
+
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = SCSI_DEVICE(kid->child);
+ uint16_t sdev_id = ((sdev->id & 0xFF) << 8) | (sdev->lun & 0xFF);
+ struct mfi_array *array;
+ struct mfi_ld_config *ld;
+ uint64_t pd_size;
+ int i;
+
+ array = (struct mfi_array *)(data + array_offset);
+ blk_get_geometry(sdev->conf.blk, &pd_size);
+ array->size = cpu_to_le64(pd_size);
+ array->num_drives = 1;
+ array->array_ref = cpu_to_le16(sdev_id);
+ array->pd[0].ref.v.device_id = cpu_to_le16(sdev_id);
+ array->pd[0].ref.v.seq_num = 0;
+ array->pd[0].fw_state = MFI_PD_STATE_ONLINE;
+ array->pd[0].encl.pd = 0xFF;
+ array->pd[0].encl.slot = (sdev->id & 0xFF);
+ for (i = 1; i < MFI_MAX_ROW_SIZE; i++) {
+ array->pd[i].ref.v.device_id = 0xFFFF;
+ array->pd[i].ref.v.seq_num = 0;
+ array->pd[i].fw_state = MFI_PD_STATE_UNCONFIGURED_GOOD;
+ array->pd[i].encl.pd = 0xFF;
+ array->pd[i].encl.slot = 0xFF;
+ }
+ array_offset += sizeof(struct mfi_array);
+ ld = (struct mfi_ld_config *)(data + ld_offset);
+ memset(ld, 0, sizeof(struct mfi_ld_config));
+ ld->properties.ld.v.target_id = sdev->id;
+ ld->properties.default_cache_policy = MR_LD_CACHE_READ_AHEAD |
+ MR_LD_CACHE_READ_ADAPTIVE;
+ ld->properties.current_cache_policy = MR_LD_CACHE_READ_AHEAD |
+ MR_LD_CACHE_READ_ADAPTIVE;
+ ld->params.state = MFI_LD_STATE_OPTIMAL;
+ ld->params.stripe_size = 3;
+ ld->params.num_drives = 1;
+ ld->params.span_depth = 1;
+ ld->params.is_consistent = 1;
+ ld->span[0].start_block = 0;
+ ld->span[0].num_blocks = cpu_to_le64(pd_size);
+ ld->span[0].array_ref = cpu_to_le16(sdev_id);
+ ld_offset += sizeof(struct mfi_ld_config);
+ }
+
+ dma_buf_read(data, info->size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_get_properties(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ctrl_props info;
+ size_t dcmd_size = sizeof(info);
+ dma_addr_t residual;
+
+ memset(&info, 0x0, dcmd_size);
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ info.pred_fail_poll_interval = cpu_to_le16(300);
+ info.intr_throttle_cnt = cpu_to_le16(16);
+ info.intr_throttle_timeout = cpu_to_le16(50);
+ info.rebuild_rate = 30;
+ info.patrol_read_rate = 30;
+ info.bgi_rate = 30;
+ info.cc_rate = 30;
+ info.recon_rate = 30;
+ info.cache_flush_interval = 4;
+ info.spinup_drv_cnt = 2;
+ info.spinup_delay = 6;
+ info.ecc_bucket_size = 15;
+ info.ecc_bucket_leak_rate = cpu_to_le16(1440);
+ info.expose_encl_devices = 1;
+
+ dma_buf_read(&info, dcmd_size, &residual, &cmd->qsg,
+ MEMTXATTRS_UNSPECIFIED);
+ cmd->iov_size -= residual;
+ return MFI_STAT_OK;
+}
+
+static int megasas_cache_flush(MegasasState *s, MegasasCmd *cmd)
+{
+ blk_drain_all();
+ return MFI_STAT_OK;
+}
+
+static int megasas_ctrl_shutdown(MegasasState *s, MegasasCmd *cmd)
+{
+ s->fw_state = MFI_FWSTATE_READY;
+ return MFI_STAT_OK;
+}
+
+/* Some implementations use CLUSTER RESET LD to simulate a device reset */
+static int megasas_cluster_reset_ld(MegasasState *s, MegasasCmd *cmd)
+{
+ uint16_t target_id;
+ int i;
+
+ /* mbox0 contains the device index */
+ target_id = le16_to_cpu(cmd->frame->dcmd.mbox[0]);
+ trace_megasas_dcmd_reset_ld(cmd->index, target_id);
+ for (i = 0; i < s->fw_cmds; i++) {
+ MegasasCmd *tmp_cmd = &s->frames[i];
+ if (tmp_cmd->req && tmp_cmd->req->dev->id == target_id) {
+ SCSIDevice *d = tmp_cmd->req->dev;
+ device_cold_reset(&d->qdev);
+ }
+ }
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_set_properties(MegasasState *s, MegasasCmd *cmd)
+{
+ struct mfi_ctrl_props info;
+ size_t dcmd_size = sizeof(info);
+
+ if (cmd->iov_size < dcmd_size) {
+ trace_megasas_dcmd_invalid_xfer_len(cmd->index, cmd->iov_size,
+ dcmd_size);
+ return MFI_STAT_INVALID_PARAMETER;
+ }
+ dma_buf_write(&info, dcmd_size, NULL, &cmd->qsg, MEMTXATTRS_UNSPECIFIED);
+ trace_megasas_dcmd_unsupported(cmd->index, cmd->iov_size);
+ return MFI_STAT_OK;
+}
+
+static int megasas_dcmd_dummy(MegasasState *s, MegasasCmd *cmd)
+{
+ trace_megasas_dcmd_dummy(cmd->index, cmd->iov_size);
+ return MFI_STAT_OK;
+}
+
+static const struct dcmd_cmd_tbl_t {
+ int opcode;
+ const char *desc;
+ int (*func)(MegasasState *s, MegasasCmd *cmd);
+} dcmd_cmd_tbl[] = {
+ { MFI_DCMD_CTRL_MFI_HOST_MEM_ALLOC, "CTRL_HOST_MEM_ALLOC",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_GET_INFO, "CTRL_GET_INFO",
+ megasas_ctrl_get_info },
+ { MFI_DCMD_CTRL_GET_PROPERTIES, "CTRL_GET_PROPERTIES",
+ megasas_dcmd_get_properties },
+ { MFI_DCMD_CTRL_SET_PROPERTIES, "CTRL_SET_PROPERTIES",
+ megasas_dcmd_set_properties },
+ { MFI_DCMD_CTRL_ALARM_GET, "CTRL_ALARM_GET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_ENABLE, "CTRL_ALARM_ENABLE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_DISABLE, "CTRL_ALARM_DISABLE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_SILENCE, "CTRL_ALARM_SILENCE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_ALARM_TEST, "CTRL_ALARM_TEST",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_EVENT_GETINFO, "CTRL_EVENT_GETINFO",
+ megasas_event_info },
+ { MFI_DCMD_CTRL_EVENT_GET, "CTRL_EVENT_GET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_EVENT_WAIT, "CTRL_EVENT_WAIT",
+ megasas_event_wait },
+ { MFI_DCMD_CTRL_SHUTDOWN, "CTRL_SHUTDOWN",
+ megasas_ctrl_shutdown },
+ { MFI_DCMD_HIBERNATE_STANDBY, "CTRL_STANDBY",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_GET_TIME, "CTRL_GET_TIME",
+ megasas_dcmd_get_fw_time },
+ { MFI_DCMD_CTRL_SET_TIME, "CTRL_SET_TIME",
+ megasas_dcmd_set_fw_time },
+ { MFI_DCMD_CTRL_BIOS_DATA_GET, "CTRL_BIOS_DATA_GET",
+ megasas_dcmd_get_bios_info },
+ { MFI_DCMD_CTRL_FACTORY_DEFAULTS, "CTRL_FACTORY_DEFAULTS",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_MFC_DEFAULTS_GET, "CTRL_MFC_DEFAULTS_GET",
+ megasas_mfc_get_defaults },
+ { MFI_DCMD_CTRL_MFC_DEFAULTS_SET, "CTRL_MFC_DEFAULTS_SET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CTRL_CACHE_FLUSH, "CTRL_CACHE_FLUSH",
+ megasas_cache_flush },
+ { MFI_DCMD_PD_GET_LIST, "PD_GET_LIST",
+ megasas_dcmd_pd_get_list },
+ { MFI_DCMD_PD_LIST_QUERY, "PD_LIST_QUERY",
+ megasas_dcmd_pd_list_query },
+ { MFI_DCMD_PD_GET_INFO, "PD_GET_INFO",
+ megasas_dcmd_pd_get_info },
+ { MFI_DCMD_PD_STATE_SET, "PD_STATE_SET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_PD_REBUILD, "PD_REBUILD",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_PD_BLINK, "PD_BLINK",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_PD_UNBLINK, "PD_UNBLINK",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_LD_GET_LIST, "LD_GET_LIST",
+ megasas_dcmd_ld_get_list},
+ { MFI_DCMD_LD_LIST_QUERY, "LD_LIST_QUERY",
+ megasas_dcmd_ld_list_query },
+ { MFI_DCMD_LD_GET_INFO, "LD_GET_INFO",
+ megasas_dcmd_ld_get_info },
+ { MFI_DCMD_LD_GET_PROP, "LD_GET_PROP",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_LD_SET_PROP, "LD_SET_PROP",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_LD_DELETE, "LD_DELETE",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_READ, "CFG_READ",
+ megasas_dcmd_cfg_read },
+ { MFI_DCMD_CFG_ADD, "CFG_ADD",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_CLEAR, "CFG_CLEAR",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_FOREIGN_READ, "CFG_FOREIGN_READ",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CFG_FOREIGN_IMPORT, "CFG_FOREIGN_IMPORT",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_STATUS, "BBU_STATUS",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_CAPACITY_INFO, "BBU_CAPACITY_INFO",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_DESIGN_INFO, "BBU_DESIGN_INFO",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_BBU_PROP_GET, "BBU_PROP_GET",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CLUSTER, "CLUSTER",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CLUSTER_RESET_ALL, "CLUSTER_RESET_ALL",
+ megasas_dcmd_dummy },
+ { MFI_DCMD_CLUSTER_RESET_LD, "CLUSTER_RESET_LD",
+ megasas_cluster_reset_ld },
+ { -1, NULL, NULL }
+};
+
+static int megasas_handle_dcmd(MegasasState *s, MegasasCmd *cmd)
+{
+ int retval = 0;
+ size_t len;
+ const struct dcmd_cmd_tbl_t *cmdptr = dcmd_cmd_tbl;
+
+ cmd->dcmd_opcode = le32_to_cpu(cmd->frame->dcmd.opcode);
+ trace_megasas_handle_dcmd(cmd->index, cmd->dcmd_opcode);
+ if (megasas_map_dcmd(s, cmd) < 0) {
+ return MFI_STAT_MEMORY_NOT_AVAILABLE;
+ }
+ while (cmdptr->opcode != -1 && cmdptr->opcode != cmd->dcmd_opcode) {
+ cmdptr++;
+ }
+ len = cmd->iov_size;
+ if (cmdptr->opcode == -1) {
+ trace_megasas_dcmd_unhandled(cmd->index, cmd->dcmd_opcode, len);
+ retval = megasas_dcmd_dummy(s, cmd);
+ } else {
+ trace_megasas_dcmd_enter(cmd->index, cmdptr->desc, len);
+ retval = cmdptr->func(s, cmd);
+ }
+ if (retval != MFI_STAT_INVALID_STATUS) {
+ megasas_finish_dcmd(cmd, len);
+ }
+ return retval;
+}
+
+static int megasas_finish_internal_dcmd(MegasasCmd *cmd,
+ SCSIRequest *req, dma_addr_t residual)
+{
+ int retval = MFI_STAT_OK;
+ int lun = req->lun;
+
+ trace_megasas_dcmd_internal_finish(cmd->index, cmd->dcmd_opcode, lun);
+ cmd->iov_size -= residual;
+ switch (cmd->dcmd_opcode) {
+ case MFI_DCMD_PD_GET_INFO:
+ retval = megasas_pd_get_info_submit(req->dev, lun, cmd);
+ break;
+ case MFI_DCMD_LD_GET_INFO:
+ retval = megasas_ld_get_info_submit(req->dev, lun, cmd);
+ break;
+ default:
+ trace_megasas_dcmd_internal_invalid(cmd->index, cmd->dcmd_opcode);
+ retval = MFI_STAT_INVALID_DCMD;
+ break;
+ }
+ if (retval != MFI_STAT_INVALID_STATUS) {
+ megasas_finish_dcmd(cmd, cmd->iov_size);
+ }
+ return retval;
+}
+
+static int megasas_enqueue_req(MegasasCmd *cmd, bool is_write)
+{
+ int len;
+
+ len = scsi_req_enqueue(cmd->req);
+ if (len < 0) {
+ len = -len;
+ }
+ if (len > 0) {
+ if (len > cmd->iov_size) {
+ if (is_write) {
+ trace_megasas_iov_write_overflow(cmd->index, len,
+ cmd->iov_size);
+ } else {
+ trace_megasas_iov_read_overflow(cmd->index, len,
+ cmd->iov_size);
+ }
+ }
+ if (len < cmd->iov_size) {
+ if (is_write) {
+ trace_megasas_iov_write_underflow(cmd->index, len,
+ cmd->iov_size);
+ } else {
+ trace_megasas_iov_read_underflow(cmd->index, len,
+ cmd->iov_size);
+ }
+ cmd->iov_size = len;
+ }
+ scsi_req_continue(cmd->req);
+ }
+ return len;
+}
+
+static int megasas_handle_scsi(MegasasState *s, MegasasCmd *cmd,
+ int frame_cmd)
+{
+ uint8_t *cdb;
+ int target_id, lun_id, cdb_len;
+ bool is_write;
+ struct SCSIDevice *sdev = NULL;
+ bool is_logical = (frame_cmd == MFI_CMD_LD_SCSI_IO);
+
+ cdb = cmd->frame->pass.cdb;
+ target_id = cmd->frame->header.target_id;
+ lun_id = cmd->frame->header.lun_id;
+ cdb_len = cmd->frame->header.cdb_len;
+
+ if (is_logical) {
+ if (target_id >= MFI_MAX_LD || lun_id != 0) {
+ trace_megasas_scsi_target_not_present(
+ mfi_frame_desc(frame_cmd), is_logical, target_id, lun_id);
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+ }
+ sdev = scsi_device_find(&s->bus, 0, target_id, lun_id);
+
+ cmd->iov_size = le32_to_cpu(cmd->frame->header.data_len);
+ trace_megasas_handle_scsi(mfi_frame_desc(frame_cmd), is_logical,
+ target_id, lun_id, sdev, cmd->iov_size);
+
+ if (!sdev || (megasas_is_jbod(s) && is_logical)) {
+ trace_megasas_scsi_target_not_present(
+ mfi_frame_desc(frame_cmd), is_logical, target_id, lun_id);
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+
+ if (cdb_len > 16) {
+ trace_megasas_scsi_invalid_cdb_len(
+ mfi_frame_desc(frame_cmd), is_logical,
+ target_id, lun_id, cdb_len);
+ megasas_write_sense(cmd, SENSE_CODE(INVALID_OPCODE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ if (megasas_map_sgl(s, cmd, &cmd->frame->pass.sgl)) {
+ megasas_write_sense(cmd, SENSE_CODE(TARGET_FAILURE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ cmd->req = scsi_req_new(sdev, cmd->index, lun_id, cdb, cdb_len, cmd);
+ if (!cmd->req) {
+ trace_megasas_scsi_req_alloc_failed(
+ mfi_frame_desc(frame_cmd), target_id, lun_id);
+ megasas_write_sense(cmd, SENSE_CODE(NO_SENSE));
+ cmd->frame->header.scsi_status = BUSY;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ is_write = (cmd->req->cmd.mode == SCSI_XFER_TO_DEV);
+ if (cmd->iov_size) {
+ if (is_write) {
+ trace_megasas_scsi_write_start(cmd->index, cmd->iov_size);
+ } else {
+ trace_megasas_scsi_read_start(cmd->index, cmd->iov_size);
+ }
+ } else {
+ trace_megasas_scsi_nodata(cmd->index);
+ }
+ megasas_enqueue_req(cmd, is_write);
+ return MFI_STAT_INVALID_STATUS;
+}
+
+static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd)
+{
+ uint32_t lba_count, lba_start_hi, lba_start_lo;
+ uint64_t lba_start;
+ bool is_write = (frame_cmd == MFI_CMD_LD_WRITE);
+ uint8_t cdb[16];
+ int len;
+ struct SCSIDevice *sdev = NULL;
+ int target_id, lun_id, cdb_len;
+
+ lba_count = le32_to_cpu(cmd->frame->io.header.data_len);
+ lba_start_lo = le32_to_cpu(cmd->frame->io.lba_lo);
+ lba_start_hi = le32_to_cpu(cmd->frame->io.lba_hi);
+ lba_start = ((uint64_t)lba_start_hi << 32) | lba_start_lo;
+
+ target_id = cmd->frame->header.target_id;
+ lun_id = cmd->frame->header.lun_id;
+ cdb_len = cmd->frame->header.cdb_len;
+
+ if (target_id < MFI_MAX_LD && lun_id == 0) {
+ sdev = scsi_device_find(&s->bus, 0, target_id, lun_id);
+ }
+
+ trace_megasas_handle_io(cmd->index,
+ mfi_frame_desc(frame_cmd), target_id, lun_id,
+ (unsigned long)lba_start, (unsigned long)lba_count);
+ if (!sdev) {
+ trace_megasas_io_target_not_present(cmd->index,
+ mfi_frame_desc(frame_cmd), target_id, lun_id);
+ return MFI_STAT_DEVICE_NOT_FOUND;
+ }
+
+ if (cdb_len > 16) {
+ trace_megasas_scsi_invalid_cdb_len(
+ mfi_frame_desc(frame_cmd), 1, target_id, lun_id, cdb_len);
+ megasas_write_sense(cmd, SENSE_CODE(INVALID_OPCODE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ cmd->iov_size = lba_count * sdev->blocksize;
+ if (megasas_map_sgl(s, cmd, &cmd->frame->io.sgl)) {
+ megasas_write_sense(cmd, SENSE_CODE(TARGET_FAILURE));
+ cmd->frame->header.scsi_status = CHECK_CONDITION;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+
+ megasas_encode_lba(cdb, lba_start, lba_count, is_write);
+ cmd->req = scsi_req_new(sdev, cmd->index,
+ lun_id, cdb, cdb_len, cmd);
+ if (!cmd->req) {
+ trace_megasas_scsi_req_alloc_failed(
+ mfi_frame_desc(frame_cmd), target_id, lun_id);
+ megasas_write_sense(cmd, SENSE_CODE(NO_SENSE));
+ cmd->frame->header.scsi_status = BUSY;
+ s->event_count++;
+ return MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+ len = megasas_enqueue_req(cmd, is_write);
+ if (len > 0) {
+ if (is_write) {
+ trace_megasas_io_write_start(cmd->index, lba_start, lba_count, len);
+ } else {
+ trace_megasas_io_read_start(cmd->index, lba_start, lba_count, len);
+ }
+ }
+ return MFI_STAT_INVALID_STATUS;
+}
+
+static QEMUSGList *megasas_get_sg_list(SCSIRequest *req)
+{
+ MegasasCmd *cmd = req->hba_private;
+
+ if (cmd->dcmd_opcode != -1) {
+ return NULL;
+ } else {
+ return &cmd->qsg;
+ }
+}
+
+static void megasas_xfer_complete(SCSIRequest *req, uint32_t len)
+{
+ MegasasCmd *cmd = req->hba_private;
+ uint8_t *buf;
+
+ trace_megasas_io_complete(cmd->index, len);
+
+ if (cmd->dcmd_opcode != -1) {
+ scsi_req_continue(req);
+ return;
+ }
+
+ buf = scsi_req_get_buf(req);
+ if (cmd->dcmd_opcode == MFI_DCMD_PD_GET_INFO && cmd->iov_buf) {
+ struct mfi_pd_info *info = cmd->iov_buf;
+
+ if (info->inquiry_data[0] == 0x7f) {
+ memset(info->inquiry_data, 0, sizeof(info->inquiry_data));
+ memcpy(info->inquiry_data, buf, len);
+ } else if (info->vpd_page83[0] == 0x7f) {
+ memset(info->vpd_page83, 0, sizeof(info->vpd_page83));
+ memcpy(info->vpd_page83, buf, len);
+ }
+ scsi_req_continue(req);
+ } else if (cmd->dcmd_opcode == MFI_DCMD_LD_GET_INFO) {
+ struct mfi_ld_info *info = cmd->iov_buf;
+
+ if (cmd->iov_buf) {
+ memcpy(info->vpd_page83, buf, sizeof(info->vpd_page83));
+ scsi_req_continue(req);
+ }
+ }
+}
+
+static void megasas_command_complete(SCSIRequest *req, size_t residual)
+{
+ MegasasCmd *cmd = req->hba_private;
+ uint8_t cmd_status = MFI_STAT_OK;
+
+ trace_megasas_command_complete(cmd->index, req->status, residual);
+
+ if (req->io_canceled) {
+ return;
+ }
+
+ if (cmd->dcmd_opcode != -1) {
+ /*
+ * Internal command complete
+ */
+ cmd_status = megasas_finish_internal_dcmd(cmd, req, residual);
+ if (cmd_status == MFI_STAT_INVALID_STATUS) {
+ return;
+ }
+ } else {
+ trace_megasas_scsi_complete(cmd->index, req->status,
+ cmd->iov_size, req->cmd.xfer);
+ if (req->status != GOOD) {
+ cmd_status = MFI_STAT_SCSI_DONE_WITH_ERROR;
+ }
+ if (req->status == CHECK_CONDITION) {
+ megasas_copy_sense(cmd);
+ }
+
+ cmd->frame->header.scsi_status = req->status;
+ }
+ cmd->frame->header.cmd_status = cmd_status;
+ megasas_complete_command(cmd);
+}
+
+static void megasas_command_cancelled(SCSIRequest *req)
+{
+ MegasasCmd *cmd = req->hba_private;
+
+ if (!cmd) {
+ return;
+ }
+ cmd->frame->header.cmd_status = MFI_STAT_SCSI_IO_FAILED;
+ megasas_complete_command(cmd);
+}
+
+static int megasas_handle_abort(MegasasState *s, MegasasCmd *cmd)
+{
+ uint64_t abort_ctx = le64_to_cpu(cmd->frame->abort.abort_context);
+ hwaddr abort_addr, addr_hi, addr_lo;
+ MegasasCmd *abort_cmd;
+
+ addr_hi = le32_to_cpu(cmd->frame->abort.abort_mfi_addr_hi);
+ addr_lo = le32_to_cpu(cmd->frame->abort.abort_mfi_addr_lo);
+ abort_addr = ((uint64_t)addr_hi << 32) | addr_lo;
+
+ abort_cmd = megasas_lookup_frame(s, abort_addr);
+ if (!abort_cmd) {
+ trace_megasas_abort_no_cmd(cmd->index, abort_ctx);
+ s->event_count++;
+ return MFI_STAT_OK;
+ }
+ if (!megasas_use_queue64(s)) {
+ abort_ctx &= (uint64_t)0xFFFFFFFF;
+ }
+ if (abort_cmd->context != abort_ctx) {
+ trace_megasas_abort_invalid_context(cmd->index, abort_cmd->context,
+ abort_cmd->index);
+ s->event_count++;
+ return MFI_STAT_ABORT_NOT_POSSIBLE;
+ }
+ trace_megasas_abort_frame(cmd->index, abort_cmd->index);
+ megasas_abort_command(abort_cmd);
+ if (!s->event_cmd || abort_cmd != s->event_cmd) {
+ s->event_cmd = NULL;
+ }
+ s->event_count++;
+ return MFI_STAT_OK;
+}
+
+static void megasas_handle_frame(MegasasState *s, uint64_t frame_addr,
+ uint32_t frame_count)
+{
+ uint8_t frame_status = MFI_STAT_INVALID_CMD;
+ uint64_t frame_context;
+ int frame_cmd;
+ MegasasCmd *cmd;
+
+ /*
+ * Always read 64bit context, top bits will be
+ * masked out if required in megasas_enqueue_frame()
+ */
+ frame_context = megasas_frame_get_context(s, frame_addr);
+
+ cmd = megasas_enqueue_frame(s, frame_addr, frame_context, frame_count);
+ if (!cmd) {
+ /* reply queue full */
+ trace_megasas_frame_busy(frame_addr);
+ megasas_frame_set_scsi_status(s, frame_addr, BUSY);
+ megasas_frame_set_cmd_status(s, frame_addr, MFI_STAT_SCSI_DONE_WITH_ERROR);
+ megasas_complete_frame(s, frame_context);
+ s->event_count++;
+ return;
+ }
+ frame_cmd = cmd->frame->header.frame_cmd;
+ switch (frame_cmd) {
+ case MFI_CMD_INIT:
+ frame_status = megasas_init_firmware(s, cmd);
+ break;
+ case MFI_CMD_DCMD:
+ frame_status = megasas_handle_dcmd(s, cmd);
+ break;
+ case MFI_CMD_ABORT:
+ frame_status = megasas_handle_abort(s, cmd);
+ break;
+ case MFI_CMD_PD_SCSI_IO:
+ case MFI_CMD_LD_SCSI_IO:
+ frame_status = megasas_handle_scsi(s, cmd, frame_cmd);
+ break;
+ case MFI_CMD_LD_READ:
+ case MFI_CMD_LD_WRITE:
+ frame_status = megasas_handle_io(s, cmd, frame_cmd);
+ break;
+ default:
+ trace_megasas_unhandled_frame_cmd(cmd->index, frame_cmd);
+ s->event_count++;
+ break;
+ }
+ if (frame_status != MFI_STAT_INVALID_STATUS) {
+ if (cmd->frame) {
+ cmd->frame->header.cmd_status = frame_status;
+ } else {
+ megasas_frame_set_cmd_status(s, frame_addr, frame_status);
+ }
+ megasas_unmap_frame(s, cmd);
+ megasas_complete_frame(s, cmd->context);
+ }
+}
+
+static uint64_t megasas_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MegasasState *s = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ MegasasBaseClass *base_class = MEGASAS_GET_CLASS(s);
+ uint32_t retval = 0;
+
+ switch (addr) {
+ case MFI_IDB:
+ retval = 0;
+ trace_megasas_mmio_readl("MFI_IDB", retval);
+ break;
+ case MFI_OMSG0:
+ case MFI_OSP0:
+ retval = (msix_present(pci_dev) ? MFI_FWSTATE_MSIX_SUPPORTED : 0) |
+ (s->fw_state & MFI_FWSTATE_MASK) |
+ ((s->fw_sge & 0xff) << 16) |
+ (s->fw_cmds & 0xFFFF);
+ trace_megasas_mmio_readl(addr == MFI_OMSG0 ? "MFI_OMSG0" : "MFI_OSP0",
+ retval);
+ break;
+ case MFI_OSTS:
+ if (megasas_intr_enabled(s) && s->doorbell) {
+ retval = base_class->osts;
+ }
+ trace_megasas_mmio_readl("MFI_OSTS", retval);
+ break;
+ case MFI_OMSK:
+ retval = s->intr_mask;
+ trace_megasas_mmio_readl("MFI_OMSK", retval);
+ break;
+ case MFI_ODCR0:
+ retval = s->doorbell ? 1 : 0;
+ trace_megasas_mmio_readl("MFI_ODCR0", retval);
+ break;
+ case MFI_DIAG:
+ retval = s->diag;
+ trace_megasas_mmio_readl("MFI_DIAG", retval);
+ break;
+ case MFI_OSP1:
+ retval = 15;
+ trace_megasas_mmio_readl("MFI_OSP1", retval);
+ break;
+ default:
+ trace_megasas_mmio_invalid_readl(addr);
+ break;
+ }
+ return retval;
+}
+
+static int adp_reset_seq[] = {0x00, 0x04, 0x0b, 0x02, 0x07, 0x0d};
+
+static void megasas_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MegasasState *s = opaque;
+ PCIDevice *pci_dev = PCI_DEVICE(s);
+ uint64_t frame_addr;
+ uint32_t frame_count;
+ int i;
+
+ switch (addr) {
+ case MFI_IDB:
+ trace_megasas_mmio_writel("MFI_IDB", val);
+ if (val & MFI_FWINIT_ABORT) {
+ /* Abort all pending cmds */
+ for (i = 0; i < s->fw_cmds; i++) {
+ megasas_abort_command(&s->frames[i]);
+ }
+ }
+ if (val & MFI_FWINIT_READY) {
+ /* move to FW READY */
+ megasas_soft_reset(s);
+ }
+ if (val & MFI_FWINIT_MFIMODE) {
+ /* discard MFIs */
+ }
+ if (val & MFI_FWINIT_STOP_ADP) {
+ /* Terminal error, stop processing */
+ s->fw_state = MFI_FWSTATE_FAULT;
+ }
+ break;
+ case MFI_OMSK:
+ trace_megasas_mmio_writel("MFI_OMSK", val);
+ s->intr_mask = val;
+ if (!megasas_intr_enabled(s) &&
+ !msi_enabled(pci_dev) &&
+ !msix_enabled(pci_dev)) {
+ trace_megasas_irq_lower();
+ pci_irq_deassert(pci_dev);
+ }
+ if (megasas_intr_enabled(s)) {
+ if (msix_enabled(pci_dev)) {
+ trace_megasas_msix_enabled(0);
+ } else if (msi_enabled(pci_dev)) {
+ trace_megasas_msi_enabled(0);
+ } else {
+ trace_megasas_intr_enabled();
+ }
+ } else {
+ trace_megasas_intr_disabled();
+ megasas_soft_reset(s);
+ }
+ break;
+ case MFI_ODCR0:
+ trace_megasas_mmio_writel("MFI_ODCR0", val);
+ s->doorbell = 0;
+ if (megasas_intr_enabled(s)) {
+ if (!msix_enabled(pci_dev) && !msi_enabled(pci_dev)) {
+ trace_megasas_irq_lower();
+ pci_irq_deassert(pci_dev);
+ }
+ }
+ break;
+ case MFI_IQPH:
+ trace_megasas_mmio_writel("MFI_IQPH", val);
+ /* Received high 32 bits of a 64 bit MFI frame address */
+ s->frame_hi = val;
+ break;
+ case MFI_IQPL:
+ trace_megasas_mmio_writel("MFI_IQPL", val);
+ /* Received low 32 bits of a 64 bit MFI frame address */
+ /* Fallthrough */
+ case MFI_IQP:
+ if (addr == MFI_IQP) {
+ trace_megasas_mmio_writel("MFI_IQP", val);
+ /* Received 64 bit MFI frame address */
+ s->frame_hi = 0;
+ }
+ frame_addr = (val & ~0x1F);
+ /* Add possible 64 bit offset */
+ frame_addr |= ((uint64_t)s->frame_hi << 32);
+ s->frame_hi = 0;
+ frame_count = (val >> 1) & 0xF;
+ megasas_handle_frame(s, frame_addr, frame_count);
+ break;
+ case MFI_SEQ:
+ trace_megasas_mmio_writel("MFI_SEQ", val);
+ /* Magic sequence to start ADP reset */
+ if (adp_reset_seq[s->adp_reset++] == val) {
+ if (s->adp_reset == 6) {
+ s->adp_reset = 0;
+ s->diag = MFI_DIAG_WRITE_ENABLE;
+ }
+ } else {
+ s->adp_reset = 0;
+ s->diag = 0;
+ }
+ break;
+ case MFI_DIAG:
+ trace_megasas_mmio_writel("MFI_DIAG", val);
+ /* ADP reset */
+ if ((s->diag & MFI_DIAG_WRITE_ENABLE) &&
+ (val & MFI_DIAG_RESET_ADP)) {
+ s->diag |= MFI_DIAG_RESET_ADP;
+ megasas_soft_reset(s);
+ s->adp_reset = 0;
+ s->diag = 0;
+ }
+ break;
+ default:
+ trace_megasas_mmio_invalid_writel(addr, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps megasas_mmio_ops = {
+ .read = megasas_mmio_read,
+ .write = megasas_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ }
+};
+
+static uint64_t megasas_port_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return megasas_mmio_read(opaque, addr & 0xff, size);
+}
+
+static void megasas_port_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ megasas_mmio_write(opaque, addr & 0xff, val, size);
+}
+
+static const MemoryRegionOps megasas_port_ops = {
+ .read = megasas_port_read,
+ .write = megasas_port_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static uint64_t megasas_queue_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void megasas_queue_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ return;
+}
+
+static const MemoryRegionOps megasas_queue_ops = {
+ .read = megasas_queue_read,
+ .write = megasas_queue_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 8,
+ .max_access_size = 8,
+ }
+};
+
+static void megasas_soft_reset(MegasasState *s)
+{
+ int i;
+ MegasasCmd *cmd;
+
+ trace_megasas_reset(s->fw_state);
+ for (i = 0; i < s->fw_cmds; i++) {
+ cmd = &s->frames[i];
+ megasas_abort_command(cmd);
+ }
+ if (s->fw_state == MFI_FWSTATE_READY) {
+ BusChild *kid;
+
+ /*
+ * The EFI firmware doesn't handle UA,
+ * so we need to clear the Power On/Reset UA
+ * after the initial reset.
+ */
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *sdev = SCSI_DEVICE(kid->child);
+
+ sdev->unit_attention = SENSE_CODE(NO_SENSE);
+ scsi_device_unit_attention_reported(sdev);
+ }
+ }
+ megasas_reset_frames(s);
+ s->reply_queue_len = s->fw_cmds;
+ s->reply_queue_pa = 0;
+ s->consumer_pa = 0;
+ s->producer_pa = 0;
+ s->fw_state = MFI_FWSTATE_READY;
+ s->doorbell = 0;
+ s->intr_mask = MEGASAS_INTR_DISABLED_MASK;
+ s->frame_hi = 0;
+ s->flags &= ~MEGASAS_MASK_USE_QUEUE64;
+ s->event_count++;
+ s->boot_event = s->event_count;
+}
+
+static void megasas_scsi_reset(DeviceState *dev)
+{
+ MegasasState *s = MEGASAS(dev);
+
+ megasas_soft_reset(s);
+}
+
+static const VMStateDescription vmstate_megasas_gen1 = {
+ .name = "megasas",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, MegasasState),
+ VMSTATE_MSIX(parent_obj, MegasasState),
+
+ VMSTATE_UINT32(fw_state, MegasasState),
+ VMSTATE_UINT32(intr_mask, MegasasState),
+ VMSTATE_UINT32(doorbell, MegasasState),
+ VMSTATE_UINT64(reply_queue_pa, MegasasState),
+ VMSTATE_UINT64(consumer_pa, MegasasState),
+ VMSTATE_UINT64(producer_pa, MegasasState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_megasas_gen2 = {
+ .name = "megasas-gen2",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, MegasasState),
+ VMSTATE_MSIX(parent_obj, MegasasState),
+
+ VMSTATE_UINT32(fw_state, MegasasState),
+ VMSTATE_UINT32(intr_mask, MegasasState),
+ VMSTATE_UINT32(doorbell, MegasasState),
+ VMSTATE_UINT64(reply_queue_pa, MegasasState),
+ VMSTATE_UINT64(consumer_pa, MegasasState),
+ VMSTATE_UINT64(producer_pa, MegasasState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void megasas_scsi_uninit(PCIDevice *d)
+{
+ MegasasState *s = MEGASAS(d);
+
+ if (megasas_use_msix(s)) {
+ msix_uninit(d, &s->mmio_io, &s->mmio_io);
+ }
+ msi_uninit(d);
+}
+
+static const struct SCSIBusInfo megasas_scsi_info = {
+ .tcq = true,
+ .max_target = MFI_MAX_LD,
+ .max_lun = 255,
+
+ .transfer_data = megasas_xfer_complete,
+ .get_sg_list = megasas_get_sg_list,
+ .complete = megasas_command_complete,
+ .cancel = megasas_command_cancelled,
+};
+
+static void megasas_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ MegasasState *s = MEGASAS(dev);
+ MegasasBaseClass *b = MEGASAS_GET_CLASS(s);
+ uint8_t *pci_conf;
+ int i, bar_type;
+ Error *err = NULL;
+ int ret;
+
+ pci_conf = dev->config;
+
+ /* PCI latency timer = 0 */
+ pci_conf[PCI_LATENCY_TIMER] = 0;
+ /* Interrupt pin 1 */
+ pci_conf[PCI_INTERRUPT_PIN] = 0x01;
+
+ if (s->msi != ON_OFF_AUTO_OFF) {
+ ret = msi_init(dev, 0x50, 1, true, false, &err);
+ /* Any error other than -ENOTSUP(board's MSI support is broken)
+ * is a programming error */
+ assert(!ret || ret == -ENOTSUP);
+ if (ret && s->msi == ON_OFF_AUTO_ON) {
+ /* Can't satisfy user's explicit msi=on request, fail */
+ error_append_hint(&err, "You have to use msi=auto (default) or "
+ "msi=off with this machine type.\n");
+ error_propagate(errp, err);
+ return;
+ } else if (ret) {
+ /* With msi=auto, we fall back to MSI off silently */
+ s->msi = ON_OFF_AUTO_OFF;
+ error_free(err);
+ }
+ }
+
+ memory_region_init_io(&s->mmio_io, OBJECT(s), &megasas_mmio_ops, s,
+ "megasas-mmio", 0x4000);
+ memory_region_init_io(&s->port_io, OBJECT(s), &megasas_port_ops, s,
+ "megasas-io", 256);
+ memory_region_init_io(&s->queue_io, OBJECT(s), &megasas_queue_ops, s,
+ "megasas-queue", 0x40000);
+
+ if (megasas_use_msix(s) &&
+ msix_init(dev, 15, &s->mmio_io, b->mmio_bar, 0x2000,
+ &s->mmio_io, b->mmio_bar, 0x3800, 0x68, NULL)) {
+ /* TODO: check msix_init's error, and should fail on msix=on */
+ s->msix = ON_OFF_AUTO_OFF;
+ }
+
+ if (pci_is_express(dev)) {
+ pcie_endpoint_cap_init(dev, 0xa0);
+ }
+
+ bar_type = PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64;
+ pci_register_bar(dev, b->ioport_bar,
+ PCI_BASE_ADDRESS_SPACE_IO, &s->port_io);
+ pci_register_bar(dev, b->mmio_bar, bar_type, &s->mmio_io);
+ pci_register_bar(dev, 3, bar_type, &s->queue_io);
+
+ if (megasas_use_msix(s)) {
+ msix_vector_use(dev, 0);
+ }
+
+ s->fw_state = MFI_FWSTATE_READY;
+ if (!s->sas_addr) {
+ s->sas_addr = ((NAA_LOCALLY_ASSIGNED_ID << 24) |
+ IEEE_COMPANY_LOCALLY_ASSIGNED) << 36;
+ s->sas_addr |= pci_dev_bus_num(dev) << 16;
+ s->sas_addr |= PCI_SLOT(dev->devfn) << 8;
+ s->sas_addr |= PCI_FUNC(dev->devfn);
+ }
+ if (!s->hba_serial) {
+ s->hba_serial = g_strdup(MEGASAS_HBA_SERIAL);
+ }
+ if (s->fw_sge >= MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE) {
+ s->fw_sge = MEGASAS_MAX_SGE - MFI_PASS_FRAME_SIZE;
+ } else if (s->fw_sge >= 128 - MFI_PASS_FRAME_SIZE) {
+ s->fw_sge = 128 - MFI_PASS_FRAME_SIZE;
+ } else {
+ s->fw_sge = 64 - MFI_PASS_FRAME_SIZE;
+ }
+ if (s->fw_cmds > MEGASAS_MAX_FRAMES) {
+ s->fw_cmds = MEGASAS_MAX_FRAMES;
+ }
+ trace_megasas_init(s->fw_sge, s->fw_cmds,
+ megasas_is_jbod(s) ? "jbod" : "raid");
+
+ if (megasas_is_jbod(s)) {
+ s->fw_luns = MFI_MAX_SYS_PDS;
+ } else {
+ s->fw_luns = MFI_MAX_LD;
+ }
+ s->producer_pa = 0;
+ s->consumer_pa = 0;
+ for (i = 0; i < s->fw_cmds; i++) {
+ s->frames[i].index = i;
+ s->frames[i].context = -1;
+ s->frames[i].pa = 0;
+ s->frames[i].state = s;
+ }
+
+ scsi_bus_init(&s->bus, sizeof(s->bus), DEVICE(dev), &megasas_scsi_info);
+}
+
+static Property megasas_properties_gen1[] = {
+ DEFINE_PROP_UINT32("max_sge", MegasasState, fw_sge,
+ MEGASAS_DEFAULT_SGE),
+ DEFINE_PROP_UINT32("max_cmds", MegasasState, fw_cmds,
+ MEGASAS_DEFAULT_FRAMES),
+ DEFINE_PROP_STRING("hba_serial", MegasasState, hba_serial),
+ DEFINE_PROP_UINT64("sas_address", MegasasState, sas_addr, 0),
+ DEFINE_PROP_ON_OFF_AUTO("msi", MegasasState, msi, ON_OFF_AUTO_AUTO),
+ DEFINE_PROP_ON_OFF_AUTO("msix", MegasasState, msix, ON_OFF_AUTO_AUTO),
+ DEFINE_PROP_BIT("use_jbod", MegasasState, flags,
+ MEGASAS_FLAG_USE_JBOD, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property megasas_properties_gen2[] = {
+ DEFINE_PROP_UINT32("max_sge", MegasasState, fw_sge,
+ MEGASAS_DEFAULT_SGE),
+ DEFINE_PROP_UINT32("max_cmds", MegasasState, fw_cmds,
+ MEGASAS_GEN2_DEFAULT_FRAMES),
+ DEFINE_PROP_STRING("hba_serial", MegasasState, hba_serial),
+ DEFINE_PROP_UINT64("sas_address", MegasasState, sas_addr, 0),
+ DEFINE_PROP_ON_OFF_AUTO("msi", MegasasState, msi, ON_OFF_AUTO_AUTO),
+ DEFINE_PROP_ON_OFF_AUTO("msix", MegasasState, msix, ON_OFF_AUTO_AUTO),
+ DEFINE_PROP_BIT("use_jbod", MegasasState, flags,
+ MEGASAS_FLAG_USE_JBOD, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+typedef struct MegasasInfo {
+ const char *name;
+ const char *desc;
+ const char *product_name;
+ const char *product_version;
+ uint16_t device_id;
+ uint16_t subsystem_id;
+ int ioport_bar;
+ int mmio_bar;
+ int osts;
+ const VMStateDescription *vmsd;
+ Property *props;
+ InterfaceInfo *interfaces;
+} MegasasInfo;
+
+static struct MegasasInfo megasas_devices[] = {
+ {
+ .name = TYPE_MEGASAS_GEN1,
+ .desc = "LSI MegaRAID SAS 1078",
+ .product_name = "LSI MegaRAID SAS 8708EM2",
+ .product_version = MEGASAS_VERSION_GEN1,
+ .device_id = PCI_DEVICE_ID_LSI_SAS1078,
+ .subsystem_id = 0x1013,
+ .ioport_bar = 2,
+ .mmio_bar = 0,
+ .osts = MFI_1078_RM | 1,
+ .vmsd = &vmstate_megasas_gen1,
+ .props = megasas_properties_gen1,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+ },{
+ .name = TYPE_MEGASAS_GEN2,
+ .desc = "LSI MegaRAID SAS 2108",
+ .product_name = "LSI MegaRAID SAS 9260-8i",
+ .product_version = MEGASAS_VERSION_GEN2,
+ .device_id = PCI_DEVICE_ID_LSI_SAS0079,
+ .subsystem_id = 0x9261,
+ .ioport_bar = 0,
+ .mmio_bar = 1,
+ .osts = MFI_GEN2_RM,
+ .vmsd = &vmstate_megasas_gen2,
+ .props = megasas_properties_gen2,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_PCIE_DEVICE },
+ { }
+ },
+ }
+};
+
+static void megasas_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
+ MegasasBaseClass *e = MEGASAS_CLASS(oc);
+ const MegasasInfo *info = data;
+
+ pc->realize = megasas_scsi_realize;
+ pc->exit = megasas_scsi_uninit;
+ pc->vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ pc->device_id = info->device_id;
+ pc->subsystem_vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ pc->subsystem_id = info->subsystem_id;
+ pc->class_id = PCI_CLASS_STORAGE_RAID;
+ e->mmio_bar = info->mmio_bar;
+ e->ioport_bar = info->ioport_bar;
+ e->osts = info->osts;
+ e->product_name = info->product_name;
+ e->product_version = info->product_version;
+ device_class_set_props(dc, info->props);
+ dc->reset = megasas_scsi_reset;
+ dc->vmsd = info->vmsd;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ dc->desc = info->desc;
+}
+
+static const TypeInfo megasas_info = {
+ .name = TYPE_MEGASAS_BASE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MegasasState),
+ .class_size = sizeof(MegasasBaseClass),
+ .abstract = true,
+};
+
+static void megasas_register_types(void)
+{
+ int i;
+
+ type_register_static(&megasas_info);
+ for (i = 0; i < ARRAY_SIZE(megasas_devices); i++) {
+ const MegasasInfo *info = &megasas_devices[i];
+ TypeInfo type_info = {};
+
+ type_info.name = info->name;
+ type_info.parent = TYPE_MEGASAS_BASE;
+ type_info.class_data = (void *)info;
+ type_info.class_init = megasas_class_init;
+ type_info.interfaces = info->interfaces;
+
+ type_register(&type_info);
+ }
+}
+
+type_init(megasas_register_types)
diff --git a/hw/scsi/meson.build b/hw/scsi/meson.build
new file mode 100644
index 00000000..923a34f3
--- /dev/null
+++ b/hw/scsi/meson.build
@@ -0,0 +1,26 @@
+scsi_ss = ss.source_set()
+scsi_ss.add(files(
+ 'emulation.c',
+ 'scsi-bus.c',
+ 'scsi-disk.c',
+ 'scsi-generic.c',
+))
+scsi_ss.add(when: 'CONFIG_ESP', if_true: files('esp.c'))
+scsi_ss.add(when: 'CONFIG_ESP_PCI', if_true: files('esp-pci.c'))
+scsi_ss.add(when: 'CONFIG_LSI_SCSI_PCI', if_true: files('lsi53c895a.c'))
+scsi_ss.add(when: 'CONFIG_MEGASAS_SCSI_PCI', if_true: files('megasas.c'))
+scsi_ss.add(when: 'CONFIG_MPTSAS_SCSI_PCI', if_true: files('mptsas.c', 'mptconfig.c', 'mptendian.c'))
+scsi_ss.add(when: 'CONFIG_VMW_PVSCSI_SCSI_PCI', if_true: files('vmw_pvscsi.c'))
+softmmu_ss.add_all(when: 'CONFIG_SCSI', if_true: scsi_ss)
+
+specific_scsi_ss = ss.source_set()
+
+virtio_scsi_ss = ss.source_set()
+virtio_scsi_ss.add(files('virtio-scsi.c', 'virtio-scsi-dataplane.c'))
+virtio_scsi_ss.add(when: 'CONFIG_VHOST_SCSI', if_true: files('vhost-scsi-common.c', 'vhost-scsi.c'))
+virtio_scsi_ss.add(when: 'CONFIG_VHOST_USER_SCSI', if_true: files('vhost-scsi-common.c', 'vhost-user-scsi.c'))
+specific_scsi_ss.add_all(when: 'CONFIG_VIRTIO_SCSI', if_true: virtio_scsi_ss)
+
+specific_scsi_ss.add(when: 'CONFIG_SPAPR_VSCSI', if_true: files('spapr_vscsi.c'))
+
+specific_ss.add_all(when: 'CONFIG_SCSI', if_true: specific_scsi_ss)
diff --git a/hw/scsi/mfi.h b/hw/scsi/mfi.h
new file mode 100644
index 00000000..0b4ee53d
--- /dev/null
+++ b/hw/scsi/mfi.h
@@ -0,0 +1,1272 @@
+/*
+ * NetBSD header file, copied from
+ * http://gitorious.org/freebsd/freebsd/blobs/HEAD/sys/dev/mfi/mfireg.h
+ */
+/*-
+ * Copyright (c) 2006 IronPort Systems
+ * Copyright (c) 2007 LSI Corp.
+ * Copyright (c) 2007 Rajesh Prabhakaran.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef SCSI_MFI_H
+#define SCSI_MFI_H
+
+/*
+ * MegaRAID SAS MFI firmware definitions
+ */
+
+/*
+ * Start with the register set. All registers are 32 bits wide.
+ * The usual Intel IOP style setup.
+ */
+#define MFI_IMSG0 0x10 /* Inbound message 0 */
+#define MFI_IMSG1 0x14 /* Inbound message 1 */
+#define MFI_OMSG0 0x18 /* Outbound message 0 */
+#define MFI_OMSG1 0x1c /* Outbound message 1 */
+#define MFI_IDB 0x20 /* Inbound doorbell */
+#define MFI_ISTS 0x24 /* Inbound interrupt status */
+#define MFI_IMSK 0x28 /* Inbound interrupt mask */
+#define MFI_ODB 0x2c /* Outbound doorbell */
+#define MFI_OSTS 0x30 /* Outbound interrupt status */
+#define MFI_OMSK 0x34 /* Outbound interrupt mask */
+#define MFI_IQP 0x40 /* Inbound queue port */
+#define MFI_OQP 0x44 /* Outbound queue port */
+
+/*
+ * 1078 specific related register
+ */
+#define MFI_ODR0 0x9c /* outbound doorbell register0 */
+#define MFI_ODCR0 0xa0 /* outbound doorbell clear register0 */
+#define MFI_OSP0 0xb0 /* outbound scratch pad0 */
+#define MFI_OSP1 0xb4 /* outbound scratch pad1 */
+#define MFI_IQPL 0xc0 /* Inbound queue port (low bytes) */
+#define MFI_IQPH 0xc4 /* Inbound queue port (high bytes) */
+#define MFI_DIAG 0xf8 /* Host diag */
+#define MFI_SEQ 0xfc /* Sequencer offset */
+#define MFI_1078_EIM 0x80000004 /* 1078 enable intrrupt mask */
+#define MFI_RMI 0x2 /* reply message interrupt */
+#define MFI_1078_RM 0x80000000 /* reply 1078 message interrupt */
+#define MFI_ODC 0x4 /* outbound doorbell change interrupt */
+
+/*
+ * gen2 specific changes
+ */
+#define MFI_GEN2_EIM 0x00000005 /* gen2 enable interrupt mask */
+#define MFI_GEN2_RM 0x00000001 /* reply gen2 message interrupt */
+
+/*
+ * skinny specific changes
+ */
+#define MFI_SKINNY_IDB 0x00 /* Inbound doorbell is at 0x00 for skinny */
+#define MFI_SKINNY_RM 0x00000001 /* reply skinny message interrupt */
+
+/* Bits for MFI_OSTS */
+#define MFI_OSTS_INTR_VALID 0x00000002
+
+/*
+ * Firmware state values. Found in OMSG0 during initialization.
+ */
+#define MFI_FWSTATE_MASK 0xf0000000
+#define MFI_FWSTATE_UNDEFINED 0x00000000
+#define MFI_FWSTATE_BB_INIT 0x10000000
+#define MFI_FWSTATE_FW_INIT 0x40000000
+#define MFI_FWSTATE_WAIT_HANDSHAKE 0x60000000
+#define MFI_FWSTATE_FW_INIT_2 0x70000000
+#define MFI_FWSTATE_DEVICE_SCAN 0x80000000
+#define MFI_FWSTATE_BOOT_MSG_PENDING 0x90000000
+#define MFI_FWSTATE_FLUSH_CACHE 0xa0000000
+#define MFI_FWSTATE_READY 0xb0000000
+#define MFI_FWSTATE_OPERATIONAL 0xc0000000
+#define MFI_FWSTATE_FAULT 0xf0000000
+#define MFI_FWSTATE_MAXSGL_MASK 0x00ff0000
+#define MFI_FWSTATE_MAXCMD_MASK 0x0000ffff
+#define MFI_FWSTATE_MSIX_SUPPORTED 0x04000000
+#define MFI_FWSTATE_HOSTMEMREQD_MASK 0x08000000
+
+/*
+ * Control bits to drive the card to ready state. These go into the IDB
+ * register.
+ */
+#define MFI_FWINIT_ABORT 0x00000001 /* Abort all pending commands */
+#define MFI_FWINIT_READY 0x00000002 /* Move from operational to ready */
+#define MFI_FWINIT_MFIMODE 0x00000004 /* unknown */
+#define MFI_FWINIT_CLEAR_HANDSHAKE 0x00000008 /* Respond to WAIT_HANDSHAKE */
+#define MFI_FWINIT_HOTPLUG 0x00000010
+#define MFI_FWINIT_STOP_ADP 0x00000020 /* Move to operational, stop */
+#define MFI_FWINIT_ADP_RESET 0x00000040 /* Reset ADP */
+
+/*
+ * Control bits for the DIAG register
+ */
+#define MFI_DIAG_WRITE_ENABLE 0x00000080
+#define MFI_DIAG_RESET_ADP 0x00000004
+
+/* MFI Commands */
+typedef enum {
+ MFI_CMD_INIT = 0x00,
+ MFI_CMD_LD_READ,
+ MFI_CMD_LD_WRITE,
+ MFI_CMD_LD_SCSI_IO,
+ MFI_CMD_PD_SCSI_IO,
+ MFI_CMD_DCMD,
+ MFI_CMD_ABORT,
+ MFI_CMD_SMP,
+ MFI_CMD_STP
+} mfi_cmd_t;
+
+/* Direct commands */
+typedef enum {
+ MFI_DCMD_CTRL_MFI_HOST_MEM_ALLOC = 0x0100e100,
+ MFI_DCMD_CTRL_GET_INFO = 0x01010000,
+ MFI_DCMD_CTRL_GET_PROPERTIES = 0x01020100,
+ MFI_DCMD_CTRL_SET_PROPERTIES = 0x01020200,
+ MFI_DCMD_CTRL_ALARM = 0x01030000,
+ MFI_DCMD_CTRL_ALARM_GET = 0x01030100,
+ MFI_DCMD_CTRL_ALARM_ENABLE = 0x01030200,
+ MFI_DCMD_CTRL_ALARM_DISABLE = 0x01030300,
+ MFI_DCMD_CTRL_ALARM_SILENCE = 0x01030400,
+ MFI_DCMD_CTRL_ALARM_TEST = 0x01030500,
+ MFI_DCMD_CTRL_EVENT_GETINFO = 0x01040100,
+ MFI_DCMD_CTRL_EVENT_CLEAR = 0x01040200,
+ MFI_DCMD_CTRL_EVENT_GET = 0x01040300,
+ MFI_DCMD_CTRL_EVENT_COUNT = 0x01040400,
+ MFI_DCMD_CTRL_EVENT_WAIT = 0x01040500,
+ MFI_DCMD_CTRL_SHUTDOWN = 0x01050000,
+ MFI_DCMD_HIBERNATE_STANDBY = 0x01060000,
+ MFI_DCMD_CTRL_GET_TIME = 0x01080101,
+ MFI_DCMD_CTRL_SET_TIME = 0x01080102,
+ MFI_DCMD_CTRL_BIOS_DATA_GET = 0x010c0100,
+ MFI_DCMD_CTRL_BIOS_DATA_SET = 0x010c0200,
+ MFI_DCMD_CTRL_FACTORY_DEFAULTS = 0x010d0000,
+ MFI_DCMD_CTRL_MFC_DEFAULTS_GET = 0x010e0201,
+ MFI_DCMD_CTRL_MFC_DEFAULTS_SET = 0x010e0202,
+ MFI_DCMD_CTRL_CACHE_FLUSH = 0x01101000,
+ MFI_DCMD_PD_GET_LIST = 0x02010000,
+ MFI_DCMD_PD_LIST_QUERY = 0x02010100,
+ MFI_DCMD_PD_GET_INFO = 0x02020000,
+ MFI_DCMD_PD_STATE_SET = 0x02030100,
+ MFI_DCMD_PD_REBUILD = 0x02040100,
+ MFI_DCMD_PD_BLINK = 0x02070100,
+ MFI_DCMD_PD_UNBLINK = 0x02070200,
+ MFI_DCMD_LD_GET_LIST = 0x03010000,
+ MFI_DCMD_LD_LIST_QUERY = 0x03010100,
+ MFI_DCMD_LD_GET_INFO = 0x03020000,
+ MFI_DCMD_LD_GET_PROP = 0x03030000,
+ MFI_DCMD_LD_SET_PROP = 0x03040000,
+ MFI_DCMD_LD_DELETE = 0x03090000,
+ MFI_DCMD_CFG_READ = 0x04010000,
+ MFI_DCMD_CFG_ADD = 0x04020000,
+ MFI_DCMD_CFG_CLEAR = 0x04030000,
+ MFI_DCMD_CFG_FOREIGN_READ = 0x04060100,
+ MFI_DCMD_CFG_FOREIGN_IMPORT = 0x04060400,
+ MFI_DCMD_BBU_STATUS = 0x05010000,
+ MFI_DCMD_BBU_CAPACITY_INFO = 0x05020000,
+ MFI_DCMD_BBU_DESIGN_INFO = 0x05030000,
+ MFI_DCMD_BBU_PROP_GET = 0x05050100,
+ MFI_DCMD_CLUSTER = 0x08000000,
+ MFI_DCMD_CLUSTER_RESET_ALL = 0x08010100,
+ MFI_DCMD_CLUSTER_RESET_LD = 0x08010200
+} mfi_dcmd_t;
+
+/* Modifiers for MFI_DCMD_CTRL_FLUSHCACHE */
+#define MFI_FLUSHCACHE_CTRL 0x01
+#define MFI_FLUSHCACHE_DISK 0x02
+
+/* Modifiers for MFI_DCMD_CTRL_SHUTDOWN */
+#define MFI_SHUTDOWN_SPINDOWN 0x01
+
+/*
+ * MFI Frame flags
+ */
+typedef enum {
+ MFI_FRAME_DONT_POST_IN_REPLY_QUEUE = 0x0001,
+ MFI_FRAME_SGL64 = 0x0002,
+ MFI_FRAME_SENSE64 = 0x0004,
+ MFI_FRAME_DIR_WRITE = 0x0008,
+ MFI_FRAME_DIR_READ = 0x0010,
+ MFI_FRAME_IEEE_SGL = 0x0020,
+} mfi_frame_flags;
+
+/* MFI Status codes */
+typedef enum {
+ MFI_STAT_OK = 0x00,
+ MFI_STAT_INVALID_CMD,
+ MFI_STAT_INVALID_DCMD,
+ MFI_STAT_INVALID_PARAMETER,
+ MFI_STAT_INVALID_SEQUENCE_NUMBER,
+ MFI_STAT_ABORT_NOT_POSSIBLE,
+ MFI_STAT_APP_HOST_CODE_NOT_FOUND,
+ MFI_STAT_APP_IN_USE,
+ MFI_STAT_APP_NOT_INITIALIZED,
+ MFI_STAT_ARRAY_INDEX_INVALID,
+ MFI_STAT_ARRAY_ROW_NOT_EMPTY,
+ MFI_STAT_CONFIG_RESOURCE_CONFLICT,
+ MFI_STAT_DEVICE_NOT_FOUND,
+ MFI_STAT_DRIVE_TOO_SMALL,
+ MFI_STAT_FLASH_ALLOC_FAIL,
+ MFI_STAT_FLASH_BUSY,
+ MFI_STAT_FLASH_ERROR = 0x10,
+ MFI_STAT_FLASH_IMAGE_BAD,
+ MFI_STAT_FLASH_IMAGE_INCOMPLETE,
+ MFI_STAT_FLASH_NOT_OPEN,
+ MFI_STAT_FLASH_NOT_STARTED,
+ MFI_STAT_FLUSH_FAILED,
+ MFI_STAT_HOST_CODE_NOT_FOUNT,
+ MFI_STAT_LD_CC_IN_PROGRESS,
+ MFI_STAT_LD_INIT_IN_PROGRESS,
+ MFI_STAT_LD_LBA_OUT_OF_RANGE,
+ MFI_STAT_LD_MAX_CONFIGURED,
+ MFI_STAT_LD_NOT_OPTIMAL,
+ MFI_STAT_LD_RBLD_IN_PROGRESS,
+ MFI_STAT_LD_RECON_IN_PROGRESS,
+ MFI_STAT_LD_WRONG_RAID_LEVEL,
+ MFI_STAT_MAX_SPARES_EXCEEDED,
+ MFI_STAT_MEMORY_NOT_AVAILABLE = 0x20,
+ MFI_STAT_MFC_HW_ERROR,
+ MFI_STAT_NO_HW_PRESENT,
+ MFI_STAT_NOT_FOUND,
+ MFI_STAT_NOT_IN_ENCL,
+ MFI_STAT_PD_CLEAR_IN_PROGRESS,
+ MFI_STAT_PD_TYPE_WRONG,
+ MFI_STAT_PR_DISABLED,
+ MFI_STAT_ROW_INDEX_INVALID,
+ MFI_STAT_SAS_CONFIG_INVALID_ACTION,
+ MFI_STAT_SAS_CONFIG_INVALID_DATA,
+ MFI_STAT_SAS_CONFIG_INVALID_PAGE,
+ MFI_STAT_SAS_CONFIG_INVALID_TYPE,
+ MFI_STAT_SCSI_DONE_WITH_ERROR,
+ MFI_STAT_SCSI_IO_FAILED,
+ MFI_STAT_SCSI_RESERVATION_CONFLICT,
+ MFI_STAT_SHUTDOWN_FAILED = 0x30,
+ MFI_STAT_TIME_NOT_SET,
+ MFI_STAT_WRONG_STATE,
+ MFI_STAT_LD_OFFLINE,
+ MFI_STAT_PEER_NOTIFICATION_REJECTED,
+ MFI_STAT_PEER_NOTIFICATION_FAILED,
+ MFI_STAT_RESERVATION_IN_PROGRESS,
+ MFI_STAT_I2C_ERRORS_DETECTED,
+ MFI_STAT_PCI_ERRORS_DETECTED,
+ MFI_STAT_DIAG_FAILED,
+ MFI_STAT_BOOT_MSG_PENDING,
+ MFI_STAT_FOREIGN_CONFIG_INCOMPLETE,
+ MFI_STAT_INVALID_SGL,
+ MFI_STAT_UNSUPPORTED_HW,
+ MFI_STAT_CC_SCHEDULE_DISABLED,
+ MFI_STAT_PD_COPYBACK_IN_PROGRESS,
+ MFI_STAT_MULTIPLE_PDS_IN_ARRAY = 0x40,
+ MFI_STAT_FW_DOWNLOAD_ERROR,
+ MFI_STAT_FEATURE_SECURITY_NOT_ENABLED,
+ MFI_STAT_LOCK_KEY_ALREADY_EXISTS,
+ MFI_STAT_LOCK_KEY_BACKUP_NOT_ALLOWED,
+ MFI_STAT_LOCK_KEY_VERIFY_NOT_ALLOWED,
+ MFI_STAT_LOCK_KEY_VERIFY_FAILED,
+ MFI_STAT_LOCK_KEY_REKEY_NOT_ALLOWED,
+ MFI_STAT_LOCK_KEY_INVALID,
+ MFI_STAT_LOCK_KEY_ESCROW_INVALID,
+ MFI_STAT_LOCK_KEY_BACKUP_REQUIRED,
+ MFI_STAT_SECURE_LD_EXISTS,
+ MFI_STAT_LD_SECURE_NOT_ALLOWED,
+ MFI_STAT_REPROVISION_NOT_ALLOWED,
+ MFI_STAT_PD_SECURITY_TYPE_WRONG,
+ MFI_STAT_LD_ENCRYPTION_TYPE_INVALID,
+ MFI_STAT_CONFIG_FDE_NON_FDE_MIX_NOT_ALLOWED = 0x50,
+ MFI_STAT_CONFIG_LD_ENCRYPTION_TYPE_MIX_NOT_ALLOWED,
+ MFI_STAT_SECRET_KEY_NOT_ALLOWED,
+ MFI_STAT_PD_HW_ERRORS_DETECTED,
+ MFI_STAT_LD_CACHE_PINNED,
+ MFI_STAT_POWER_STATE_SET_IN_PROGRESS,
+ MFI_STAT_POWER_STATE_SET_BUSY,
+ MFI_STAT_POWER_STATE_WRONG,
+ MFI_STAT_PR_NO_AVAILABLE_PD_FOUND,
+ MFI_STAT_CTRL_RESET_REQUIRED,
+ MFI_STAT_LOCK_KEY_EKM_NO_BOOT_AGENT,
+ MFI_STAT_SNAP_NO_SPACE,
+ MFI_STAT_SNAP_PARTIAL_FAILURE,
+ MFI_STAT_UPGRADE_KEY_INCOMPATIBLE,
+ MFI_STAT_PFK_INCOMPATIBLE,
+ MFI_STAT_PD_MAX_UNCONFIGURED,
+ MFI_STAT_IO_METRICS_DISABLED = 0x60,
+ MFI_STAT_AEC_NOT_STOPPED,
+ MFI_STAT_PI_TYPE_WRONG,
+ MFI_STAT_LD_PD_PI_INCOMPATIBLE,
+ MFI_STAT_PI_NOT_ENABLED,
+ MFI_STAT_LD_BLOCK_SIZE_MISMATCH,
+ MFI_STAT_INVALID_STATUS = 0xFF
+} mfi_status_t;
+
+/* Event classes */
+typedef enum {
+ MFI_EVT_CLASS_DEBUG = -2,
+ MFI_EVT_CLASS_PROGRESS = -1,
+ MFI_EVT_CLASS_INFO = 0,
+ MFI_EVT_CLASS_WARNING = 1,
+ MFI_EVT_CLASS_CRITICAL = 2,
+ MFI_EVT_CLASS_FATAL = 3,
+ MFI_EVT_CLASS_DEAD = 4
+} mfi_evt_class_t;
+
+/* Event locales */
+typedef enum {
+ MFI_EVT_LOCALE_LD = 0x0001,
+ MFI_EVT_LOCALE_PD = 0x0002,
+ MFI_EVT_LOCALE_ENCL = 0x0004,
+ MFI_EVT_LOCALE_BBU = 0x0008,
+ MFI_EVT_LOCALE_SAS = 0x0010,
+ MFI_EVT_LOCALE_CTRL = 0x0020,
+ MFI_EVT_LOCALE_CONFIG = 0x0040,
+ MFI_EVT_LOCALE_CLUSTER = 0x0080,
+ MFI_EVT_LOCALE_ALL = 0xffff
+} mfi_evt_locale_t;
+
+/* Event args */
+typedef enum {
+ MR_EVT_ARGS_NONE = 0x00,
+ MR_EVT_ARGS_CDB_SENSE,
+ MR_EVT_ARGS_LD,
+ MR_EVT_ARGS_LD_COUNT,
+ MR_EVT_ARGS_LD_LBA,
+ MR_EVT_ARGS_LD_OWNER,
+ MR_EVT_ARGS_LD_LBA_PD_LBA,
+ MR_EVT_ARGS_LD_PROG,
+ MR_EVT_ARGS_LD_STATE,
+ MR_EVT_ARGS_LD_STRIP,
+ MR_EVT_ARGS_PD,
+ MR_EVT_ARGS_PD_ERR,
+ MR_EVT_ARGS_PD_LBA,
+ MR_EVT_ARGS_PD_LBA_LD,
+ MR_EVT_ARGS_PD_PROG,
+ MR_EVT_ARGS_PD_STATE,
+ MR_EVT_ARGS_PCI,
+ MR_EVT_ARGS_RATE,
+ MR_EVT_ARGS_STR,
+ MR_EVT_ARGS_TIME,
+ MR_EVT_ARGS_ECC,
+ MR_EVT_ARGS_LD_PROP,
+ MR_EVT_ARGS_PD_SPARE,
+ MR_EVT_ARGS_PD_INDEX,
+ MR_EVT_ARGS_DIAG_PASS,
+ MR_EVT_ARGS_DIAG_FAIL,
+ MR_EVT_ARGS_PD_LBA_LBA,
+ MR_EVT_ARGS_PORT_PHY,
+ MR_EVT_ARGS_PD_MISSING,
+ MR_EVT_ARGS_PD_ADDRESS,
+ MR_EVT_ARGS_BITMAP,
+ MR_EVT_ARGS_CONNECTOR,
+ MR_EVT_ARGS_PD_PD,
+ MR_EVT_ARGS_PD_FRU,
+ MR_EVT_ARGS_PD_PATHINFO,
+ MR_EVT_ARGS_PD_POWER_STATE,
+ MR_EVT_ARGS_GENERIC,
+} mfi_evt_args;
+
+/* Event codes */
+#define MR_EVT_CFG_CLEARED 0x0004
+#define MR_EVT_CTRL_SHUTDOWN 0x002a
+#define MR_EVT_LD_STATE_CHANGE 0x0051
+#define MR_EVT_PD_INSERTED 0x005b
+#define MR_EVT_PD_REMOVED 0x0070
+#define MR_EVT_PD_STATE_CHANGED 0x0072
+#define MR_EVT_LD_CREATED 0x008a
+#define MR_EVT_LD_DELETED 0x008b
+#define MR_EVT_FOREIGN_CFG_IMPORTED 0x00db
+#define MR_EVT_LD_OFFLINE 0x00fc
+#define MR_EVT_CTRL_HOST_BUS_SCAN_REQUESTED 0x0152
+
+typedef enum {
+ MR_LD_CACHE_WRITE_BACK = 0x01,
+ MR_LD_CACHE_WRITE_ADAPTIVE = 0x02,
+ MR_LD_CACHE_READ_AHEAD = 0x04,
+ MR_LD_CACHE_READ_ADAPTIVE = 0x08,
+ MR_LD_CACHE_WRITE_CACHE_BAD_BBU = 0x10,
+ MR_LD_CACHE_ALLOW_WRITE_CACHE = 0x20,
+ MR_LD_CACHE_ALLOW_READ_CACHE = 0x40
+} mfi_ld_cache;
+
+typedef enum {
+ MR_PD_CACHE_UNCHANGED = 0,
+ MR_PD_CACHE_ENABLE = 1,
+ MR_PD_CACHE_DISABLE = 2
+} mfi_pd_cache;
+
+typedef enum {
+ MR_PD_QUERY_TYPE_ALL = 0,
+ MR_PD_QUERY_TYPE_STATE = 1,
+ MR_PD_QUERY_TYPE_POWER_STATE = 2,
+ MR_PD_QUERY_TYPE_MEDIA_TYPE = 3,
+ MR_PD_QUERY_TYPE_SPEED = 4,
+ MR_PD_QUERY_TYPE_EXPOSED_TO_HOST = 5, /*query for system drives */
+} mfi_pd_query_type;
+
+typedef enum {
+ MR_LD_QUERY_TYPE_ALL = 0,
+ MR_LD_QUERY_TYPE_EXPOSED_TO_HOST = 1,
+ MR_LD_QUERY_TYPE_USED_TGT_IDS = 2,
+ MR_LD_QUERY_TYPE_CLUSTER_ACCESS = 3,
+ MR_LD_QUERY_TYPE_CLUSTER_LOCALE = 4,
+} mfi_ld_query_type;
+
+/*
+ * Other propertities and definitions
+ */
+#define MFI_MAX_PD_CHANNELS 2
+#define MFI_MAX_LD_CHANNELS 2
+#define MFI_MAX_CHANNELS (MFI_MAX_PD_CHANNELS + MFI_MAX_LD_CHANNELS)
+#define MFI_MAX_CHANNEL_DEVS 128
+#define MFI_DEFAULT_ID -1
+#define MFI_MAX_LUN 8
+#define MFI_MAX_LD 64
+
+#define MFI_FRAME_SIZE 64
+#define MFI_MBOX_SIZE 12
+
+/* Firmware flashing can take 40s */
+#define MFI_POLL_TIMEOUT_SECS 50
+
+/* Allow for speedier math calculations */
+#define MFI_SECTOR_LEN 512
+
+/* Scatter Gather elements */
+struct mfi_sg32 {
+ uint32_t addr;
+ uint32_t len;
+} QEMU_PACKED;
+
+struct mfi_sg64 {
+ uint64_t addr;
+ uint32_t len;
+} QEMU_PACKED;
+
+struct mfi_sg_skinny {
+ uint64_t addr;
+ uint32_t len;
+ uint32_t flag;
+} QEMU_PACKED;
+
+union mfi_sgl {
+ struct mfi_sg32 sg32[1];
+ struct mfi_sg64 sg64[1];
+ struct mfi_sg_skinny sg_skinny[1];
+} QEMU_PACKED;
+
+/* Message frames. All messages have a common header */
+struct mfi_frame_header {
+ uint8_t frame_cmd;
+ uint8_t sense_len;
+ uint8_t cmd_status;
+ uint8_t scsi_status;
+ uint8_t target_id;
+ uint8_t lun_id;
+ uint8_t cdb_len;
+ uint8_t sge_count;
+ uint64_t context;
+ uint16_t flags;
+ uint16_t timeout;
+ uint32_t data_len;
+} QEMU_PACKED;
+
+struct mfi_init_frame {
+ struct mfi_frame_header header;
+ uint32_t qinfo_new_addr_lo;
+ uint32_t qinfo_new_addr_hi;
+ uint32_t qinfo_old_addr_lo;
+ uint32_t qinfo_old_addr_hi;
+ uint32_t reserved[6];
+};
+
+#define MFI_IO_FRAME_SIZE 40
+struct mfi_io_frame {
+ struct mfi_frame_header header;
+ uint32_t sense_addr_lo;
+ uint32_t sense_addr_hi;
+ uint32_t lba_lo;
+ uint32_t lba_hi;
+ union mfi_sgl sgl;
+} QEMU_PACKED;
+
+#define MFI_PASS_FRAME_SIZE 48
+struct mfi_pass_frame {
+ struct mfi_frame_header header;
+ uint32_t sense_addr_lo;
+ uint32_t sense_addr_hi;
+ uint8_t cdb[16];
+ union mfi_sgl sgl;
+} QEMU_PACKED;
+
+#define MFI_DCMD_FRAME_SIZE 40
+struct mfi_dcmd_frame {
+ struct mfi_frame_header header;
+ uint32_t opcode;
+ uint8_t mbox[MFI_MBOX_SIZE];
+ union mfi_sgl sgl;
+} QEMU_PACKED;
+
+struct mfi_abort_frame {
+ struct mfi_frame_header header;
+ uint64_t abort_context;
+ uint32_t abort_mfi_addr_lo;
+ uint32_t abort_mfi_addr_hi;
+ uint32_t reserved1[6];
+} QEMU_PACKED;
+
+struct mfi_smp_frame {
+ struct mfi_frame_header header;
+ uint64_t sas_addr;
+ union {
+ struct mfi_sg32 sg32[2];
+ struct mfi_sg64 sg64[2];
+ } sgl;
+} QEMU_PACKED;
+
+struct mfi_stp_frame {
+ struct mfi_frame_header header;
+ uint16_t fis[10];
+ uint32_t stp_flags;
+ union {
+ struct mfi_sg32 sg32[2];
+ struct mfi_sg64 sg64[2];
+ } sgl;
+} QEMU_PACKED;
+
+union mfi_frame {
+ struct mfi_frame_header header;
+ struct mfi_init_frame init;
+ struct mfi_io_frame io;
+ struct mfi_pass_frame pass;
+ struct mfi_dcmd_frame dcmd;
+ struct mfi_abort_frame abort;
+ struct mfi_smp_frame smp;
+ struct mfi_stp_frame stp;
+ uint64_t raw[8];
+ uint8_t bytes[MFI_FRAME_SIZE];
+};
+
+#define MFI_SENSE_LEN 128
+struct mfi_sense {
+ uint8_t data[MFI_SENSE_LEN];
+};
+
+#define MFI_QUEUE_FLAG_CONTEXT64 0x00000002
+
+/* The queue init structure that is passed with the init message */
+struct mfi_init_qinfo {
+ uint32_t flags;
+ uint32_t rq_entries;
+ uint32_t rq_addr_lo;
+ uint32_t rq_addr_hi;
+ uint32_t pi_addr_lo;
+ uint32_t pi_addr_hi;
+ uint32_t ci_addr_lo;
+ uint32_t ci_addr_hi;
+} QEMU_PACKED;
+
+/* Controller properties */
+struct mfi_ctrl_props {
+ uint16_t seq_num;
+ uint16_t pred_fail_poll_interval;
+ uint16_t intr_throttle_cnt;
+ uint16_t intr_throttle_timeout;
+ uint8_t rebuild_rate;
+ uint8_t patrol_read_rate;
+ uint8_t bgi_rate;
+ uint8_t cc_rate;
+ uint8_t recon_rate;
+ uint8_t cache_flush_interval;
+ uint8_t spinup_drv_cnt;
+ uint8_t spinup_delay;
+ uint8_t cluster_enable;
+ uint8_t coercion_mode;
+ uint8_t alarm_enable;
+ uint8_t disable_auto_rebuild;
+ uint8_t disable_battery_warn;
+ uint8_t ecc_bucket_size;
+ uint16_t ecc_bucket_leak_rate;
+ uint8_t restore_hotspare_on_insertion;
+ uint8_t expose_encl_devices;
+ uint8_t maintainPdFailHistory;
+ uint8_t disallowHostRequestReordering;
+ uint8_t abortCCOnError;
+ uint8_t loadBalanceMode;
+ uint8_t disableAutoDetectBackplane;
+ uint8_t snapVDSpace;
+ uint32_t OnOffProperties;
+/* set TRUE to disable copyBack (0=copyback enabled) */
+#define MFI_CTRL_PROP_CopyBackDisabled (1 << 0)
+#define MFI_CTRL_PROP_SMARTerEnabled (1 << 1)
+#define MFI_CTRL_PROP_PRCorrectUnconfiguredAreas (1 << 2)
+#define MFI_CTRL_PROP_UseFdeOnly (1 << 3)
+#define MFI_CTRL_PROP_DisableNCQ (1 << 4)
+#define MFI_CTRL_PROP_SSDSMARTerEnabled (1 << 5)
+#define MFI_CTRL_PROP_SSDPatrolReadEnabled (1 << 6)
+#define MFI_CTRL_PROP_EnableSpinDownUnconfigured (1 << 7)
+#define MFI_CTRL_PROP_AutoEnhancedImport (1 << 8)
+#define MFI_CTRL_PROP_EnableSecretKeyControl (1 << 9)
+#define MFI_CTRL_PROP_DisableOnlineCtrlReset (1 << 10)
+#define MFI_CTRL_PROP_AllowBootWithPinnedCache (1 << 11)
+#define MFI_CTRL_PROP_DisableSpinDownHS (1 << 12)
+#define MFI_CTRL_PROP_EnableJBOD (1 << 13)
+
+ uint8_t autoSnapVDSpace; /* % of source LD to be
+ * reserved for auto snapshot
+ * in snapshot repository, for
+ * metadata and user data
+ * 1=5%, 2=10%, 3=15% and so on
+ */
+ uint8_t viewSpace; /* snapshot writable VIEWs
+ * capacity as a % of source LD
+ * capacity. 0=READ only
+ * 1=5%, 2=10%, 3=15% and so on
+ */
+ uint16_t spinDownTime; /* # of idle minutes before device
+ * is spun down (0=use FW defaults)
+ */
+ uint8_t reserved[24];
+} QEMU_PACKED;
+
+/* PCI information about the card. */
+struct mfi_info_pci {
+ uint16_t vendor;
+ uint16_t device;
+ uint16_t subvendor;
+ uint16_t subdevice;
+ uint8_t reserved[24];
+} QEMU_PACKED;
+
+/* Host (front end) interface information */
+struct mfi_info_host {
+ uint8_t type;
+#define MFI_INFO_HOST_PCIX 0x01
+#define MFI_INFO_HOST_PCIE 0x02
+#define MFI_INFO_HOST_ISCSI 0x04
+#define MFI_INFO_HOST_SAS3G 0x08
+ uint8_t reserved[6];
+ uint8_t port_count;
+ uint64_t port_addr[8];
+} QEMU_PACKED;
+
+/* Device (back end) interface information */
+struct mfi_info_device {
+ uint8_t type;
+#define MFI_INFO_DEV_SPI 0x01
+#define MFI_INFO_DEV_SAS3G 0x02
+#define MFI_INFO_DEV_SATA1 0x04
+#define MFI_INFO_DEV_SATA3G 0x08
+#define MFI_INFO_DEV_PCIE 0x10
+ uint8_t reserved[6];
+ uint8_t port_count;
+ uint64_t port_addr[8];
+} QEMU_PACKED;
+
+/* Firmware component information */
+struct mfi_info_component {
+ char name[8];
+ char version[32];
+ char build_date[16];
+ char build_time[16];
+} QEMU_PACKED;
+
+/* Controller default settings */
+struct mfi_defaults {
+ uint64_t sas_addr;
+ uint8_t phy_polarity;
+ uint8_t background_rate;
+ uint8_t stripe_size;
+ uint8_t flush_time;
+ uint8_t write_back;
+ uint8_t read_ahead;
+ uint8_t cache_when_bbu_bad;
+ uint8_t cached_io;
+ uint8_t smart_mode;
+ uint8_t alarm_disable;
+ uint8_t coercion;
+ uint8_t zrc_config;
+ uint8_t dirty_led_shows_drive_activity;
+ uint8_t bios_continue_on_error;
+ uint8_t spindown_mode;
+ uint8_t allowed_device_types;
+ uint8_t allow_mix_in_enclosure;
+ uint8_t allow_mix_in_ld;
+ uint8_t allow_sata_in_cluster;
+ uint8_t max_chained_enclosures;
+ uint8_t disable_ctrl_r;
+ uint8_t enable_web_bios;
+ uint8_t phy_polarity_split;
+ uint8_t direct_pd_mapping;
+ uint8_t bios_enumerate_lds;
+ uint8_t restored_hot_spare_on_insertion;
+ uint8_t expose_enclosure_devices;
+ uint8_t maintain_pd_fail_history;
+ uint8_t disable_puncture;
+ uint8_t zero_based_enumeration;
+ uint8_t disable_preboot_cli;
+ uint8_t show_drive_led_on_activity;
+ uint8_t cluster_disable;
+ uint8_t sas_disable;
+ uint8_t auto_detect_backplane;
+ uint8_t fde_only;
+ uint8_t delay_during_post;
+ uint8_t resv[19];
+} QEMU_PACKED;
+
+/* Controller default settings */
+struct mfi_bios_data {
+ uint16_t boot_target_id;
+ uint8_t do_not_int_13;
+ uint8_t continue_on_error;
+ uint8_t verbose;
+ uint8_t geometry;
+ uint8_t expose_all_drives;
+ uint8_t reserved[56];
+ uint8_t check_sum;
+} QEMU_PACKED;
+
+/* SAS (?) controller info, returned from MFI_DCMD_CTRL_GETINFO. */
+struct mfi_ctrl_info {
+ struct mfi_info_pci pci;
+ struct mfi_info_host host;
+ struct mfi_info_device device;
+
+ /* Firmware components that are present and active. */
+ uint32_t image_check_word;
+ uint32_t image_component_count;
+ struct mfi_info_component image_component[8];
+
+ /* Firmware components that have been flashed but are inactive */
+ uint32_t pending_image_component_count;
+ struct mfi_info_component pending_image_component[8];
+
+ uint8_t max_arms;
+ uint8_t max_spans;
+ uint8_t max_arrays;
+ uint8_t max_lds;
+ char product_name[80];
+ char serial_number[32];
+ uint32_t hw_present;
+#define MFI_INFO_HW_BBU 0x01
+#define MFI_INFO_HW_ALARM 0x02
+#define MFI_INFO_HW_NVRAM 0x04
+#define MFI_INFO_HW_UART 0x08
+#define MFI_INFO_HW_MEM 0x10
+#define MFI_INFO_HW_FLASH 0x20
+ uint32_t current_fw_time;
+ uint16_t max_cmds;
+ uint16_t max_sg_elements;
+ uint32_t max_request_size;
+ uint16_t lds_present;
+ uint16_t lds_degraded;
+ uint16_t lds_offline;
+ uint16_t pd_present;
+ uint16_t pd_disks_present;
+ uint16_t pd_disks_pred_failure;
+ uint16_t pd_disks_failed;
+ uint16_t nvram_size;
+ uint16_t memory_size;
+ uint16_t flash_size;
+ uint16_t ram_correctable_errors;
+ uint16_t ram_uncorrectable_errors;
+ uint8_t cluster_allowed;
+ uint8_t cluster_active;
+ uint16_t max_strips_per_io;
+
+ uint32_t raid_levels;
+#define MFI_INFO_RAID_0 0x01
+#define MFI_INFO_RAID_1 0x02
+#define MFI_INFO_RAID_5 0x04
+#define MFI_INFO_RAID_1E 0x08
+#define MFI_INFO_RAID_6 0x10
+
+ uint32_t adapter_ops;
+#define MFI_INFO_AOPS_RBLD_RATE 0x0001
+#define MFI_INFO_AOPS_CC_RATE 0x0002
+#define MFI_INFO_AOPS_BGI_RATE 0x0004
+#define MFI_INFO_AOPS_RECON_RATE 0x0008
+#define MFI_INFO_AOPS_PATROL_RATE 0x0010
+#define MFI_INFO_AOPS_ALARM_CONTROL 0x0020
+#define MFI_INFO_AOPS_CLUSTER_SUPPORTED 0x0040
+#define MFI_INFO_AOPS_BBU 0x0080
+#define MFI_INFO_AOPS_SPANNING_ALLOWED 0x0100
+#define MFI_INFO_AOPS_DEDICATED_SPARES 0x0200
+#define MFI_INFO_AOPS_REVERTIBLE_SPARES 0x0400
+#define MFI_INFO_AOPS_FOREIGN_IMPORT 0x0800
+#define MFI_INFO_AOPS_SELF_DIAGNOSTIC 0x1000
+#define MFI_INFO_AOPS_MIXED_ARRAY 0x2000
+#define MFI_INFO_AOPS_GLOBAL_SPARES 0x4000
+
+ uint32_t ld_ops;
+#define MFI_INFO_LDOPS_READ_POLICY 0x01
+#define MFI_INFO_LDOPS_WRITE_POLICY 0x02
+#define MFI_INFO_LDOPS_IO_POLICY 0x04
+#define MFI_INFO_LDOPS_ACCESS_POLICY 0x08
+#define MFI_INFO_LDOPS_DISK_CACHE_POLICY 0x10
+
+ struct {
+ uint8_t min;
+ uint8_t max;
+ uint8_t reserved[2];
+ } QEMU_PACKED stripe_sz_ops;
+
+ uint32_t pd_ops;
+#define MFI_INFO_PDOPS_FORCE_ONLINE 0x01
+#define MFI_INFO_PDOPS_FORCE_OFFLINE 0x02
+#define MFI_INFO_PDOPS_FORCE_REBUILD 0x04
+
+ uint32_t pd_mix_support;
+#define MFI_INFO_PDMIX_SAS 0x01
+#define MFI_INFO_PDMIX_SATA 0x02
+#define MFI_INFO_PDMIX_ENCL 0x04
+#define MFI_INFO_PDMIX_LD 0x08
+#define MFI_INFO_PDMIX_SATA_CLUSTER 0x10
+
+ uint8_t ecc_bucket_count;
+ uint8_t reserved2[11];
+ struct mfi_ctrl_props properties;
+ char package_version[0x60];
+ uint8_t pad[0x800 - 0x6a0];
+} QEMU_PACKED;
+
+/* keep track of an event. */
+union mfi_evt {
+ struct {
+ uint16_t locale;
+ uint8_t reserved;
+ int8_t class;
+ } members;
+ uint32_t word;
+} QEMU_PACKED;
+
+/* event log state. */
+struct mfi_evt_log_state {
+ uint32_t newest_seq_num;
+ uint32_t oldest_seq_num;
+ uint32_t clear_seq_num;
+ uint32_t shutdown_seq_num;
+ uint32_t boot_seq_num;
+} QEMU_PACKED;
+
+struct mfi_progress {
+ uint16_t progress;
+ uint16_t elapsed_seconds;
+} QEMU_PACKED;
+
+struct mfi_evt_ld {
+ uint16_t target_id;
+ uint8_t ld_index;
+ uint8_t reserved;
+} QEMU_PACKED;
+
+struct mfi_evt_pd {
+ uint16_t device_id;
+ uint8_t enclosure_index;
+ uint8_t slot_number;
+} QEMU_PACKED;
+
+/* event detail, returned from MFI_DCMD_CTRL_EVENT_WAIT. */
+struct mfi_evt_detail {
+ uint32_t seq;
+ uint32_t time;
+ uint32_t code;
+ union mfi_evt class;
+ uint8_t arg_type;
+ uint8_t reserved1[15];
+
+ union {
+ struct {
+ struct mfi_evt_pd pd;
+ uint8_t cdb_len;
+ uint8_t sense_len;
+ uint8_t reserved[2];
+ uint8_t cdb[16];
+ uint8_t sense[64];
+ } cdb_sense;
+
+ struct mfi_evt_ld ld;
+
+ struct {
+ struct mfi_evt_ld ld;
+ uint64_t count;
+ } ld_count;
+
+ struct {
+ uint64_t lba;
+ struct mfi_evt_ld ld;
+ } ld_lba;
+
+ struct {
+ struct mfi_evt_ld ld;
+ uint32_t pre_owner;
+ uint32_t new_owner;
+ } ld_owner;
+
+ struct {
+ uint64_t ld_lba;
+ uint64_t pd_lba;
+ struct mfi_evt_ld ld;
+ struct mfi_evt_pd pd;
+ } ld_lba_pd_lba;
+
+ struct {
+ struct mfi_evt_ld ld;
+ struct mfi_progress prog;
+ } ld_prog;
+
+ struct {
+ struct mfi_evt_ld ld;
+ uint32_t prev_state;
+ uint32_t new_state;
+ } ld_state;
+
+ struct {
+ uint64_t strip;
+ struct mfi_evt_ld ld;
+ } ld_strip;
+
+ struct mfi_evt_pd pd;
+
+ struct {
+ struct mfi_evt_pd pd;
+ uint32_t err;
+ } pd_err;
+
+ struct {
+ uint64_t lba;
+ struct mfi_evt_pd pd;
+ } pd_lba;
+
+ struct {
+ uint64_t lba;
+ struct mfi_evt_pd pd;
+ struct mfi_evt_ld ld;
+ } pd_lba_ld;
+
+ struct {
+ struct mfi_evt_pd pd;
+ struct mfi_progress prog;
+ } pd_prog;
+
+ struct {
+ struct mfi_evt_pd ld;
+ uint32_t prev_state;
+ uint32_t new_state;
+ } pd_state;
+
+ struct {
+ uint16_t venderId;
+ uint16_t deviceId;
+ uint16_t subVenderId;
+ uint16_t subDeviceId;
+ } pci;
+
+ uint32_t rate;
+
+ char str[96];
+
+ struct {
+ uint32_t rtc;
+ uint16_t elapsedSeconds;
+ } time;
+
+ struct {
+ uint32_t ecar;
+ uint32_t elog;
+ char str[64];
+ } ecc;
+
+ uint8_t b[96];
+ uint16_t s[48];
+ uint32_t w[24];
+ uint64_t d[12];
+ } args;
+
+ char description[128];
+} QEMU_PACKED;
+
+struct mfi_evt_list {
+ uint32_t count;
+ uint32_t reserved;
+ struct mfi_evt_detail event[1];
+} QEMU_PACKED;
+
+union mfi_pd_ref {
+ struct {
+ uint16_t device_id;
+ uint16_t seq_num;
+ } v;
+ uint32_t ref;
+} QEMU_PACKED;
+
+union mfi_pd_ddf_type {
+ struct {
+ uint16_t pd_type;
+#define MFI_PD_DDF_TYPE_FORCED_PD_GUID (1 << 0)
+#define MFI_PD_DDF_TYPE_IN_VD (1 << 1)
+#define MFI_PD_DDF_TYPE_IS_GLOBAL_SPARE (1 << 2)
+#define MFI_PD_DDF_TYPE_IS_SPARE (1 << 3)
+#define MFI_PD_DDF_TYPE_IS_FOREIGN (1 << 4)
+#define MFI_PD_DDF_TYPE_INTF_SPI (1 << 12)
+#define MFI_PD_DDF_TYPE_INTF_SAS (1 << 13)
+#define MFI_PD_DDF_TYPE_INTF_SATA1 (1 << 14)
+#define MFI_PD_DDF_TYPE_INTF_SATA3G (1 << 15)
+ uint16_t reserved;
+ } ddf;
+ struct {
+ uint32_t reserved;
+ } non_disk;
+ uint32_t type;
+} QEMU_PACKED;
+
+struct mfi_pd_progress {
+ uint32_t active;
+#define PD_PROGRESS_ACTIVE_REBUILD (1 << 0)
+#define PD_PROGRESS_ACTIVE_PATROL (1 << 1)
+#define PD_PROGRESS_ACTIVE_CLEAR (1 << 2)
+ struct mfi_progress rbld;
+ struct mfi_progress patrol;
+ struct mfi_progress clear;
+ struct mfi_progress reserved[4];
+} QEMU_PACKED;
+
+struct mfi_pd_info {
+ union mfi_pd_ref ref;
+ uint8_t inquiry_data[96];
+ uint8_t vpd_page83[64];
+ uint8_t not_supported;
+ uint8_t scsi_dev_type;
+ uint8_t connected_port_bitmap;
+ uint8_t device_speed;
+ uint32_t media_err_count;
+ uint32_t other_err_count;
+ uint32_t pred_fail_count;
+ uint32_t last_pred_fail_event_seq_num;
+ uint16_t fw_state;
+ uint8_t disable_for_removal;
+ uint8_t link_speed;
+ union mfi_pd_ddf_type state;
+ struct {
+ uint8_t count;
+ uint8_t is_path_broken;
+ uint8_t reserved[6];
+ uint64_t sas_addr[4];
+ } path_info;
+ uint64_t raw_size;
+ uint64_t non_coerced_size;
+ uint64_t coerced_size;
+ uint16_t encl_device_id;
+ uint8_t encl_index;
+ uint8_t slot_number;
+ struct mfi_pd_progress prog_info;
+ uint8_t bad_block_table_full;
+ uint8_t unusable_in_current_config;
+ uint8_t vpd_page83_ext[64];
+ uint8_t reserved[512-358];
+} QEMU_PACKED;
+
+struct mfi_pd_address {
+ uint16_t device_id;
+ uint16_t encl_device_id;
+ uint8_t encl_index;
+ uint8_t slot_number;
+ uint8_t scsi_dev_type;
+ uint8_t connect_port_bitmap;
+ uint64_t sas_addr[2];
+} QEMU_PACKED;
+
+#define MFI_MAX_SYS_PDS 240
+struct mfi_pd_list {
+ uint32_t size;
+ uint32_t count;
+ struct mfi_pd_address addr[MFI_MAX_SYS_PDS];
+} QEMU_PACKED;
+
+union mfi_ld_ref {
+ struct {
+ uint8_t target_id;
+ uint8_t reserved;
+ uint16_t seq;
+ } v;
+ uint32_t ref;
+} QEMU_PACKED;
+
+struct mfi_ld_list {
+ uint32_t ld_count;
+ uint32_t reserved1;
+ struct {
+ union mfi_ld_ref ld;
+ uint8_t state;
+ uint8_t reserved2[3];
+ uint64_t size;
+ } ld_list[MFI_MAX_LD];
+} QEMU_PACKED;
+
+struct mfi_ld_targetid_list {
+ uint32_t size;
+ uint32_t ld_count;
+ uint8_t pad[3];
+ uint8_t targetid[MFI_MAX_LD];
+} QEMU_PACKED;
+
+enum mfi_ld_access {
+ MFI_LD_ACCESS_RW = 0,
+ MFI_LD_ACCSSS_RO = 2,
+ MFI_LD_ACCESS_BLOCKED = 3,
+};
+#define MFI_LD_ACCESS_MASK 3
+
+enum mfi_ld_state {
+ MFI_LD_STATE_OFFLINE = 0,
+ MFI_LD_STATE_PARTIALLY_DEGRADED = 1,
+ MFI_LD_STATE_DEGRADED = 2,
+ MFI_LD_STATE_OPTIMAL = 3
+};
+
+enum mfi_syspd_state {
+ MFI_PD_STATE_UNCONFIGURED_GOOD = 0x00,
+ MFI_PD_STATE_UNCONFIGURED_BAD = 0x01,
+ MFI_PD_STATE_HOT_SPARE = 0x02,
+ MFI_PD_STATE_OFFLINE = 0x10,
+ MFI_PD_STATE_FAILED = 0x11,
+ MFI_PD_STATE_REBUILD = 0x14,
+ MFI_PD_STATE_ONLINE = 0x18,
+ MFI_PD_STATE_COPYBACK = 0x20,
+ MFI_PD_STATE_SYSTEM = 0x40
+};
+
+struct mfi_ld_props {
+ union mfi_ld_ref ld;
+ char name[16];
+ uint8_t default_cache_policy;
+ uint8_t access_policy;
+ uint8_t disk_cache_policy;
+ uint8_t current_cache_policy;
+ uint8_t no_bgi;
+ uint8_t reserved[7];
+} QEMU_PACKED;
+
+struct mfi_ld_params {
+ uint8_t primary_raid_level;
+ uint8_t raid_level_qualifier;
+ uint8_t secondary_raid_level;
+ uint8_t stripe_size;
+ uint8_t num_drives;
+ uint8_t span_depth;
+ uint8_t state;
+ uint8_t init_state;
+ uint8_t is_consistent;
+ uint8_t reserved[23];
+} QEMU_PACKED;
+
+struct mfi_ld_progress {
+ uint32_t active;
+#define MFI_LD_PROGRESS_CC (1<<0)
+#define MFI_LD_PROGRESS_BGI (1<<1)
+#define MFI_LD_PROGRESS_FGI (1<<2)
+#define MFI_LD_PORGRESS_RECON (1<<3)
+ struct mfi_progress cc;
+ struct mfi_progress bgi;
+ struct mfi_progress fgi;
+ struct mfi_progress recon;
+ struct mfi_progress reserved[4];
+} QEMU_PACKED;
+
+struct mfi_span {
+ uint64_t start_block;
+ uint64_t num_blocks;
+ uint16_t array_ref;
+ uint8_t reserved[6];
+} QEMU_PACKED;
+
+#define MFI_MAX_SPAN_DEPTH 8
+struct mfi_ld_config {
+ struct mfi_ld_props properties;
+ struct mfi_ld_params params;
+ struct mfi_span span[MFI_MAX_SPAN_DEPTH];
+} QEMU_PACKED;
+
+struct mfi_ld_info {
+ struct mfi_ld_config ld_config;
+ uint64_t size;
+ struct mfi_ld_progress progress;
+ uint16_t cluster_owner;
+ uint8_t reconstruct_active;
+ uint8_t reserved1[1];
+ uint8_t vpd_page83[64];
+ uint8_t reserved2[16];
+} QEMU_PACKED;
+
+union mfi_spare_type {
+ uint8_t flags;
+#define MFI_SPARE_IS_DEDICATED (1 << 0)
+#define MFI_SPARE_IS_REVERTABLE (1 << 1)
+#define MFI_SPARE_IS_ENCL_AFFINITY (1 << 2)
+ uint8_t type;
+} QEMU_PACKED;
+
+#define MFI_MAX_ARRAYS 16
+struct mfi_spare {
+ union mfi_pd_ref ref;
+ union mfi_spare_type spare_type;
+ uint8_t reserved[2];
+ uint8_t array_count;
+ uint16_t array_refd[MFI_MAX_ARRAYS];
+} QEMU_PACKED;
+
+#define MFI_MAX_ROW_SIZE 32
+struct mfi_array {
+ uint64_t size;
+ uint8_t num_drives;
+ uint8_t reserved;
+ uint16_t array_ref;
+ uint8_t pad[20];
+ struct {
+ union mfi_pd_ref ref;
+ uint16_t fw_state; /* enum mfi_syspd_state */
+ struct {
+ uint8_t pd;
+ uint8_t slot;
+ } encl;
+ } pd[MFI_MAX_ROW_SIZE];
+} QEMU_PACKED;
+
+struct mfi_config_data {
+ uint32_t size;
+ uint16_t array_count;
+ uint16_t array_size;
+ uint16_t log_drv_count;
+ uint16_t log_drv_size;
+ uint16_t spares_count;
+ uint16_t spares_size;
+ uint8_t reserved[16];
+ /*
+ struct mfi_array array[];
+ struct mfi_ld_config ld[];
+ struct mfi_spare spare[];
+ */
+} QEMU_PACKED;
+
+#define MFI_SCSI_MAX_TARGETS 128
+#define MFI_SCSI_MAX_LUNS 8
+#define MFI_SCSI_INITIATOR_ID 255
+#define MFI_SCSI_MAX_CMDS 8
+#define MFI_SCSI_MAX_CDB_LEN 16
+
+#endif /* SCSI_MFI_H */
diff --git a/hw/scsi/mpi.h b/hw/scsi/mpi.h
new file mode 100644
index 00000000..0568e195
--- /dev/null
+++ b/hw/scsi/mpi.h
@@ -0,0 +1,1153 @@
+/*-
+ * Based on FreeBSD sys/dev/mpt/mpilib headers.
+ *
+ * Copyright (c) 2000-2010, LSI Logic Corporation and its contributors.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon including
+ * a substantially similar Disclaimer requirement for further binary
+ * redistribution.
+ * 3. Neither the name of the LSI Logic Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF THE COPYRIGHT
+ * OWNER OR CONTRIBUTOR IS ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef MPI_H
+#define MPI_H
+
+enum {
+ MPI_FUNCTION_SCSI_IO_REQUEST = 0x00,
+ MPI_FUNCTION_SCSI_TASK_MGMT = 0x01,
+ MPI_FUNCTION_IOC_INIT = 0x02,
+ MPI_FUNCTION_IOC_FACTS = 0x03,
+ MPI_FUNCTION_CONFIG = 0x04,
+ MPI_FUNCTION_PORT_FACTS = 0x05,
+ MPI_FUNCTION_PORT_ENABLE = 0x06,
+ MPI_FUNCTION_EVENT_NOTIFICATION = 0x07,
+ MPI_FUNCTION_EVENT_ACK = 0x08,
+ MPI_FUNCTION_FW_DOWNLOAD = 0x09,
+ MPI_FUNCTION_TARGET_CMD_BUFFER_POST = 0x0A,
+ MPI_FUNCTION_TARGET_ASSIST = 0x0B,
+ MPI_FUNCTION_TARGET_STATUS_SEND = 0x0C,
+ MPI_FUNCTION_TARGET_MODE_ABORT = 0x0D,
+ MPI_FUNCTION_FC_LINK_SRVC_BUF_POST = 0x0E,
+ MPI_FUNCTION_FC_LINK_SRVC_RSP = 0x0F,
+ MPI_FUNCTION_FC_EX_LINK_SRVC_SEND = 0x10,
+ MPI_FUNCTION_FC_ABORT = 0x11,
+ MPI_FUNCTION_FW_UPLOAD = 0x12,
+ MPI_FUNCTION_FC_COMMON_TRANSPORT_SEND = 0x13,
+ MPI_FUNCTION_FC_PRIMITIVE_SEND = 0x14,
+
+ MPI_FUNCTION_RAID_ACTION = 0x15,
+ MPI_FUNCTION_RAID_SCSI_IO_PASSTHROUGH = 0x16,
+
+ MPI_FUNCTION_TOOLBOX = 0x17,
+
+ MPI_FUNCTION_SCSI_ENCLOSURE_PROCESSOR = 0x18,
+
+ MPI_FUNCTION_MAILBOX = 0x19,
+
+ MPI_FUNCTION_SMP_PASSTHROUGH = 0x1A,
+ MPI_FUNCTION_SAS_IO_UNIT_CONTROL = 0x1B,
+ MPI_FUNCTION_SATA_PASSTHROUGH = 0x1C,
+
+ MPI_FUNCTION_DIAG_BUFFER_POST = 0x1D,
+ MPI_FUNCTION_DIAG_RELEASE = 0x1E,
+
+ MPI_FUNCTION_SCSI_IO_32 = 0x1F,
+
+ MPI_FUNCTION_LAN_SEND = 0x20,
+ MPI_FUNCTION_LAN_RECEIVE = 0x21,
+ MPI_FUNCTION_LAN_RESET = 0x22,
+
+ MPI_FUNCTION_TARGET_ASSIST_EXTENDED = 0x23,
+ MPI_FUNCTION_TARGET_CMD_BUF_BASE_POST = 0x24,
+ MPI_FUNCTION_TARGET_CMD_BUF_LIST_POST = 0x25,
+
+ MPI_FUNCTION_INBAND_BUFFER_POST = 0x28,
+ MPI_FUNCTION_INBAND_SEND = 0x29,
+ MPI_FUNCTION_INBAND_RSP = 0x2A,
+ MPI_FUNCTION_INBAND_ABORT = 0x2B,
+
+ MPI_FUNCTION_IOC_MESSAGE_UNIT_RESET = 0x40,
+ MPI_FUNCTION_IO_UNIT_RESET = 0x41,
+ MPI_FUNCTION_HANDSHAKE = 0x42,
+ MPI_FUNCTION_REPLY_FRAME_REMOVAL = 0x43,
+ MPI_FUNCTION_HOST_PAGEBUF_ACCESS_CONTROL = 0x44,
+};
+
+/****************************************************************************/
+/* Registers */
+/****************************************************************************/
+
+enum {
+ MPI_IOC_STATE_RESET = 0x00000000,
+ MPI_IOC_STATE_READY = 0x10000000,
+ MPI_IOC_STATE_OPERATIONAL = 0x20000000,
+ MPI_IOC_STATE_FAULT = 0x40000000,
+
+ MPI_DOORBELL_OFFSET = 0x00000000,
+ MPI_DOORBELL_ACTIVE = 0x08000000, /* DoorbellUsed */
+ MPI_DOORBELL_WHO_INIT_MASK = 0x07000000,
+ MPI_DOORBELL_WHO_INIT_SHIFT = 24,
+ MPI_DOORBELL_FUNCTION_MASK = 0xFF000000,
+ MPI_DOORBELL_FUNCTION_SHIFT = 24,
+ MPI_DOORBELL_ADD_DWORDS_MASK = 0x00FF0000,
+ MPI_DOORBELL_ADD_DWORDS_SHIFT = 16,
+ MPI_DOORBELL_DATA_MASK = 0x0000FFFF,
+ MPI_DOORBELL_FUNCTION_SPECIFIC_MASK = 0x0000FFFF,
+
+ MPI_DB_HPBAC_VALUE_MASK = 0x0000F000,
+ MPI_DB_HPBAC_ENABLE_ACCESS = 0x01,
+ MPI_DB_HPBAC_DISABLE_ACCESS = 0x02,
+ MPI_DB_HPBAC_FREE_BUFFER = 0x03,
+
+ MPI_WRITE_SEQUENCE_OFFSET = 0x00000004,
+ MPI_WRSEQ_KEY_VALUE_MASK = 0x0000000F,
+ MPI_WRSEQ_1ST_KEY_VALUE = 0x04,
+ MPI_WRSEQ_2ND_KEY_VALUE = 0x0B,
+ MPI_WRSEQ_3RD_KEY_VALUE = 0x02,
+ MPI_WRSEQ_4TH_KEY_VALUE = 0x07,
+ MPI_WRSEQ_5TH_KEY_VALUE = 0x0D,
+
+ MPI_DIAGNOSTIC_OFFSET = 0x00000008,
+ MPI_DIAG_CLEAR_FLASH_BAD_SIG = 0x00000400,
+ MPI_DIAG_PREVENT_IOC_BOOT = 0x00000200,
+ MPI_DIAG_DRWE = 0x00000080,
+ MPI_DIAG_FLASH_BAD_SIG = 0x00000040,
+ MPI_DIAG_RESET_HISTORY = 0x00000020,
+ MPI_DIAG_RW_ENABLE = 0x00000010,
+ MPI_DIAG_RESET_ADAPTER = 0x00000004,
+ MPI_DIAG_DISABLE_ARM = 0x00000002,
+ MPI_DIAG_MEM_ENABLE = 0x00000001,
+
+ MPI_TEST_BASE_ADDRESS_OFFSET = 0x0000000C,
+
+ MPI_DIAG_RW_DATA_OFFSET = 0x00000010,
+
+ MPI_DIAG_RW_ADDRESS_OFFSET = 0x00000014,
+
+ MPI_HOST_INTERRUPT_STATUS_OFFSET = 0x00000030,
+ MPI_HIS_IOP_DOORBELL_STATUS = 0x80000000,
+ MPI_HIS_REPLY_MESSAGE_INTERRUPT = 0x00000008,
+ MPI_HIS_DOORBELL_INTERRUPT = 0x00000001,
+
+ MPI_HOST_INTERRUPT_MASK_OFFSET = 0x00000034,
+ MPI_HIM_RIM = 0x00000008,
+ MPI_HIM_DIM = 0x00000001,
+
+ MPI_REQUEST_QUEUE_OFFSET = 0x00000040,
+ MPI_REQUEST_POST_FIFO_OFFSET = 0x00000040,
+
+ MPI_REPLY_QUEUE_OFFSET = 0x00000044,
+ MPI_REPLY_POST_FIFO_OFFSET = 0x00000044,
+ MPI_REPLY_FREE_FIFO_OFFSET = 0x00000044,
+
+ MPI_HI_PRI_REQUEST_QUEUE_OFFSET = 0x00000048,
+};
+
+#define MPI_ADDRESS_REPLY_A_BIT 0x80000000
+
+/****************************************************************************/
+/* Scatter/gather elements */
+/****************************************************************************/
+
+typedef struct MPISGEntry {
+ uint32_t FlagsLength;
+ union
+ {
+ uint32_t Address32;
+ uint64_t Address64;
+ } u;
+} QEMU_PACKED MPISGEntry;
+
+/* Flags field bit definitions */
+
+enum {
+ MPI_SGE_FLAGS_LAST_ELEMENT = 0x80000000,
+ MPI_SGE_FLAGS_END_OF_BUFFER = 0x40000000,
+ MPI_SGE_FLAGS_ELEMENT_TYPE_MASK = 0x30000000,
+ MPI_SGE_FLAGS_LOCAL_ADDRESS = 0x08000000,
+ MPI_SGE_FLAGS_DIRECTION = 0x04000000,
+ MPI_SGE_FLAGS_64_BIT_ADDRESSING = 0x02000000,
+ MPI_SGE_FLAGS_END_OF_LIST = 0x01000000,
+
+ MPI_SGE_LENGTH_MASK = 0x00FFFFFF,
+ MPI_SGE_CHAIN_LENGTH_MASK = 0x0000FFFF,
+
+ MPI_SGE_FLAGS_TRANSACTION_ELEMENT = 0x00000000,
+ MPI_SGE_FLAGS_SIMPLE_ELEMENT = 0x10000000,
+ MPI_SGE_FLAGS_CHAIN_ELEMENT = 0x30000000,
+
+ /* Direction */
+
+ MPI_SGE_FLAGS_IOC_TO_HOST = 0x00000000,
+ MPI_SGE_FLAGS_HOST_TO_IOC = 0x04000000,
+
+ MPI_SGE_CHAIN_OFFSET_MASK = 0x00FF0000,
+};
+
+#define MPI_SGE_CHAIN_OFFSET_SHIFT 16
+
+/****************************************************************************/
+/* Standard message request header for all request messages */
+/****************************************************************************/
+
+typedef struct MPIRequestHeader {
+ uint8_t Reserved[2]; /* function specific */
+ uint8_t ChainOffset;
+ uint8_t Function;
+ uint8_t Reserved1[3]; /* function specific */
+ uint8_t MsgFlags;
+ uint32_t MsgContext;
+} QEMU_PACKED MPIRequestHeader;
+
+
+typedef struct MPIDefaultReply {
+ uint8_t Reserved[2]; /* function specific */
+ uint8_t MsgLength;
+ uint8_t Function;
+ uint8_t Reserved1[3]; /* function specific */
+ uint8_t MsgFlags;
+ uint32_t MsgContext;
+ uint8_t Reserved2[2]; /* function specific */
+ uint16_t IOCStatus;
+ uint32_t IOCLogInfo;
+} QEMU_PACKED MPIDefaultReply;
+
+/* MsgFlags definition for all replies */
+
+#define MPI_MSGFLAGS_CONTINUATION_REPLY (0x80)
+
+enum {
+
+ /************************************************************************/
+ /* Common IOCStatus values for all replies */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_SUCCESS = 0x0000,
+ MPI_IOCSTATUS_INVALID_FUNCTION = 0x0001,
+ MPI_IOCSTATUS_BUSY = 0x0002,
+ MPI_IOCSTATUS_INVALID_SGL = 0x0003,
+ MPI_IOCSTATUS_INTERNAL_ERROR = 0x0004,
+ MPI_IOCSTATUS_RESERVED = 0x0005,
+ MPI_IOCSTATUS_INSUFFICIENT_RESOURCES = 0x0006,
+ MPI_IOCSTATUS_INVALID_FIELD = 0x0007,
+ MPI_IOCSTATUS_INVALID_STATE = 0x0008,
+ MPI_IOCSTATUS_OP_STATE_NOT_SUPPORTED = 0x0009,
+
+ /************************************************************************/
+ /* Config IOCStatus values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_CONFIG_INVALID_ACTION = 0x0020,
+ MPI_IOCSTATUS_CONFIG_INVALID_TYPE = 0x0021,
+ MPI_IOCSTATUS_CONFIG_INVALID_PAGE = 0x0022,
+ MPI_IOCSTATUS_CONFIG_INVALID_DATA = 0x0023,
+ MPI_IOCSTATUS_CONFIG_NO_DEFAULTS = 0x0024,
+ MPI_IOCSTATUS_CONFIG_CANT_COMMIT = 0x0025,
+
+ /************************************************************************/
+ /* SCSIIO Reply = SPI & FCP, initiator values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_SCSI_RECOVERED_ERROR = 0x0040,
+ MPI_IOCSTATUS_SCSI_INVALID_BUS = 0x0041,
+ MPI_IOCSTATUS_SCSI_INVALID_TARGETID = 0x0042,
+ MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE = 0x0043,
+ MPI_IOCSTATUS_SCSI_DATA_OVERRUN = 0x0044,
+ MPI_IOCSTATUS_SCSI_DATA_UNDERRUN = 0x0045,
+ MPI_IOCSTATUS_SCSI_IO_DATA_ERROR = 0x0046,
+ MPI_IOCSTATUS_SCSI_PROTOCOL_ERROR = 0x0047,
+ MPI_IOCSTATUS_SCSI_TASK_TERMINATED = 0x0048,
+ MPI_IOCSTATUS_SCSI_RESIDUAL_MISMATCH = 0x0049,
+ MPI_IOCSTATUS_SCSI_TASK_MGMT_FAILED = 0x004A,
+ MPI_IOCSTATUS_SCSI_IOC_TERMINATED = 0x004B,
+ MPI_IOCSTATUS_SCSI_EXT_TERMINATED = 0x004C,
+
+ /************************************************************************/
+ /* For use by SCSI Initiator and SCSI Target end-to-end data protection*/
+ /************************************************************************/
+
+ MPI_IOCSTATUS_EEDP_GUARD_ERROR = 0x004D,
+ MPI_IOCSTATUS_EEDP_REF_TAG_ERROR = 0x004E,
+ MPI_IOCSTATUS_EEDP_APP_TAG_ERROR = 0x004F,
+
+ /************************************************************************/
+ /* SCSI Target values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_TARGET_PRIORITY_IO = 0x0060,
+ MPI_IOCSTATUS_TARGET_INVALID_PORT = 0x0061,
+ MPI_IOCSTATUS_TARGET_INVALID_IO_INDEX = 0x0062,
+ MPI_IOCSTATUS_TARGET_ABORTED = 0x0063,
+ MPI_IOCSTATUS_TARGET_NO_CONN_RETRYABLE = 0x0064,
+ MPI_IOCSTATUS_TARGET_NO_CONNECTION = 0x0065,
+ MPI_IOCSTATUS_TARGET_XFER_COUNT_MISMATCH = 0x006A,
+ MPI_IOCSTATUS_TARGET_STS_DATA_NOT_SENT = 0x006B,
+ MPI_IOCSTATUS_TARGET_DATA_OFFSET_ERROR = 0x006D,
+ MPI_IOCSTATUS_TARGET_TOO_MUCH_WRITE_DATA = 0x006E,
+ MPI_IOCSTATUS_TARGET_IU_TOO_SHORT = 0x006F,
+ MPI_IOCSTATUS_TARGET_ACK_NAK_TIMEOUT = 0x0070,
+ MPI_IOCSTATUS_TARGET_NAK_RECEIVED = 0x0071,
+
+ /************************************************************************/
+ /* Fibre Channel Direct Access values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_FC_ABORTED = 0x0066,
+ MPI_IOCSTATUS_FC_RX_ID_INVALID = 0x0067,
+ MPI_IOCSTATUS_FC_DID_INVALID = 0x0068,
+ MPI_IOCSTATUS_FC_NODE_LOGGED_OUT = 0x0069,
+ MPI_IOCSTATUS_FC_EXCHANGE_CANCELED = 0x006C,
+
+ /************************************************************************/
+ /* LAN values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_LAN_DEVICE_NOT_FOUND = 0x0080,
+ MPI_IOCSTATUS_LAN_DEVICE_FAILURE = 0x0081,
+ MPI_IOCSTATUS_LAN_TRANSMIT_ERROR = 0x0082,
+ MPI_IOCSTATUS_LAN_TRANSMIT_ABORTED = 0x0083,
+ MPI_IOCSTATUS_LAN_RECEIVE_ERROR = 0x0084,
+ MPI_IOCSTATUS_LAN_RECEIVE_ABORTED = 0x0085,
+ MPI_IOCSTATUS_LAN_PARTIAL_PACKET = 0x0086,
+ MPI_IOCSTATUS_LAN_CANCELED = 0x0087,
+
+ /************************************************************************/
+ /* Serial Attached SCSI values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_SAS_SMP_REQUEST_FAILED = 0x0090,
+ MPI_IOCSTATUS_SAS_SMP_DATA_OVERRUN = 0x0091,
+
+ /************************************************************************/
+ /* Inband values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_INBAND_ABORTED = 0x0098,
+ MPI_IOCSTATUS_INBAND_NO_CONNECTION = 0x0099,
+
+ /************************************************************************/
+ /* Diagnostic Tools values */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_DIAGNOSTIC_RELEASED = 0x00A0,
+
+ /************************************************************************/
+ /* IOCStatus flag to indicate that log info is available */
+ /************************************************************************/
+
+ MPI_IOCSTATUS_FLAG_LOG_INFO_AVAILABLE = 0x8000,
+ MPI_IOCSTATUS_MASK = 0x7FFF,
+
+ /************************************************************************/
+ /* LogInfo Types */
+ /************************************************************************/
+
+ MPI_IOCLOGINFO_TYPE_MASK = 0xF0000000,
+ MPI_IOCLOGINFO_TYPE_SHIFT = 28,
+ MPI_IOCLOGINFO_TYPE_NONE = 0x0,
+ MPI_IOCLOGINFO_TYPE_SCSI = 0x1,
+ MPI_IOCLOGINFO_TYPE_FC = 0x2,
+ MPI_IOCLOGINFO_TYPE_SAS = 0x3,
+ MPI_IOCLOGINFO_TYPE_ISCSI = 0x4,
+ MPI_IOCLOGINFO_LOG_DATA_MASK = 0x0FFFFFFF,
+};
+
+/****************************************************************************/
+/* SCSI IO messages and associated structures */
+/****************************************************************************/
+
+typedef struct MPIMsgSCSIIORequest {
+ uint8_t TargetID; /* 00h */
+ uint8_t Bus; /* 01h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t CDBLength; /* 04h */
+ uint8_t SenseBufferLength; /* 05h */
+ uint8_t Reserved; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint8_t LUN[8]; /* 0Ch */
+ uint32_t Control; /* 14h */
+ uint8_t CDB[16]; /* 18h */
+ uint32_t DataLength; /* 28h */
+ uint32_t SenseBufferLowAddr; /* 2Ch */
+} QEMU_PACKED MPIMsgSCSIIORequest;
+
+/* SCSI IO MsgFlags bits */
+
+#define MPI_SCSIIO_MSGFLGS_SENSE_WIDTH (0x01)
+#define MPI_SCSIIO_MSGFLGS_SENSE_WIDTH_32 (0x00)
+#define MPI_SCSIIO_MSGFLGS_SENSE_WIDTH_64 (0x01)
+
+#define MPI_SCSIIO_MSGFLGS_SENSE_LOCATION (0x02)
+#define MPI_SCSIIO_MSGFLGS_SENSE_LOC_HOST (0x00)
+#define MPI_SCSIIO_MSGFLGS_SENSE_LOC_IOC (0x02)
+
+#define MPI_SCSIIO_MSGFLGS_CMD_DETERMINES_DATA_DIR (0x04)
+
+/* SCSI IO LUN fields */
+
+#define MPI_SCSIIO_LUN_FIRST_LEVEL_ADDRESSING (0x0000FFFF)
+#define MPI_SCSIIO_LUN_SECOND_LEVEL_ADDRESSING (0xFFFF0000)
+#define MPI_SCSIIO_LUN_THIRD_LEVEL_ADDRESSING (0x0000FFFF)
+#define MPI_SCSIIO_LUN_FOURTH_LEVEL_ADDRESSING (0xFFFF0000)
+#define MPI_SCSIIO_LUN_LEVEL_1_WORD (0xFF00)
+#define MPI_SCSIIO_LUN_LEVEL_1_DWORD (0x0000FF00)
+
+/* SCSI IO Control bits */
+
+#define MPI_SCSIIO_CONTROL_DATADIRECTION_MASK (0x03000000)
+#define MPI_SCSIIO_CONTROL_NODATATRANSFER (0x00000000)
+#define MPI_SCSIIO_CONTROL_WRITE (0x01000000)
+#define MPI_SCSIIO_CONTROL_READ (0x02000000)
+
+#define MPI_SCSIIO_CONTROL_ADDCDBLEN_MASK (0x3C000000)
+#define MPI_SCSIIO_CONTROL_ADDCDBLEN_SHIFT (26)
+
+#define MPI_SCSIIO_CONTROL_TASKATTRIBUTE_MASK (0x00000700)
+#define MPI_SCSIIO_CONTROL_SIMPLEQ (0x00000000)
+#define MPI_SCSIIO_CONTROL_HEADOFQ (0x00000100)
+#define MPI_SCSIIO_CONTROL_ORDEREDQ (0x00000200)
+#define MPI_SCSIIO_CONTROL_ACAQ (0x00000400)
+#define MPI_SCSIIO_CONTROL_UNTAGGED (0x00000500)
+#define MPI_SCSIIO_CONTROL_NO_DISCONNECT (0x00000700)
+
+#define MPI_SCSIIO_CONTROL_TASKMANAGE_MASK (0x00FF0000)
+#define MPI_SCSIIO_CONTROL_OBSOLETE (0x00800000)
+#define MPI_SCSIIO_CONTROL_CLEAR_ACA_RSV (0x00400000)
+#define MPI_SCSIIO_CONTROL_TARGET_RESET (0x00200000)
+#define MPI_SCSIIO_CONTROL_LUN_RESET_RSV (0x00100000)
+#define MPI_SCSIIO_CONTROL_RESERVED (0x00080000)
+#define MPI_SCSIIO_CONTROL_CLR_TASK_SET_RSV (0x00040000)
+#define MPI_SCSIIO_CONTROL_ABORT_TASK_SET (0x00020000)
+#define MPI_SCSIIO_CONTROL_RESERVED2 (0x00010000)
+
+/* SCSI IO reply structure */
+typedef struct MPIMsgSCSIIOReply
+{
+ uint8_t TargetID; /* 00h */
+ uint8_t Bus; /* 01h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t CDBLength; /* 04h */
+ uint8_t SenseBufferLength; /* 05h */
+ uint8_t Reserved; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint8_t SCSIStatus; /* 0Ch */
+ uint8_t SCSIState; /* 0Dh */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+ uint32_t TransferCount; /* 14h */
+ uint32_t SenseCount; /* 18h */
+ uint32_t ResponseInfo; /* 1Ch */
+ uint16_t TaskTag; /* 20h */
+ uint16_t Reserved1; /* 22h */
+} QEMU_PACKED MPIMsgSCSIIOReply;
+
+/* SCSI IO Reply SCSIStatus values (SAM-2 status codes) */
+
+#define MPI_SCSI_STATUS_SUCCESS (0x00)
+#define MPI_SCSI_STATUS_CHECK_CONDITION (0x02)
+#define MPI_SCSI_STATUS_CONDITION_MET (0x04)
+#define MPI_SCSI_STATUS_BUSY (0x08)
+#define MPI_SCSI_STATUS_INTERMEDIATE (0x10)
+#define MPI_SCSI_STATUS_INTERMEDIATE_CONDMET (0x14)
+#define MPI_SCSI_STATUS_RESERVATION_CONFLICT (0x18)
+#define MPI_SCSI_STATUS_COMMAND_TERMINATED (0x22)
+#define MPI_SCSI_STATUS_TASK_SET_FULL (0x28)
+#define MPI_SCSI_STATUS_ACA_ACTIVE (0x30)
+
+#define MPI_SCSI_STATUS_FCPEXT_DEVICE_LOGGED_OUT (0x80)
+#define MPI_SCSI_STATUS_FCPEXT_NO_LINK (0x81)
+#define MPI_SCSI_STATUS_FCPEXT_UNASSIGNED (0x82)
+
+
+/* SCSI IO Reply SCSIState values */
+
+#define MPI_SCSI_STATE_AUTOSENSE_VALID (0x01)
+#define MPI_SCSI_STATE_AUTOSENSE_FAILED (0x02)
+#define MPI_SCSI_STATE_NO_SCSI_STATUS (0x04)
+#define MPI_SCSI_STATE_TERMINATED (0x08)
+#define MPI_SCSI_STATE_RESPONSE_INFO_VALID (0x10)
+#define MPI_SCSI_STATE_QUEUE_TAG_REJECTED (0x20)
+
+/* SCSI IO Reply ResponseInfo values */
+/* (FCP-1 RSP_CODE values and SPI-3 Packetized Failure codes) */
+
+#define MPI_SCSI_RSP_INFO_FUNCTION_COMPLETE (0x00000000)
+#define MPI_SCSI_RSP_INFO_FCP_BURST_LEN_ERROR (0x01000000)
+#define MPI_SCSI_RSP_INFO_CMND_FIELDS_INVALID (0x02000000)
+#define MPI_SCSI_RSP_INFO_FCP_DATA_RO_ERROR (0x03000000)
+#define MPI_SCSI_RSP_INFO_TASK_MGMT_UNSUPPORTED (0x04000000)
+#define MPI_SCSI_RSP_INFO_TASK_MGMT_FAILED (0x05000000)
+#define MPI_SCSI_RSP_INFO_SPI_LQ_INVALID_TYPE (0x06000000)
+
+#define MPI_SCSI_TASKTAG_UNKNOWN (0xFFFF)
+
+
+/****************************************************************************/
+/* SCSI Task Management messages */
+/****************************************************************************/
+
+typedef struct MPIMsgSCSITaskMgmt {
+ uint8_t TargetID; /* 00h */
+ uint8_t Bus; /* 01h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved; /* 04h */
+ uint8_t TaskType; /* 05h */
+ uint8_t Reserved1; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint8_t LUN[8]; /* 0Ch */
+ uint32_t Reserved2[7]; /* 14h */
+ uint32_t TaskMsgContext; /* 30h */
+} QEMU_PACKED MPIMsgSCSITaskMgmt;
+
+enum {
+ /* TaskType values */
+
+ MPI_SCSITASKMGMT_TASKTYPE_ABORT_TASK = 0x01,
+ MPI_SCSITASKMGMT_TASKTYPE_ABRT_TASK_SET = 0x02,
+ MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET = 0x03,
+ MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS = 0x04,
+ MPI_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET = 0x05,
+ MPI_SCSITASKMGMT_TASKTYPE_CLEAR_TASK_SET = 0x06,
+ MPI_SCSITASKMGMT_TASKTYPE_QUERY_TASK = 0x07,
+ MPI_SCSITASKMGMT_TASKTYPE_CLR_ACA = 0x08,
+
+ /* MsgFlags bits */
+
+ MPI_SCSITASKMGMT_MSGFLAGS_DO_NOT_SEND_TASK_IU = 0x01,
+
+ MPI_SCSITASKMGMT_MSGFLAGS_TARGET_RESET_OPTION = 0x00,
+ MPI_SCSITASKMGMT_MSGFLAGS_LIP_RESET_OPTION = 0x02,
+ MPI_SCSITASKMGMT_MSGFLAGS_LIPRESET_RESET_OPTION = 0x04,
+
+ MPI_SCSITASKMGMT_MSGFLAGS_SOFT_RESET_OPTION = 0x08,
+};
+
+/* SCSI Task Management Reply */
+typedef struct MPIMsgSCSITaskMgmtReply {
+ uint8_t TargetID; /* 00h */
+ uint8_t Bus; /* 01h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t ResponseCode; /* 04h */
+ uint8_t TaskType; /* 05h */
+ uint8_t Reserved1; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint8_t Reserved2[2]; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+ uint32_t TerminationCount; /* 14h */
+} QEMU_PACKED MPIMsgSCSITaskMgmtReply;
+
+/* ResponseCode values */
+enum {
+ MPI_SCSITASKMGMT_RSP_TM_COMPLETE = 0x00,
+ MPI_SCSITASKMGMT_RSP_INVALID_FRAME = 0x02,
+ MPI_SCSITASKMGMT_RSP_TM_NOT_SUPPORTED = 0x04,
+ MPI_SCSITASKMGMT_RSP_TM_FAILED = 0x05,
+ MPI_SCSITASKMGMT_RSP_TM_SUCCEEDED = 0x08,
+ MPI_SCSITASKMGMT_RSP_TM_INVALID_LUN = 0x09,
+ MPI_SCSITASKMGMT_RSP_IO_QUEUED_ON_IOC = 0x80,
+};
+
+/****************************************************************************/
+/* IOCInit message */
+/****************************************************************************/
+
+typedef struct MPIMsgIOCInit {
+ uint8_t WhoInit; /* 00h */
+ uint8_t Reserved; /* 01h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Flags; /* 04h */
+ uint8_t MaxDevices; /* 05h */
+ uint8_t MaxBuses; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint16_t ReplyFrameSize; /* 0Ch */
+ uint8_t Reserved1[2]; /* 0Eh */
+ uint32_t HostMfaHighAddr; /* 10h */
+ uint32_t SenseBufferHighAddr; /* 14h */
+ uint32_t ReplyFifoHostSignalingAddr; /* 18h */
+ MPISGEntry HostPageBufferSGE; /* 1Ch */
+ uint16_t MsgVersion; /* 28h */
+ uint16_t HeaderVersion; /* 2Ah */
+} QEMU_PACKED MPIMsgIOCInit;
+
+enum {
+ /* WhoInit values */
+
+ MPI_WHOINIT_NO_ONE = 0x00,
+ MPI_WHOINIT_SYSTEM_BIOS = 0x01,
+ MPI_WHOINIT_ROM_BIOS = 0x02,
+ MPI_WHOINIT_PCI_PEER = 0x03,
+ MPI_WHOINIT_HOST_DRIVER = 0x04,
+ MPI_WHOINIT_MANUFACTURER = 0x05,
+
+ /* Flags values */
+
+ MPI_IOCINIT_FLAGS_HOST_PAGE_BUFFER_PERSISTENT = 0x04,
+ MPI_IOCINIT_FLAGS_REPLY_FIFO_HOST_SIGNAL = 0x02,
+ MPI_IOCINIT_FLAGS_DISCARD_FW_IMAGE = 0x01,
+
+ /* MsgVersion */
+
+ MPI_IOCINIT_MSGVERSION_MAJOR_MASK = 0xFF00,
+ MPI_IOCINIT_MSGVERSION_MAJOR_SHIFT = 8,
+ MPI_IOCINIT_MSGVERSION_MINOR_MASK = 0x00FF,
+ MPI_IOCINIT_MSGVERSION_MINOR_SHIFT = 0,
+
+ /* HeaderVersion */
+
+ MPI_IOCINIT_HEADERVERSION_UNIT_MASK = 0xFF00,
+ MPI_IOCINIT_HEADERVERSION_UNIT_SHIFT = 8,
+ MPI_IOCINIT_HEADERVERSION_DEV_MASK = 0x00FF,
+ MPI_IOCINIT_HEADERVERSION_DEV_SHIFT = 0,
+};
+
+typedef struct MPIMsgIOCInitReply {
+ uint8_t WhoInit; /* 00h */
+ uint8_t Reserved; /* 01h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Flags; /* 04h */
+ uint8_t MaxDevices; /* 05h */
+ uint8_t MaxBuses; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint16_t Reserved2; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+} QEMU_PACKED MPIMsgIOCInitReply;
+
+
+
+/****************************************************************************/
+/* IOC Facts message */
+/****************************************************************************/
+
+typedef struct MPIMsgIOCFacts {
+ uint8_t Reserved[2]; /* 00h */
+ uint8_t ChainOffset; /* 01h */
+ uint8_t Function; /* 02h */
+ uint8_t Reserved1[3]; /* 03h */
+ uint8_t MsgFlags; /* 04h */
+ uint32_t MsgContext; /* 08h */
+} QEMU_PACKED MPIMsgIOCFacts;
+
+/* IOC Facts Reply */
+typedef struct MPIMsgIOCFactsReply {
+ uint16_t MsgVersion; /* 00h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint16_t HeaderVersion; /* 04h */
+ uint8_t IOCNumber; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint16_t IOCExceptions; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+ uint8_t MaxChainDepth; /* 14h */
+ uint8_t WhoInit; /* 15h */
+ uint8_t BlockSize; /* 16h */
+ uint8_t Flags; /* 17h */
+ uint16_t ReplyQueueDepth; /* 18h */
+ uint16_t RequestFrameSize; /* 1Ah */
+ uint16_t Reserved_0101_FWVersion; /* 1Ch */ /* obsolete 16-bit FWVersion */
+ uint16_t ProductID; /* 1Eh */
+ uint32_t CurrentHostMfaHighAddr; /* 20h */
+ uint16_t GlobalCredits; /* 24h */
+ uint8_t NumberOfPorts; /* 26h */
+ uint8_t EventState; /* 27h */
+ uint32_t CurrentSenseBufferHighAddr; /* 28h */
+ uint16_t CurReplyFrameSize; /* 2Ch */
+ uint8_t MaxDevices; /* 2Eh */
+ uint8_t MaxBuses; /* 2Fh */
+ uint32_t FWImageSize; /* 30h */
+ uint32_t IOCCapabilities; /* 34h */
+ uint8_t FWVersionDev; /* 38h */
+ uint8_t FWVersionUnit; /* 39h */
+ uint8_t FWVersionMinor; /* 3ah */
+ uint8_t FWVersionMajor; /* 3bh */
+ uint16_t HighPriorityQueueDepth; /* 3Ch */
+ uint16_t Reserved2; /* 3Eh */
+ MPISGEntry HostPageBufferSGE; /* 40h */
+ uint32_t ReplyFifoHostSignalingAddr; /* 4Ch */
+} QEMU_PACKED MPIMsgIOCFactsReply;
+
+enum {
+ MPI_IOCFACTS_MSGVERSION_MAJOR_MASK = 0xFF00,
+ MPI_IOCFACTS_MSGVERSION_MAJOR_SHIFT = 8,
+ MPI_IOCFACTS_MSGVERSION_MINOR_MASK = 0x00FF,
+ MPI_IOCFACTS_MSGVERSION_MINOR_SHIFT = 0,
+
+ MPI_IOCFACTS_HDRVERSION_UNIT_MASK = 0xFF00,
+ MPI_IOCFACTS_HDRVERSION_UNIT_SHIFT = 8,
+ MPI_IOCFACTS_HDRVERSION_DEV_MASK = 0x00FF,
+ MPI_IOCFACTS_HDRVERSION_DEV_SHIFT = 0,
+
+ MPI_IOCFACTS_EXCEPT_CONFIG_CHECKSUM_FAIL = 0x0001,
+ MPI_IOCFACTS_EXCEPT_RAID_CONFIG_INVALID = 0x0002,
+ MPI_IOCFACTS_EXCEPT_FW_CHECKSUM_FAIL = 0x0004,
+ MPI_IOCFACTS_EXCEPT_PERSISTENT_TABLE_FULL = 0x0008,
+ MPI_IOCFACTS_EXCEPT_METADATA_UNSUPPORTED = 0x0010,
+
+ MPI_IOCFACTS_FLAGS_FW_DOWNLOAD_BOOT = 0x01,
+ MPI_IOCFACTS_FLAGS_REPLY_FIFO_HOST_SIGNAL = 0x02,
+ MPI_IOCFACTS_FLAGS_HOST_PAGE_BUFFER_PERSISTENT = 0x04,
+
+ MPI_IOCFACTS_EVENTSTATE_DISABLED = 0x00,
+ MPI_IOCFACTS_EVENTSTATE_ENABLED = 0x01,
+
+ MPI_IOCFACTS_CAPABILITY_HIGH_PRI_Q = 0x00000001,
+ MPI_IOCFACTS_CAPABILITY_REPLY_HOST_SIGNAL = 0x00000002,
+ MPI_IOCFACTS_CAPABILITY_QUEUE_FULL_HANDLING = 0x00000004,
+ MPI_IOCFACTS_CAPABILITY_DIAG_TRACE_BUFFER = 0x00000008,
+ MPI_IOCFACTS_CAPABILITY_SNAPSHOT_BUFFER = 0x00000010,
+ MPI_IOCFACTS_CAPABILITY_EXTENDED_BUFFER = 0x00000020,
+ MPI_IOCFACTS_CAPABILITY_EEDP = 0x00000040,
+ MPI_IOCFACTS_CAPABILITY_BIDIRECTIONAL = 0x00000080,
+ MPI_IOCFACTS_CAPABILITY_MULTICAST = 0x00000100,
+ MPI_IOCFACTS_CAPABILITY_SCSIIO32 = 0x00000200,
+ MPI_IOCFACTS_CAPABILITY_NO_SCSIIO16 = 0x00000400,
+ MPI_IOCFACTS_CAPABILITY_TLR = 0x00000800,
+};
+
+/****************************************************************************/
+/* Port Facts message and Reply */
+/****************************************************************************/
+
+typedef struct MPIMsgPortFacts {
+ uint8_t Reserved[2]; /* 00h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved1[2]; /* 04h */
+ uint8_t PortNumber; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+} QEMU_PACKED MPIMsgPortFacts;
+
+typedef struct MPIMsgPortFactsReply {
+ uint16_t Reserved; /* 00h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint16_t Reserved1; /* 04h */
+ uint8_t PortNumber; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint16_t Reserved2; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+ uint8_t Reserved3; /* 14h */
+ uint8_t PortType; /* 15h */
+ uint16_t MaxDevices; /* 16h */
+ uint16_t PortSCSIID; /* 18h */
+ uint16_t ProtocolFlags; /* 1Ah */
+ uint16_t MaxPostedCmdBuffers; /* 1Ch */
+ uint16_t MaxPersistentIDs; /* 1Eh */
+ uint16_t MaxLanBuckets; /* 20h */
+ uint8_t MaxInitiators; /* 22h */
+ uint8_t Reserved4; /* 23h */
+ uint32_t Reserved5; /* 24h */
+} QEMU_PACKED MPIMsgPortFactsReply;
+
+
+enum {
+ /* PortTypes values */
+ MPI_PORTFACTS_PORTTYPE_INACTIVE = 0x00,
+ MPI_PORTFACTS_PORTTYPE_SCSI = 0x01,
+ MPI_PORTFACTS_PORTTYPE_FC = 0x10,
+ MPI_PORTFACTS_PORTTYPE_ISCSI = 0x20,
+ MPI_PORTFACTS_PORTTYPE_SAS = 0x30,
+
+ /* ProtocolFlags values */
+ MPI_PORTFACTS_PROTOCOL_LOGBUSADDR = 0x01,
+ MPI_PORTFACTS_PROTOCOL_LAN = 0x02,
+ MPI_PORTFACTS_PROTOCOL_TARGET = 0x04,
+ MPI_PORTFACTS_PROTOCOL_INITIATOR = 0x08,
+};
+
+
+/****************************************************************************/
+/* Port Enable Message */
+/****************************************************************************/
+
+typedef struct MPIMsgPortEnable {
+ uint8_t Reserved[2]; /* 00h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved1[2]; /* 04h */
+ uint8_t PortNumber; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+} QEMU_PACKED MPIMsgPortEnable;
+
+typedef struct MPIMsgPortEnableReply {
+ uint8_t Reserved[2]; /* 00h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved1[2]; /* 04h */
+ uint8_t PortNumber; /* 05h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint16_t Reserved2; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+} QEMU_PACKED MPIMsgPortEnableReply;
+
+/****************************************************************************/
+/* Event Notification messages */
+/****************************************************************************/
+
+typedef struct MPIMsgEventNotify {
+ uint8_t Switch; /* 00h */
+ uint8_t Reserved; /* 01h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved1[3]; /* 04h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+} QEMU_PACKED MPIMsgEventNotify;
+
+/* Event Notification Reply */
+
+typedef struct MPIMsgEventNotifyReply {
+ uint16_t EventDataLength; /* 00h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved1[2]; /* 04h */
+ uint8_t AckRequired; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint8_t Reserved2[2]; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+ uint32_t Event; /* 14h */
+ uint32_t EventContext; /* 18h */
+ uint32_t Data[1]; /* 1Ch */
+} QEMU_PACKED MPIMsgEventNotifyReply;
+
+/* Event Acknowledge */
+
+typedef struct MPIMsgEventAck {
+ uint8_t Reserved[2]; /* 00h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved1[3]; /* 04h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint32_t Event; /* 0Ch */
+ uint32_t EventContext; /* 10h */
+} QEMU_PACKED MPIMsgEventAck;
+
+typedef struct MPIMsgEventAckReply {
+ uint8_t Reserved[2]; /* 00h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint8_t Reserved1[3]; /* 04h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint16_t Reserved2; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+} QEMU_PACKED MPIMsgEventAckReply;
+
+enum {
+ /* Switch */
+
+ MPI_EVENT_NOTIFICATION_SWITCH_OFF = 0x00,
+ MPI_EVENT_NOTIFICATION_SWITCH_ON = 0x01,
+
+ /* Event */
+
+ MPI_EVENT_NONE = 0x00000000,
+ MPI_EVENT_LOG_DATA = 0x00000001,
+ MPI_EVENT_STATE_CHANGE = 0x00000002,
+ MPI_EVENT_UNIT_ATTENTION = 0x00000003,
+ MPI_EVENT_IOC_BUS_RESET = 0x00000004,
+ MPI_EVENT_EXT_BUS_RESET = 0x00000005,
+ MPI_EVENT_RESCAN = 0x00000006,
+ MPI_EVENT_LINK_STATUS_CHANGE = 0x00000007,
+ MPI_EVENT_LOOP_STATE_CHANGE = 0x00000008,
+ MPI_EVENT_LOGOUT = 0x00000009,
+ MPI_EVENT_EVENT_CHANGE = 0x0000000A,
+ MPI_EVENT_INTEGRATED_RAID = 0x0000000B,
+ MPI_EVENT_SCSI_DEVICE_STATUS_CHANGE = 0x0000000C,
+ MPI_EVENT_ON_BUS_TIMER_EXPIRED = 0x0000000D,
+ MPI_EVENT_QUEUE_FULL = 0x0000000E,
+ MPI_EVENT_SAS_DEVICE_STATUS_CHANGE = 0x0000000F,
+ MPI_EVENT_SAS_SES = 0x00000010,
+ MPI_EVENT_PERSISTENT_TABLE_FULL = 0x00000011,
+ MPI_EVENT_SAS_PHY_LINK_STATUS = 0x00000012,
+ MPI_EVENT_SAS_DISCOVERY_ERROR = 0x00000013,
+ MPI_EVENT_IR_RESYNC_UPDATE = 0x00000014,
+ MPI_EVENT_IR2 = 0x00000015,
+ MPI_EVENT_SAS_DISCOVERY = 0x00000016,
+ MPI_EVENT_SAS_BROADCAST_PRIMITIVE = 0x00000017,
+ MPI_EVENT_SAS_INIT_DEVICE_STATUS_CHANGE = 0x00000018,
+ MPI_EVENT_SAS_INIT_TABLE_OVERFLOW = 0x00000019,
+ MPI_EVENT_SAS_SMP_ERROR = 0x0000001A,
+ MPI_EVENT_SAS_EXPANDER_STATUS_CHANGE = 0x0000001B,
+ MPI_EVENT_LOG_ENTRY_ADDED = 0x00000021,
+
+ /* AckRequired field values */
+
+ MPI_EVENT_NOTIFICATION_ACK_NOT_REQUIRED = 0x00,
+ MPI_EVENT_NOTIFICATION_ACK_REQUIRED = 0x01,
+};
+
+/****************************************************************************
+* Config Request Message
+****************************************************************************/
+
+typedef struct MPIMsgConfig {
+ uint8_t Action; /* 00h */
+ uint8_t Reserved; /* 01h */
+ uint8_t ChainOffset; /* 02h */
+ uint8_t Function; /* 03h */
+ uint16_t ExtPageLength; /* 04h */
+ uint8_t ExtPageType; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint8_t Reserved2[8]; /* 0Ch */
+ uint8_t PageVersion; /* 14h */
+ uint8_t PageLength; /* 15h */
+ uint8_t PageNumber; /* 16h */
+ uint8_t PageType; /* 17h */
+ uint32_t PageAddress; /* 18h */
+ MPISGEntry PageBufferSGE; /* 1Ch */
+} QEMU_PACKED MPIMsgConfig;
+
+/* Action field values */
+
+enum {
+ MPI_CONFIG_ACTION_PAGE_HEADER = 0x00,
+ MPI_CONFIG_ACTION_PAGE_READ_CURRENT = 0x01,
+ MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT = 0x02,
+ MPI_CONFIG_ACTION_PAGE_DEFAULT = 0x03,
+ MPI_CONFIG_ACTION_PAGE_WRITE_NVRAM = 0x04,
+ MPI_CONFIG_ACTION_PAGE_READ_DEFAULT = 0x05,
+ MPI_CONFIG_ACTION_PAGE_READ_NVRAM = 0x06,
+};
+
+
+/* Config Reply Message */
+typedef struct MPIMsgConfigReply {
+ uint8_t Action; /* 00h */
+ uint8_t Reserved; /* 01h */
+ uint8_t MsgLength; /* 02h */
+ uint8_t Function; /* 03h */
+ uint16_t ExtPageLength; /* 04h */
+ uint8_t ExtPageType; /* 06h */
+ uint8_t MsgFlags; /* 07h */
+ uint32_t MsgContext; /* 08h */
+ uint8_t Reserved2[2]; /* 0Ch */
+ uint16_t IOCStatus; /* 0Eh */
+ uint32_t IOCLogInfo; /* 10h */
+ uint8_t PageVersion; /* 14h */
+ uint8_t PageLength; /* 15h */
+ uint8_t PageNumber; /* 16h */
+ uint8_t PageType; /* 17h */
+} QEMU_PACKED MPIMsgConfigReply;
+
+enum {
+ /* PageAddress field values */
+ MPI_CONFIG_PAGEATTR_READ_ONLY = 0x00,
+ MPI_CONFIG_PAGEATTR_CHANGEABLE = 0x10,
+ MPI_CONFIG_PAGEATTR_PERSISTENT = 0x20,
+ MPI_CONFIG_PAGEATTR_RO_PERSISTENT = 0x30,
+ MPI_CONFIG_PAGEATTR_MASK = 0xF0,
+
+ MPI_CONFIG_PAGETYPE_IO_UNIT = 0x00,
+ MPI_CONFIG_PAGETYPE_IOC = 0x01,
+ MPI_CONFIG_PAGETYPE_BIOS = 0x02,
+ MPI_CONFIG_PAGETYPE_SCSI_PORT = 0x03,
+ MPI_CONFIG_PAGETYPE_SCSI_DEVICE = 0x04,
+ MPI_CONFIG_PAGETYPE_FC_PORT = 0x05,
+ MPI_CONFIG_PAGETYPE_FC_DEVICE = 0x06,
+ MPI_CONFIG_PAGETYPE_LAN = 0x07,
+ MPI_CONFIG_PAGETYPE_RAID_VOLUME = 0x08,
+ MPI_CONFIG_PAGETYPE_MANUFACTURING = 0x09,
+ MPI_CONFIG_PAGETYPE_RAID_PHYSDISK = 0x0A,
+ MPI_CONFIG_PAGETYPE_INBAND = 0x0B,
+ MPI_CONFIG_PAGETYPE_EXTENDED = 0x0F,
+ MPI_CONFIG_PAGETYPE_MASK = 0x0F,
+
+ MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT = 0x10,
+ MPI_CONFIG_EXTPAGETYPE_SAS_EXPANDER = 0x11,
+ MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE = 0x12,
+ MPI_CONFIG_EXTPAGETYPE_SAS_PHY = 0x13,
+ MPI_CONFIG_EXTPAGETYPE_LOG = 0x14,
+ MPI_CONFIG_EXTPAGETYPE_ENCLOSURE = 0x15,
+
+ MPI_SCSI_PORT_PGAD_PORT_MASK = 0x000000FF,
+
+ MPI_SCSI_DEVICE_FORM_MASK = 0xF0000000,
+ MPI_SCSI_DEVICE_FORM_BUS_TID = 0x00000000,
+ MPI_SCSI_DEVICE_TARGET_ID_MASK = 0x000000FF,
+ MPI_SCSI_DEVICE_TARGET_ID_SHIFT = 0,
+ MPI_SCSI_DEVICE_BUS_MASK = 0x0000FF00,
+ MPI_SCSI_DEVICE_BUS_SHIFT = 8,
+ MPI_SCSI_DEVICE_FORM_TARGET_MODE = 0x10000000,
+ MPI_SCSI_DEVICE_TM_RESPOND_ID_MASK = 0x000000FF,
+ MPI_SCSI_DEVICE_TM_RESPOND_ID_SHIFT = 0,
+ MPI_SCSI_DEVICE_TM_BUS_MASK = 0x0000FF00,
+ MPI_SCSI_DEVICE_TM_BUS_SHIFT = 8,
+ MPI_SCSI_DEVICE_TM_INIT_ID_MASK = 0x00FF0000,
+ MPI_SCSI_DEVICE_TM_INIT_ID_SHIFT = 16,
+
+ MPI_FC_PORT_PGAD_PORT_MASK = 0xF0000000,
+ MPI_FC_PORT_PGAD_PORT_SHIFT = 28,
+ MPI_FC_PORT_PGAD_FORM_MASK = 0x0F000000,
+ MPI_FC_PORT_PGAD_FORM_INDEX = 0x01000000,
+ MPI_FC_PORT_PGAD_INDEX_MASK = 0x0000FFFF,
+ MPI_FC_PORT_PGAD_INDEX_SHIFT = 0,
+
+ MPI_FC_DEVICE_PGAD_PORT_MASK = 0xF0000000,
+ MPI_FC_DEVICE_PGAD_PORT_SHIFT = 28,
+ MPI_FC_DEVICE_PGAD_FORM_MASK = 0x0F000000,
+ MPI_FC_DEVICE_PGAD_FORM_NEXT_DID = 0x00000000,
+ MPI_FC_DEVICE_PGAD_ND_PORT_MASK = 0xF0000000,
+ MPI_FC_DEVICE_PGAD_ND_PORT_SHIFT = 28,
+ MPI_FC_DEVICE_PGAD_ND_DID_MASK = 0x00FFFFFF,
+ MPI_FC_DEVICE_PGAD_ND_DID_SHIFT = 0,
+ MPI_FC_DEVICE_PGAD_FORM_BUS_TID = 0x01000000,
+ MPI_FC_DEVICE_PGAD_BT_BUS_MASK = 0x0000FF00,
+ MPI_FC_DEVICE_PGAD_BT_BUS_SHIFT = 8,
+ MPI_FC_DEVICE_PGAD_BT_TID_MASK = 0x000000FF,
+ MPI_FC_DEVICE_PGAD_BT_TID_SHIFT = 0,
+
+ MPI_PHYSDISK_PGAD_PHYSDISKNUM_MASK = 0x000000FF,
+ MPI_PHYSDISK_PGAD_PHYSDISKNUM_SHIFT = 0,
+
+ MPI_SAS_EXPAND_PGAD_FORM_MASK = 0xF0000000,
+ MPI_SAS_EXPAND_PGAD_FORM_SHIFT = 28,
+ MPI_SAS_EXPAND_PGAD_FORM_GET_NEXT_HANDLE = 0x00000000,
+ MPI_SAS_EXPAND_PGAD_FORM_HANDLE_PHY_NUM = 0x00000001,
+ MPI_SAS_EXPAND_PGAD_FORM_HANDLE = 0x00000002,
+ MPI_SAS_EXPAND_PGAD_GNH_MASK_HANDLE = 0x0000FFFF,
+ MPI_SAS_EXPAND_PGAD_GNH_SHIFT_HANDLE = 0,
+ MPI_SAS_EXPAND_PGAD_HPN_MASK_PHY = 0x00FF0000,
+ MPI_SAS_EXPAND_PGAD_HPN_SHIFT_PHY = 16,
+ MPI_SAS_EXPAND_PGAD_HPN_MASK_HANDLE = 0x0000FFFF,
+ MPI_SAS_EXPAND_PGAD_HPN_SHIFT_HANDLE = 0,
+ MPI_SAS_EXPAND_PGAD_H_MASK_HANDLE = 0x0000FFFF,
+ MPI_SAS_EXPAND_PGAD_H_SHIFT_HANDLE = 0,
+
+ MPI_SAS_DEVICE_PGAD_FORM_MASK = 0xF0000000,
+ MPI_SAS_DEVICE_PGAD_FORM_SHIFT = 28,
+ MPI_SAS_DEVICE_PGAD_FORM_GET_NEXT_HANDLE = 0x00000000,
+ MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID = 0x00000001,
+ MPI_SAS_DEVICE_PGAD_FORM_HANDLE = 0x00000002,
+ MPI_SAS_DEVICE_PGAD_GNH_HANDLE_MASK = 0x0000FFFF,
+ MPI_SAS_DEVICE_PGAD_GNH_HANDLE_SHIFT = 0,
+ MPI_SAS_DEVICE_PGAD_BT_BUS_MASK = 0x0000FF00,
+ MPI_SAS_DEVICE_PGAD_BT_BUS_SHIFT = 8,
+ MPI_SAS_DEVICE_PGAD_BT_TID_MASK = 0x000000FF,
+ MPI_SAS_DEVICE_PGAD_BT_TID_SHIFT = 0,
+ MPI_SAS_DEVICE_PGAD_H_HANDLE_MASK = 0x0000FFFF,
+ MPI_SAS_DEVICE_PGAD_H_HANDLE_SHIFT = 0,
+
+ MPI_SAS_PHY_PGAD_FORM_MASK = 0xF0000000,
+ MPI_SAS_PHY_PGAD_FORM_SHIFT = 28,
+ MPI_SAS_PHY_PGAD_FORM_PHY_NUMBER = 0x0,
+ MPI_SAS_PHY_PGAD_FORM_PHY_TBL_INDEX = 0x1,
+ MPI_SAS_PHY_PGAD_PHY_NUMBER_MASK = 0x000000FF,
+ MPI_SAS_PHY_PGAD_PHY_NUMBER_SHIFT = 0,
+ MPI_SAS_PHY_PGAD_PHY_TBL_INDEX_MASK = 0x0000FFFF,
+ MPI_SAS_PHY_PGAD_PHY_TBL_INDEX_SHIFT = 0,
+
+ MPI_SAS_ENCLOS_PGAD_FORM_MASK = 0xF0000000,
+ MPI_SAS_ENCLOS_PGAD_FORM_SHIFT = 28,
+ MPI_SAS_ENCLOS_PGAD_FORM_GET_NEXT_HANDLE = 0x00000000,
+ MPI_SAS_ENCLOS_PGAD_FORM_HANDLE = 0x00000001,
+ MPI_SAS_ENCLOS_PGAD_GNH_HANDLE_MASK = 0x0000FFFF,
+ MPI_SAS_ENCLOS_PGAD_GNH_HANDLE_SHIFT = 0,
+ MPI_SAS_ENCLOS_PGAD_H_HANDLE_MASK = 0x0000FFFF,
+ MPI_SAS_ENCLOS_PGAD_H_HANDLE_SHIFT = 0,
+};
+
+/* Too many structs and definitions... see mptconfig.c for the few
+ * that are used.
+ */
+
+/****************************************************************************/
+/* Firmware Upload message and associated structures */
+/****************************************************************************/
+
+enum {
+ /* defines for using the ProductId field */
+ MPI_FW_HEADER_PID_TYPE_MASK = 0xF000,
+ MPI_FW_HEADER_PID_TYPE_SCSI = 0x0000,
+ MPI_FW_HEADER_PID_TYPE_FC = 0x1000,
+ MPI_FW_HEADER_PID_TYPE_SAS = 0x2000,
+
+ MPI_FW_HEADER_PID_PROD_MASK = 0x0F00,
+ MPI_FW_HEADER_PID_PROD_INITIATOR_SCSI = 0x0100,
+ MPI_FW_HEADER_PID_PROD_TARGET_INITIATOR_SCSI = 0x0200,
+ MPI_FW_HEADER_PID_PROD_TARGET_SCSI = 0x0300,
+ MPI_FW_HEADER_PID_PROD_IM_SCSI = 0x0400,
+ MPI_FW_HEADER_PID_PROD_IS_SCSI = 0x0500,
+ MPI_FW_HEADER_PID_PROD_CTX_SCSI = 0x0600,
+ MPI_FW_HEADER_PID_PROD_IR_SCSI = 0x0700,
+
+ MPI_FW_HEADER_PID_FAMILY_MASK = 0x00FF,
+
+ /* SCSI */
+ MPI_FW_HEADER_PID_FAMILY_1030A0_SCSI = 0x0001,
+ MPI_FW_HEADER_PID_FAMILY_1030B0_SCSI = 0x0002,
+ MPI_FW_HEADER_PID_FAMILY_1030B1_SCSI = 0x0003,
+ MPI_FW_HEADER_PID_FAMILY_1030C0_SCSI = 0x0004,
+ MPI_FW_HEADER_PID_FAMILY_1020A0_SCSI = 0x0005,
+ MPI_FW_HEADER_PID_FAMILY_1020B0_SCSI = 0x0006,
+ MPI_FW_HEADER_PID_FAMILY_1020B1_SCSI = 0x0007,
+ MPI_FW_HEADER_PID_FAMILY_1020C0_SCSI = 0x0008,
+ MPI_FW_HEADER_PID_FAMILY_1035A0_SCSI = 0x0009,
+ MPI_FW_HEADER_PID_FAMILY_1035B0_SCSI = 0x000A,
+ MPI_FW_HEADER_PID_FAMILY_1030TA0_SCSI = 0x000B,
+ MPI_FW_HEADER_PID_FAMILY_1020TA0_SCSI = 0x000C,
+
+ /* Fibre Channel */
+ MPI_FW_HEADER_PID_FAMILY_909_FC = 0x0000,
+ MPI_FW_HEADER_PID_FAMILY_919_FC = 0x0001, /* 919 and 929 */
+ MPI_FW_HEADER_PID_FAMILY_919X_FC = 0x0002, /* 919X and 929X */
+ MPI_FW_HEADER_PID_FAMILY_919XL_FC = 0x0003, /* 919XL and 929XL */
+ MPI_FW_HEADER_PID_FAMILY_939X_FC = 0x0004, /* 939X and 949X */
+ MPI_FW_HEADER_PID_FAMILY_959_FC = 0x0005,
+ MPI_FW_HEADER_PID_FAMILY_949E_FC = 0x0006,
+
+ /* SAS */
+ MPI_FW_HEADER_PID_FAMILY_1064_SAS = 0x0001,
+ MPI_FW_HEADER_PID_FAMILY_1068_SAS = 0x0002,
+ MPI_FW_HEADER_PID_FAMILY_1078_SAS = 0x0003,
+ MPI_FW_HEADER_PID_FAMILY_106xE_SAS = 0x0004, /* 1068E, 1066E, and 1064E */
+};
+
+#endif
diff --git a/hw/scsi/mptconfig.c b/hw/scsi/mptconfig.c
new file mode 100644
index 00000000..19d01f39
--- /dev/null
+++ b/hw/scsi/mptconfig.c
@@ -0,0 +1,904 @@
+/*
+ * QEMU LSI SAS1068 Host Bus Adapter emulation - configuration pages
+ *
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Author: Paolo Bonzini
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/scsi/scsi.h"
+
+#include "mptsas.h"
+#include "mpi.h"
+#include "trace.h"
+
+/* Generic functions for marshaling and unmarshaling. */
+
+#define repl1(x) x
+#define repl2(x) x x
+#define repl3(x) x x x
+#define repl4(x) x x x x
+#define repl5(x) x x x x x
+#define repl6(x) x x x x x x
+#define repl7(x) x x x x x x x
+#define repl8(x) x x x x x x x x
+
+#define repl(n, x) glue(repl, n)(x)
+
+typedef union PackValue {
+ uint64_t ll;
+ char *str;
+} PackValue;
+
+static size_t vfill(uint8_t *data, size_t size, const char *fmt, va_list ap)
+{
+ size_t ofs;
+ PackValue val;
+ const char *p;
+
+ ofs = 0;
+ p = fmt;
+ while (*p) {
+ memset(&val, 0, sizeof(val));
+ switch (*p) {
+ case '*':
+ p++;
+ break;
+ case 'b':
+ case 'w':
+ case 'l':
+ val.ll = va_arg(ap, int);
+ break;
+ case 'q':
+ val.ll = va_arg(ap, int64_t);
+ break;
+ case 's':
+ val.str = va_arg(ap, void *);
+ break;
+ }
+ switch (*p++) {
+ case 'b':
+ if (data) {
+ stb_p(data + ofs, val.ll);
+ }
+ ofs++;
+ break;
+ case 'w':
+ if (data) {
+ stw_le_p(data + ofs, val.ll);
+ }
+ ofs += 2;
+ break;
+ case 'l':
+ if (data) {
+ stl_le_p(data + ofs, val.ll);
+ }
+ ofs += 4;
+ break;
+ case 'q':
+ if (data) {
+ stq_le_p(data + ofs, val.ll);
+ }
+ ofs += 8;
+ break;
+ case 's':
+ {
+ int cnt = atoi(p);
+ if (data) {
+ if (val.str) {
+ strncpy((void *)data + ofs, val.str, cnt);
+ } else {
+ memset((void *)data + ofs, 0, cnt);
+ }
+ }
+ ofs += cnt;
+ break;
+ }
+ }
+ }
+
+ return ofs;
+}
+
+static size_t vpack(uint8_t **p_data, const char *fmt, va_list ap1)
+{
+ size_t size = 0;
+ uint8_t *data = NULL;
+
+ if (p_data) {
+ va_list ap2;
+
+ va_copy(ap2, ap1);
+ size = vfill(NULL, 0, fmt, ap2);
+ *p_data = data = g_malloc(size);
+ va_end(ap2);
+ }
+ return vfill(data, size, fmt, ap1);
+}
+
+static size_t fill(uint8_t *data, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ size_t ret;
+
+ va_start(ap, fmt);
+ ret = vfill(data, size, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+/* Functions to build the page header and fill in the length, always used
+ * through the macros.
+ */
+
+#define MPTSAS_CONFIG_PACK(number, type, version, fmt, ...) \
+ mptsas_config_pack(data, "b*bbb" fmt, version, number, type, \
+ ## __VA_ARGS__)
+
+static size_t mptsas_config_pack(uint8_t **data, const char *fmt, ...)
+{
+ va_list ap;
+ size_t ret;
+
+ va_start(ap, fmt);
+ ret = vpack(data, fmt, ap);
+ va_end(ap);
+
+ if (data) {
+ assert(ret / 4 < 256 && (ret % 4) == 0);
+ stb_p(*data + 1, ret / 4);
+ }
+ return ret;
+}
+
+#define MPTSAS_CONFIG_PACK_EXT(number, type, version, fmt, ...) \
+ mptsas_config_pack_ext(data, "b*bbb*wb*b" fmt, version, number, \
+ MPI_CONFIG_PAGETYPE_EXTENDED, type, ## __VA_ARGS__)
+
+static size_t mptsas_config_pack_ext(uint8_t **data, const char *fmt, ...)
+{
+ va_list ap;
+ size_t ret;
+
+ va_start(ap, fmt);
+ ret = vpack(data, fmt, ap);
+ va_end(ap);
+
+ if (data) {
+ assert(ret < 65536 && (ret % 4) == 0);
+ stw_le_p(*data + 4, ret / 4);
+ }
+ return ret;
+}
+
+/* Manufacturing pages */
+
+static
+size_t mptsas_config_manufacturing_0(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(0, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "s16s8s16s16s16",
+ "QEMU MPT Fusion",
+ "2.5",
+ "QEMU MPT Fusion",
+ "QEMU",
+ "0000111122223333");
+}
+
+static
+size_t mptsas_config_manufacturing_1(MPTSASState *s, uint8_t **data, int address)
+{
+ /* VPD - all zeros */
+ return MPTSAS_CONFIG_PACK(1, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "*s256");
+}
+
+static
+size_t mptsas_config_manufacturing_2(MPTSASState *s, uint8_t **data, int address)
+{
+ PCIDeviceClass *pcic = PCI_DEVICE_GET_CLASS(s);
+ return MPTSAS_CONFIG_PACK(2, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "wb*b*l",
+ pcic->device_id, pcic->revision);
+}
+
+static
+size_t mptsas_config_manufacturing_3(MPTSASState *s, uint8_t **data, int address)
+{
+ PCIDeviceClass *pcic = PCI_DEVICE_GET_CLASS(s);
+ return MPTSAS_CONFIG_PACK(3, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "wb*b*l",
+ pcic->device_id, pcic->revision);
+}
+
+static
+size_t mptsas_config_manufacturing_4(MPTSASState *s, uint8_t **data, int address)
+{
+ /* All zeros */
+ return MPTSAS_CONFIG_PACK(4, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x05,
+ "*l*b*b*b*b*b*b*w*s56*l*l*l*l*l*l"
+ "*b*b*w*b*b*w*l*l");
+}
+
+static
+size_t mptsas_config_manufacturing_5(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(5, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x02,
+ "q*b*b*w*l*l", s->sas_addr);
+}
+
+static
+size_t mptsas_config_manufacturing_6(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(6, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "*l");
+}
+
+static
+size_t mptsas_config_manufacturing_7(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(7, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "*l*l*l*s16*b*b*w", MPTSAS_NUM_PORTS);
+}
+
+static
+size_t mptsas_config_manufacturing_8(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(8, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "*l");
+}
+
+static
+size_t mptsas_config_manufacturing_9(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(9, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "*l");
+}
+
+static
+size_t mptsas_config_manufacturing_10(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(10, MPI_CONFIG_PAGETYPE_MANUFACTURING, 0x00,
+ "*l");
+}
+
+/* I/O unit pages */
+
+static
+size_t mptsas_config_io_unit_0(MPTSASState *s, uint8_t **data, int address)
+{
+ PCIDevice *pci = PCI_DEVICE(s);
+ uint64_t unique_value = 0x53504D554D4551LL; /* "QEMUMPTx" */
+
+ unique_value |= (uint64_t)pci->devfn << 56;
+ return MPTSAS_CONFIG_PACK(0, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x00,
+ "q", unique_value);
+}
+
+static
+size_t mptsas_config_io_unit_1(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(1, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x02, "l",
+ 0x41 /* single function, RAID disabled */ );
+}
+
+static
+size_t mptsas_config_io_unit_2(MPTSASState *s, uint8_t **data, int address)
+{
+ PCIDevice *pci = PCI_DEVICE(s);
+ uint8_t devfn = pci->devfn;
+ return MPTSAS_CONFIG_PACK(2, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x02,
+ "llbbw*b*b*w*b*b*w*b*b*w*l",
+ 0, 0x100, 0 /* pci bus? */, devfn, 0);
+}
+
+static
+size_t mptsas_config_io_unit_3(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(3, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x01,
+ "*b*b*w*l");
+}
+
+static
+size_t mptsas_config_io_unit_4(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(4, MPI_CONFIG_PAGETYPE_IO_UNIT, 0x00, "*l*l*q");
+}
+
+/* I/O controller pages */
+
+static
+size_t mptsas_config_ioc_0(MPTSASState *s, uint8_t **data, int address)
+{
+ PCIDeviceClass *pcic = PCI_DEVICE_GET_CLASS(s);
+
+ return MPTSAS_CONFIG_PACK(0, MPI_CONFIG_PAGETYPE_IOC, 0x01,
+ "*l*lwwb*b*b*blww",
+ pcic->vendor_id, pcic->device_id, pcic->revision,
+ pcic->class_id, pcic->subsystem_vendor_id,
+ pcic->subsystem_id);
+}
+
+static
+size_t mptsas_config_ioc_1(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(1, MPI_CONFIG_PAGETYPE_IOC, 0x03,
+ "*l*l*b*b*b*b");
+}
+
+static
+size_t mptsas_config_ioc_2(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(2, MPI_CONFIG_PAGETYPE_IOC, 0x04,
+ "*l*b*b*b*b");
+}
+
+static
+size_t mptsas_config_ioc_3(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(3, MPI_CONFIG_PAGETYPE_IOC, 0x00,
+ "*b*b*w");
+}
+
+static
+size_t mptsas_config_ioc_4(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(4, MPI_CONFIG_PAGETYPE_IOC, 0x00,
+ "*b*b*w");
+}
+
+static
+size_t mptsas_config_ioc_5(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(5, MPI_CONFIG_PAGETYPE_IOC, 0x00,
+ "*l*b*b*w");
+}
+
+static
+size_t mptsas_config_ioc_6(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK(6, MPI_CONFIG_PAGETYPE_IOC, 0x01,
+ "*l*b*b*b*b*b*b*b*b*b*b*w*l*l*l*l*b*b*w"
+ "*w*w*w*w*l*l*l");
+}
+
+/* SAS I/O unit pages (extended) */
+
+#define MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE 16
+
+#define MPI_SAS_IOUNIT0_RATE_FAILED_SPEED_NEGOTIATION 0x02
+#define MPI_SAS_IOUNIT0_RATE_1_5 0x08
+#define MPI_SAS_IOUNIT0_RATE_3_0 0x09
+
+#define MPI_SAS_DEVICE_INFO_NO_DEVICE 0x00000000
+#define MPI_SAS_DEVICE_INFO_END_DEVICE 0x00000001
+#define MPI_SAS_DEVICE_INFO_SSP_TARGET 0x00000400
+
+#define MPI_SAS_DEVICE0_ASTATUS_NO_ERRORS 0x00
+
+#define MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT 0x0001
+#define MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED 0x0002
+#define MPI_SAS_DEVICE0_FLAGS_MAPPING_PERSISTENT 0x0004
+
+
+
+static SCSIDevice *mptsas_phy_get_device(MPTSASState *s, int i,
+ int *phy_handle, int *dev_handle)
+{
+ SCSIDevice *d = scsi_device_find(&s->bus, 0, i, 0);
+
+ if (phy_handle) {
+ *phy_handle = i + 1;
+ }
+ if (dev_handle) {
+ *dev_handle = d ? i + 1 + MPTSAS_NUM_PORTS : 0;
+ }
+ return d;
+}
+
+static
+size_t mptsas_config_sas_io_unit_0(MPTSASState *s, uint8_t **data, int address)
+{
+ size_t size = MPTSAS_CONFIG_PACK_EXT(0, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x04,
+ "*w*wb*b*w"
+ repl(MPTSAS_NUM_PORTS, "*s16"),
+ MPTSAS_NUM_PORTS);
+
+ if (data) {
+ size_t ofs = size - MPTSAS_NUM_PORTS * MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE;
+ int i;
+
+ for (i = 0; i < MPTSAS_NUM_PORTS; i++) {
+ int phy_handle, dev_handle;
+ SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
+
+ fill(*data + ofs, MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE,
+ "bbbblwwl", i, 0, 0,
+ (dev
+ ? MPI_SAS_IOUNIT0_RATE_3_0
+ : MPI_SAS_IOUNIT0_RATE_FAILED_SPEED_NEGOTIATION),
+ (dev
+ ? MPI_SAS_DEVICE_INFO_END_DEVICE | MPI_SAS_DEVICE_INFO_SSP_TARGET
+ : MPI_SAS_DEVICE_INFO_NO_DEVICE),
+ dev_handle,
+ dev_handle,
+ 0);
+ ofs += MPTSAS_CONFIG_SAS_IO_UNIT_0_SIZE;
+ }
+ assert(ofs == size);
+ }
+ return size;
+}
+
+#define MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE 12
+
+static
+size_t mptsas_config_sas_io_unit_1(MPTSASState *s, uint8_t **data, int address)
+{
+ size_t size = MPTSAS_CONFIG_PACK_EXT(1, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x07,
+ "*w*w*w*wb*b*b*b"
+ repl(MPTSAS_NUM_PORTS, "*s12"),
+ MPTSAS_NUM_PORTS);
+
+ if (data) {
+ size_t ofs = size - MPTSAS_NUM_PORTS * MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE;
+ int i;
+
+ for (i = 0; i < MPTSAS_NUM_PORTS; i++) {
+ SCSIDevice *dev = mptsas_phy_get_device(s, i, NULL, NULL);
+ fill(*data + ofs, MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE,
+ "bbbblww", i, 0, 0,
+ (MPI_SAS_IOUNIT0_RATE_3_0 << 4) | MPI_SAS_IOUNIT0_RATE_1_5,
+ (dev
+ ? MPI_SAS_DEVICE_INFO_END_DEVICE | MPI_SAS_DEVICE_INFO_SSP_TARGET
+ : MPI_SAS_DEVICE_INFO_NO_DEVICE),
+ 0, 0);
+ ofs += MPTSAS_CONFIG_SAS_IO_UNIT_1_SIZE;
+ }
+ assert(ofs == size);
+ }
+ return size;
+}
+
+static
+size_t mptsas_config_sas_io_unit_2(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK_EXT(2, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x06,
+ "*b*b*w*w*w*b*b*w");
+}
+
+static
+size_t mptsas_config_sas_io_unit_3(MPTSASState *s, uint8_t **data, int address)
+{
+ return MPTSAS_CONFIG_PACK_EXT(3, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT, 0x06,
+ "*l*l*l*l*l*l*l*l*l");
+}
+
+/* SAS PHY pages (extended) */
+
+static int mptsas_phy_addr_get(MPTSASState *s, int address)
+{
+ int i;
+ if ((address >> MPI_SAS_PHY_PGAD_FORM_SHIFT) == 0) {
+ i = address & 255;
+ } else if ((address >> MPI_SAS_PHY_PGAD_FORM_SHIFT) == 1) {
+ i = address & 65535;
+ } else {
+ return -EINVAL;
+ }
+
+ if (i >= MPTSAS_NUM_PORTS) {
+ return -EINVAL;
+ }
+
+ return i;
+}
+
+static
+size_t mptsas_config_phy_0(MPTSASState *s, uint8_t **data, int address)
+{
+ int phy_handle = -1;
+ int dev_handle = -1;
+ int i = mptsas_phy_addr_get(s, address);
+ SCSIDevice *dev;
+
+ if (i < 0) {
+ trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 0);
+ return i;
+ }
+
+ dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
+ trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 0);
+
+ return MPTSAS_CONFIG_PACK_EXT(0, MPI_CONFIG_EXTPAGETYPE_SAS_PHY, 0x01,
+ "w*wqwb*blbb*b*b*l",
+ dev_handle, s->sas_addr, dev_handle, i,
+ (dev
+ ? MPI_SAS_DEVICE_INFO_END_DEVICE /* | MPI_SAS_DEVICE_INFO_SSP_TARGET?? */
+ : MPI_SAS_DEVICE_INFO_NO_DEVICE),
+ (MPI_SAS_IOUNIT0_RATE_3_0 << 4) | MPI_SAS_IOUNIT0_RATE_1_5,
+ (MPI_SAS_IOUNIT0_RATE_3_0 << 4) | MPI_SAS_IOUNIT0_RATE_1_5);
+}
+
+static
+size_t mptsas_config_phy_1(MPTSASState *s, uint8_t **data, int address)
+{
+ int phy_handle = -1;
+ int dev_handle = -1;
+ int i = mptsas_phy_addr_get(s, address);
+
+ if (i < 0) {
+ trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 1);
+ return i;
+ }
+
+ (void) mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
+ trace_mptsas_config_sas_phy(s, address, i, phy_handle, dev_handle, 1);
+
+ return MPTSAS_CONFIG_PACK_EXT(1, MPI_CONFIG_EXTPAGETYPE_SAS_PHY, 0x01,
+ "*l*l*l*l*l");
+}
+
+/* SAS device pages (extended) */
+
+static int mptsas_device_addr_get(MPTSASState *s, int address)
+{
+ uint32_t handle, i;
+ uint32_t form = address >> MPI_SAS_PHY_PGAD_FORM_SHIFT;
+ if (form == MPI_SAS_DEVICE_PGAD_FORM_GET_NEXT_HANDLE) {
+ handle = address & MPI_SAS_DEVICE_PGAD_GNH_HANDLE_MASK;
+ do {
+ if (handle == 65535) {
+ handle = MPTSAS_NUM_PORTS + 1;
+ } else {
+ ++handle;
+ }
+ i = handle - 1 - MPTSAS_NUM_PORTS;
+ } while (i < MPTSAS_NUM_PORTS && !scsi_device_find(&s->bus, 0, i, 0));
+
+ } else if (form == MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID) {
+ if (address & MPI_SAS_DEVICE_PGAD_BT_BUS_MASK) {
+ return -EINVAL;
+ }
+ i = address & MPI_SAS_DEVICE_PGAD_BT_TID_MASK;
+
+ } else if (form == MPI_SAS_DEVICE_PGAD_FORM_HANDLE) {
+ handle = address & MPI_SAS_DEVICE_PGAD_H_HANDLE_MASK;
+ i = handle - 1 - MPTSAS_NUM_PORTS;
+
+ } else {
+ return -EINVAL;
+ }
+
+ if (i >= MPTSAS_NUM_PORTS) {
+ return -EINVAL;
+ }
+
+ return i;
+}
+
+static
+size_t mptsas_config_sas_device_0(MPTSASState *s, uint8_t **data, int address)
+{
+ int phy_handle = -1;
+ int dev_handle = -1;
+ int i = mptsas_device_addr_get(s, address);
+ SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
+
+ trace_mptsas_config_sas_device(s, address, i, phy_handle, dev_handle, 0);
+ if (!dev) {
+ return -ENOENT;
+ }
+
+ return MPTSAS_CONFIG_PACK_EXT(0, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE, 0x05,
+ "*w*wqwbbwbblwb*b",
+ dev->wwn, phy_handle, i,
+ MPI_SAS_DEVICE0_ASTATUS_NO_ERRORS,
+ dev_handle, i, 0,
+ MPI_SAS_DEVICE_INFO_END_DEVICE | MPI_SAS_DEVICE_INFO_SSP_TARGET,
+ (MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT |
+ MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED |
+ MPI_SAS_DEVICE0_FLAGS_MAPPING_PERSISTENT), i);
+}
+
+static
+size_t mptsas_config_sas_device_1(MPTSASState *s, uint8_t **data, int address)
+{
+ int phy_handle = -1;
+ int dev_handle = -1;
+ int i = mptsas_device_addr_get(s, address);
+ SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
+
+ trace_mptsas_config_sas_device(s, address, i, phy_handle, dev_handle, 1);
+ if (!dev) {
+ return -ENOENT;
+ }
+
+ return MPTSAS_CONFIG_PACK_EXT(1, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE, 0x00,
+ "*lq*lwbb*s20",
+ dev->wwn, dev_handle, i, 0);
+}
+
+static
+size_t mptsas_config_sas_device_2(MPTSASState *s, uint8_t **data, int address)
+{
+ int phy_handle = -1;
+ int dev_handle = -1;
+ int i = mptsas_device_addr_get(s, address);
+ SCSIDevice *dev = mptsas_phy_get_device(s, i, &phy_handle, &dev_handle);
+
+ trace_mptsas_config_sas_device(s, address, i, phy_handle, dev_handle, 2);
+ if (!dev) {
+ return -ENOENT;
+ }
+
+ return MPTSAS_CONFIG_PACK_EXT(2, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE, 0x01,
+ "ql", dev->wwn, 0);
+}
+
+typedef struct MPTSASConfigPage {
+ uint8_t number;
+ uint8_t type;
+ size_t (*mpt_config_build)(MPTSASState *s, uint8_t **data, int address);
+} MPTSASConfigPage;
+
+static const MPTSASConfigPage mptsas_config_pages[] = {
+ {
+ 0, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_0,
+ }, {
+ 1, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_1,
+ }, {
+ 2, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_2,
+ }, {
+ 3, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_3,
+ }, {
+ 4, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_4,
+ }, {
+ 5, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_5,
+ }, {
+ 6, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_6,
+ }, {
+ 7, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_7,
+ }, {
+ 8, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_8,
+ }, {
+ 9, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_9,
+ }, {
+ 10, MPI_CONFIG_PAGETYPE_MANUFACTURING,
+ mptsas_config_manufacturing_10,
+ }, {
+ 0, MPI_CONFIG_PAGETYPE_IO_UNIT,
+ mptsas_config_io_unit_0,
+ }, {
+ 1, MPI_CONFIG_PAGETYPE_IO_UNIT,
+ mptsas_config_io_unit_1,
+ }, {
+ 2, MPI_CONFIG_PAGETYPE_IO_UNIT,
+ mptsas_config_io_unit_2,
+ }, {
+ 3, MPI_CONFIG_PAGETYPE_IO_UNIT,
+ mptsas_config_io_unit_3,
+ }, {
+ 4, MPI_CONFIG_PAGETYPE_IO_UNIT,
+ mptsas_config_io_unit_4,
+ }, {
+ 0, MPI_CONFIG_PAGETYPE_IOC,
+ mptsas_config_ioc_0,
+ }, {
+ 1, MPI_CONFIG_PAGETYPE_IOC,
+ mptsas_config_ioc_1,
+ }, {
+ 2, MPI_CONFIG_PAGETYPE_IOC,
+ mptsas_config_ioc_2,
+ }, {
+ 3, MPI_CONFIG_PAGETYPE_IOC,
+ mptsas_config_ioc_3,
+ }, {
+ 4, MPI_CONFIG_PAGETYPE_IOC,
+ mptsas_config_ioc_4,
+ }, {
+ 5, MPI_CONFIG_PAGETYPE_IOC,
+ mptsas_config_ioc_5,
+ }, {
+ 6, MPI_CONFIG_PAGETYPE_IOC,
+ mptsas_config_ioc_6,
+ }, {
+ 0, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
+ mptsas_config_sas_io_unit_0,
+ }, {
+ 1, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
+ mptsas_config_sas_io_unit_1,
+ }, {
+ 2, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
+ mptsas_config_sas_io_unit_2,
+ }, {
+ 3, MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT,
+ mptsas_config_sas_io_unit_3,
+ }, {
+ 0, MPI_CONFIG_EXTPAGETYPE_SAS_PHY,
+ mptsas_config_phy_0,
+ }, {
+ 1, MPI_CONFIG_EXTPAGETYPE_SAS_PHY,
+ mptsas_config_phy_1,
+ }, {
+ 0, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE,
+ mptsas_config_sas_device_0,
+ }, {
+ 1, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE,
+ mptsas_config_sas_device_1,
+ }, {
+ 2, MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE,
+ mptsas_config_sas_device_2,
+ }
+};
+
+static const MPTSASConfigPage *mptsas_find_config_page(int type, int number)
+{
+ const MPTSASConfigPage *page;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mptsas_config_pages); i++) {
+ page = &mptsas_config_pages[i];
+ if (page->type == type && page->number == number) {
+ return page;
+ }
+ }
+
+ return NULL;
+}
+
+void mptsas_process_config(MPTSASState *s, MPIMsgConfig *req)
+{
+ PCIDevice *pci = PCI_DEVICE(s);
+
+ MPIMsgConfigReply reply;
+ const MPTSASConfigPage *page;
+ size_t length;
+ uint8_t type;
+ uint8_t *data = NULL;
+ uint32_t flags_and_length;
+ uint32_t dmalen;
+ uint64_t pa;
+
+ mptsas_fix_config_endianness(req);
+
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
+
+ /* Copy common bits from the request into the reply. */
+ memset(&reply, 0, sizeof(reply));
+ reply.Action = req->Action;
+ reply.Function = req->Function;
+ reply.MsgContext = req->MsgContext;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.PageType = req->PageType;
+ reply.PageNumber = req->PageNumber;
+ reply.PageLength = req->PageLength;
+ reply.PageVersion = req->PageVersion;
+
+ type = req->PageType & MPI_CONFIG_PAGETYPE_MASK;
+ if (type == MPI_CONFIG_PAGETYPE_EXTENDED) {
+ type = req->ExtPageType;
+ if (type <= MPI_CONFIG_PAGETYPE_MASK) {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_TYPE;
+ goto out;
+ }
+
+ reply.ExtPageType = req->ExtPageType;
+ }
+
+ page = mptsas_find_config_page(type, req->PageNumber);
+
+ switch(req->Action) {
+ case MPI_CONFIG_ACTION_PAGE_DEFAULT:
+ case MPI_CONFIG_ACTION_PAGE_HEADER:
+ case MPI_CONFIG_ACTION_PAGE_READ_NVRAM:
+ case MPI_CONFIG_ACTION_PAGE_READ_CURRENT:
+ case MPI_CONFIG_ACTION_PAGE_READ_DEFAULT:
+ case MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT:
+ case MPI_CONFIG_ACTION_PAGE_WRITE_NVRAM:
+ break;
+
+ default:
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_ACTION;
+ goto out;
+ }
+
+ if (!page) {
+ page = mptsas_find_config_page(type, 1);
+ if (page) {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
+ } else {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_TYPE;
+ }
+ goto out;
+ }
+
+ if (req->Action == MPI_CONFIG_ACTION_PAGE_DEFAULT ||
+ req->Action == MPI_CONFIG_ACTION_PAGE_HEADER) {
+ length = page->mpt_config_build(s, NULL, req->PageAddress);
+ if ((ssize_t)length < 0) {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
+ goto out;
+ } else {
+ goto done;
+ }
+ }
+
+ if (req->Action == MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT ||
+ req->Action == MPI_CONFIG_ACTION_PAGE_WRITE_NVRAM) {
+ length = page->mpt_config_build(s, NULL, req->PageAddress);
+ if ((ssize_t)length < 0) {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
+ } else {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_CANT_COMMIT;
+ }
+ goto out;
+ }
+
+ flags_and_length = req->PageBufferSGE.FlagsLength;
+ dmalen = flags_and_length & MPI_SGE_LENGTH_MASK;
+ if (dmalen == 0) {
+ length = page->mpt_config_build(s, NULL, req->PageAddress);
+ if ((ssize_t)length < 0) {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
+ goto out;
+ } else {
+ goto done;
+ }
+ }
+
+ if (flags_and_length & MPI_SGE_FLAGS_64_BIT_ADDRESSING) {
+ pa = req->PageBufferSGE.u.Address64;
+ } else {
+ pa = req->PageBufferSGE.u.Address32;
+ }
+
+ /* Only read actions left. */
+ length = page->mpt_config_build(s, &data, req->PageAddress);
+ if ((ssize_t)length < 0) {
+ reply.IOCStatus = MPI_IOCSTATUS_CONFIG_INVALID_PAGE;
+ goto out;
+ } else {
+ assert(data[2] == page->number);
+ pci_dma_write(pci, pa, data, MIN(length, dmalen));
+ goto done;
+ }
+
+ abort();
+
+done:
+ if (type > MPI_CONFIG_PAGETYPE_MASK) {
+ reply.ExtPageLength = length / 4;
+ reply.ExtPageType = req->ExtPageType;
+ } else {
+ reply.PageLength = length / 4;
+ }
+
+out:
+ mptsas_fix_config_reply_endianness(&reply);
+ mptsas_reply(s, (MPIDefaultReply *)&reply);
+ g_free(data);
+}
diff --git a/hw/scsi/mptendian.c b/hw/scsi/mptendian.c
new file mode 100644
index 00000000..0d5abb4b
--- /dev/null
+++ b/hw/scsi/mptendian.c
@@ -0,0 +1,205 @@
+/*
+ * QEMU LSI SAS1068 Host Bus Adapter emulation
+ * Endianness conversion for MPI data structures
+ *
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Authors: Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "sysemu/dma.h"
+#include "hw/pci/msi.h"
+#include "qemu/iov.h"
+#include "hw/scsi/scsi.h"
+#include "scsi/constants.h"
+#include "trace.h"
+
+#include "mptsas.h"
+#include "mpi.h"
+
+static void mptsas_fix_sgentry_endianness(MPISGEntry *sge)
+{
+ sge->FlagsLength = le32_to_cpu(sge->FlagsLength);
+ if (sge->FlagsLength & MPI_SGE_FLAGS_64_BIT_ADDRESSING) {
+ sge->u.Address64 = le64_to_cpu(sge->u.Address64);
+ } else {
+ sge->u.Address32 = le32_to_cpu(sge->u.Address32);
+ }
+}
+
+static void mptsas_fix_sgentry_endianness_reply(MPISGEntry *sge)
+{
+ if (sge->FlagsLength & MPI_SGE_FLAGS_64_BIT_ADDRESSING) {
+ sge->u.Address64 = cpu_to_le64(sge->u.Address64);
+ } else {
+ sge->u.Address32 = cpu_to_le32(sge->u.Address32);
+ }
+ sge->FlagsLength = cpu_to_le32(sge->FlagsLength);
+}
+
+void mptsas_fix_scsi_io_endianness(MPIMsgSCSIIORequest *req)
+{
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+ req->Control = le32_to_cpu(req->Control);
+ req->DataLength = le32_to_cpu(req->DataLength);
+ req->SenseBufferLowAddr = le32_to_cpu(req->SenseBufferLowAddr);
+}
+
+void mptsas_fix_scsi_io_reply_endianness(MPIMsgSCSIIOReply *reply)
+{
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+ reply->TransferCount = cpu_to_le32(reply->TransferCount);
+ reply->SenseCount = cpu_to_le32(reply->SenseCount);
+ reply->ResponseInfo = cpu_to_le32(reply->ResponseInfo);
+ reply->TaskTag = cpu_to_le16(reply->TaskTag);
+}
+
+void mptsas_fix_scsi_task_mgmt_endianness(MPIMsgSCSITaskMgmt *req)
+{
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+ req->TaskMsgContext = le32_to_cpu(req->TaskMsgContext);
+}
+
+void mptsas_fix_scsi_task_mgmt_reply_endianness(MPIMsgSCSITaskMgmtReply *reply)
+{
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+ reply->TerminationCount = cpu_to_le32(reply->TerminationCount);
+}
+
+void mptsas_fix_ioc_init_endianness(MPIMsgIOCInit *req)
+{
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+ req->ReplyFrameSize = le16_to_cpu(req->ReplyFrameSize);
+ req->HostMfaHighAddr = le32_to_cpu(req->HostMfaHighAddr);
+ req->SenseBufferHighAddr = le32_to_cpu(req->SenseBufferHighAddr);
+ req->ReplyFifoHostSignalingAddr =
+ le32_to_cpu(req->ReplyFifoHostSignalingAddr);
+ mptsas_fix_sgentry_endianness(&req->HostPageBufferSGE);
+ req->MsgVersion = le16_to_cpu(req->MsgVersion);
+ req->HeaderVersion = le16_to_cpu(req->HeaderVersion);
+}
+
+void mptsas_fix_ioc_init_reply_endianness(MPIMsgIOCInitReply *reply)
+{
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+}
+
+void mptsas_fix_ioc_facts_endianness(MPIMsgIOCFacts *req)
+{
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+}
+
+void mptsas_fix_ioc_facts_reply_endianness(MPIMsgIOCFactsReply *reply)
+{
+ reply->MsgVersion = cpu_to_le16(reply->MsgVersion);
+ reply->HeaderVersion = cpu_to_le16(reply->HeaderVersion);
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCExceptions = cpu_to_le16(reply->IOCExceptions);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+ reply->ReplyQueueDepth = cpu_to_le16(reply->ReplyQueueDepth);
+ reply->RequestFrameSize = cpu_to_le16(reply->RequestFrameSize);
+ reply->ProductID = cpu_to_le16(reply->ProductID);
+ reply->CurrentHostMfaHighAddr = cpu_to_le32(reply->CurrentHostMfaHighAddr);
+ reply->GlobalCredits = cpu_to_le16(reply->GlobalCredits);
+ reply->CurrentSenseBufferHighAddr =
+ cpu_to_le32(reply->CurrentSenseBufferHighAddr);
+ reply->CurReplyFrameSize = cpu_to_le16(reply->CurReplyFrameSize);
+ reply->FWImageSize = cpu_to_le32(reply->FWImageSize);
+ reply->IOCCapabilities = cpu_to_le32(reply->IOCCapabilities);
+ reply->HighPriorityQueueDepth = cpu_to_le16(reply->HighPriorityQueueDepth);
+ mptsas_fix_sgentry_endianness_reply(&reply->HostPageBufferSGE);
+ reply->ReplyFifoHostSignalingAddr =
+ cpu_to_le32(reply->ReplyFifoHostSignalingAddr);
+}
+
+void mptsas_fix_config_endianness(MPIMsgConfig *req)
+{
+ req->ExtPageLength = le16_to_cpu(req->ExtPageLength);
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+ req->PageAddress = le32_to_cpu(req->PageAddress);
+ mptsas_fix_sgentry_endianness(&req->PageBufferSGE);
+}
+
+void mptsas_fix_config_reply_endianness(MPIMsgConfigReply *reply)
+{
+ reply->ExtPageLength = cpu_to_le16(reply->ExtPageLength);
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+}
+
+void mptsas_fix_port_facts_endianness(MPIMsgPortFacts *req)
+{
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+}
+
+void mptsas_fix_port_facts_reply_endianness(MPIMsgPortFactsReply *reply)
+{
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+ reply->MaxDevices = cpu_to_le16(reply->MaxDevices);
+ reply->PortSCSIID = cpu_to_le16(reply->PortSCSIID);
+ reply->ProtocolFlags = cpu_to_le16(reply->ProtocolFlags);
+ reply->MaxPostedCmdBuffers = cpu_to_le16(reply->MaxPostedCmdBuffers);
+ reply->MaxPersistentIDs = cpu_to_le16(reply->MaxPersistentIDs);
+ reply->MaxLanBuckets = cpu_to_le16(reply->MaxLanBuckets);
+}
+
+void mptsas_fix_port_enable_endianness(MPIMsgPortEnable *req)
+{
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+}
+
+void mptsas_fix_port_enable_reply_endianness(MPIMsgPortEnableReply *reply)
+{
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+}
+
+void mptsas_fix_event_notification_endianness(MPIMsgEventNotify *req)
+{
+ req->MsgContext = le32_to_cpu(req->MsgContext);
+}
+
+void mptsas_fix_event_notification_reply_endianness(MPIMsgEventNotifyReply *reply)
+{
+ int length = reply->EventDataLength;
+ int i;
+
+ reply->EventDataLength = cpu_to_le16(reply->EventDataLength);
+ reply->MsgContext = cpu_to_le32(reply->MsgContext);
+ reply->IOCStatus = cpu_to_le16(reply->IOCStatus);
+ reply->IOCLogInfo = cpu_to_le32(reply->IOCLogInfo);
+ reply->Event = cpu_to_le32(reply->Event);
+ reply->EventContext = cpu_to_le32(reply->EventContext);
+
+ /* Really depends on the event kind. This will do for now. */
+ for (i = 0; i < length; i++) {
+ reply->Data[i] = cpu_to_le32(reply->Data[i]);
+ }
+}
+
diff --git a/hw/scsi/mptsas.c b/hw/scsi/mptsas.c
new file mode 100644
index 00000000..c485da79
--- /dev/null
+++ b/hw/scsi/mptsas.c
@@ -0,0 +1,1455 @@
+/*
+ * QEMU LSI SAS1068 Host Bus Adapter emulation
+ * Based on the QEMU Megaraid emulator
+ *
+ * Copyright (c) 2009-2012 Hannes Reinecke, SUSE Labs
+ * Copyright (c) 2012 Verizon, Inc.
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * Authors: Don Slutz, Paolo Bonzini
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "sysemu/dma.h"
+#include "hw/pci/msi.h"
+#include "qemu/iov.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "hw/scsi/scsi.h"
+#include "scsi/constants.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "mptsas.h"
+#include "migration/qemu-file-types.h"
+#include "migration/vmstate.h"
+#include "mpi.h"
+
+#define NAA_LOCALLY_ASSIGNED_ID 0x3ULL
+#define IEEE_COMPANY_LOCALLY_ASSIGNED 0x525400
+
+#define MPTSAS1068_PRODUCT_ID \
+ (MPI_FW_HEADER_PID_FAMILY_1068_SAS | \
+ MPI_FW_HEADER_PID_PROD_INITIATOR_SCSI | \
+ MPI_FW_HEADER_PID_TYPE_SAS)
+
+struct MPTSASRequest {
+ MPIMsgSCSIIORequest scsi_io;
+ SCSIRequest *sreq;
+ QEMUSGList qsg;
+ MPTSASState *dev;
+
+ QTAILQ_ENTRY(MPTSASRequest) next;
+};
+
+static void mptsas_update_interrupt(MPTSASState *s)
+{
+ PCIDevice *pci = (PCIDevice *) s;
+ uint32_t state = s->intr_status & ~(s->intr_mask | MPI_HIS_IOP_DOORBELL_STATUS);
+
+ if (msi_enabled(pci)) {
+ if (state) {
+ trace_mptsas_irq_msi(s);
+ msi_notify(pci, 0);
+ }
+ }
+
+ trace_mptsas_irq_intx(s, !!state);
+ pci_set_irq(pci, !!state);
+}
+
+static void mptsas_set_fault(MPTSASState *s, uint32_t code)
+{
+ if ((s->state & MPI_IOC_STATE_FAULT) == 0) {
+ s->state = MPI_IOC_STATE_FAULT | code;
+ }
+}
+
+#define MPTSAS_FIFO_INVALID(s, name) \
+ ((s)->name##_head > ARRAY_SIZE((s)->name) || \
+ (s)->name##_tail > ARRAY_SIZE((s)->name))
+
+#define MPTSAS_FIFO_EMPTY(s, name) \
+ ((s)->name##_head == (s)->name##_tail)
+
+#define MPTSAS_FIFO_FULL(s, name) \
+ ((s)->name##_head == ((s)->name##_tail + 1) % ARRAY_SIZE((s)->name))
+
+#define MPTSAS_FIFO_GET(s, name) ({ \
+ uint32_t _val = (s)->name[(s)->name##_head++]; \
+ (s)->name##_head %= ARRAY_SIZE((s)->name); \
+ _val; \
+})
+
+#define MPTSAS_FIFO_PUT(s, name, val) do { \
+ (s)->name[(s)->name##_tail++] = (val); \
+ (s)->name##_tail %= ARRAY_SIZE((s)->name); \
+} while(0)
+
+static void mptsas_post_reply(MPTSASState *s, MPIDefaultReply *reply)
+{
+ PCIDevice *pci = (PCIDevice *) s;
+ uint32_t addr_lo;
+
+ if (MPTSAS_FIFO_EMPTY(s, reply_free) || MPTSAS_FIFO_FULL(s, reply_post)) {
+ mptsas_set_fault(s, MPI_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ return;
+ }
+
+ addr_lo = MPTSAS_FIFO_GET(s, reply_free);
+
+ pci_dma_write(pci, addr_lo | s->host_mfa_high_addr, reply,
+ MIN(s->reply_frame_size, 4 * reply->MsgLength));
+
+ MPTSAS_FIFO_PUT(s, reply_post, MPI_ADDRESS_REPLY_A_BIT | (addr_lo >> 1));
+
+ s->intr_status |= MPI_HIS_REPLY_MESSAGE_INTERRUPT;
+ if (s->doorbell_state == DOORBELL_WRITE) {
+ s->doorbell_state = DOORBELL_NONE;
+ s->intr_status |= MPI_HIS_DOORBELL_INTERRUPT;
+ }
+ mptsas_update_interrupt(s);
+}
+
+void mptsas_reply(MPTSASState *s, MPIDefaultReply *reply)
+{
+ if (s->doorbell_state == DOORBELL_WRITE) {
+ /* The reply is sent out in 16 bit chunks, while the size
+ * in the reply is in 32 bit units.
+ */
+ s->doorbell_state = DOORBELL_READ;
+ s->doorbell_reply_idx = 0;
+ s->doorbell_reply_size = reply->MsgLength * 2;
+ memcpy(s->doorbell_reply, reply, s->doorbell_reply_size * 2);
+ s->intr_status |= MPI_HIS_DOORBELL_INTERRUPT;
+ mptsas_update_interrupt(s);
+ } else {
+ mptsas_post_reply(s, reply);
+ }
+}
+
+static void mptsas_turbo_reply(MPTSASState *s, uint32_t msgctx)
+{
+ if (MPTSAS_FIFO_FULL(s, reply_post)) {
+ mptsas_set_fault(s, MPI_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ return;
+ }
+
+ /* The reply is just the message context ID (bit 31 = clear). */
+ MPTSAS_FIFO_PUT(s, reply_post, msgctx);
+
+ s->intr_status |= MPI_HIS_REPLY_MESSAGE_INTERRUPT;
+ mptsas_update_interrupt(s);
+}
+
+#define MPTSAS_MAX_REQUEST_SIZE 52
+
+static const int mpi_request_sizes[] = {
+ [MPI_FUNCTION_SCSI_IO_REQUEST] = sizeof(MPIMsgSCSIIORequest),
+ [MPI_FUNCTION_SCSI_TASK_MGMT] = sizeof(MPIMsgSCSITaskMgmt),
+ [MPI_FUNCTION_IOC_INIT] = sizeof(MPIMsgIOCInit),
+ [MPI_FUNCTION_IOC_FACTS] = sizeof(MPIMsgIOCFacts),
+ [MPI_FUNCTION_CONFIG] = sizeof(MPIMsgConfig),
+ [MPI_FUNCTION_PORT_FACTS] = sizeof(MPIMsgPortFacts),
+ [MPI_FUNCTION_PORT_ENABLE] = sizeof(MPIMsgPortEnable),
+ [MPI_FUNCTION_EVENT_NOTIFICATION] = sizeof(MPIMsgEventNotify),
+};
+
+static dma_addr_t mptsas_ld_sg_base(MPTSASState *s, uint32_t flags_and_length,
+ dma_addr_t *sgaddr)
+{
+ const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
+ PCIDevice *pci = (PCIDevice *) s;
+ dma_addr_t addr;
+
+ if (flags_and_length & MPI_SGE_FLAGS_64_BIT_ADDRESSING) {
+ uint64_t addr64;
+
+ ldq_le_pci_dma(pci, *sgaddr + 4, &addr64, attrs);
+ addr = addr64;
+ *sgaddr += 12;
+ } else {
+ uint32_t addr32;
+
+ ldl_le_pci_dma(pci, *sgaddr + 4, &addr32, attrs);
+ addr = addr32;
+ *sgaddr += 8;
+ }
+ return addr;
+}
+
+static int mptsas_build_sgl(MPTSASState *s, MPTSASRequest *req, hwaddr addr)
+{
+ PCIDevice *pci = (PCIDevice *) s;
+ hwaddr next_chain_addr;
+ uint32_t left;
+ hwaddr sgaddr;
+ uint32_t chain_offset;
+
+ chain_offset = req->scsi_io.ChainOffset;
+ next_chain_addr = addr + chain_offset * sizeof(uint32_t);
+ sgaddr = addr + sizeof(MPIMsgSCSIIORequest);
+ pci_dma_sglist_init(&req->qsg, pci, 4);
+ left = req->scsi_io.DataLength;
+
+ for(;;) {
+ dma_addr_t addr, len;
+ uint32_t flags_and_length;
+
+ ldl_le_pci_dma(pci, sgaddr, &flags_and_length, MEMTXATTRS_UNSPECIFIED);
+ len = flags_and_length & MPI_SGE_LENGTH_MASK;
+ if ((flags_and_length & MPI_SGE_FLAGS_ELEMENT_TYPE_MASK)
+ != MPI_SGE_FLAGS_SIMPLE_ELEMENT ||
+ (!len &&
+ !(flags_and_length & MPI_SGE_FLAGS_END_OF_LIST) &&
+ !(flags_and_length & MPI_SGE_FLAGS_END_OF_BUFFER))) {
+ return MPI_IOCSTATUS_INVALID_SGL;
+ }
+
+ len = MIN(len, left);
+ if (!len) {
+ /* We reached the desired transfer length, ignore extra
+ * elements of the s/g list.
+ */
+ break;
+ }
+
+ addr = mptsas_ld_sg_base(s, flags_and_length, &sgaddr);
+ qemu_sglist_add(&req->qsg, addr, len);
+ left -= len;
+
+ if (flags_and_length & MPI_SGE_FLAGS_END_OF_LIST) {
+ break;
+ }
+
+ if (flags_and_length & MPI_SGE_FLAGS_LAST_ELEMENT) {
+ if (!chain_offset) {
+ break;
+ }
+
+ ldl_le_pci_dma(pci, next_chain_addr, &flags_and_length,
+ MEMTXATTRS_UNSPECIFIED);
+ if ((flags_and_length & MPI_SGE_FLAGS_ELEMENT_TYPE_MASK)
+ != MPI_SGE_FLAGS_CHAIN_ELEMENT) {
+ return MPI_IOCSTATUS_INVALID_SGL;
+ }
+
+ sgaddr = mptsas_ld_sg_base(s, flags_and_length, &next_chain_addr);
+ chain_offset =
+ (flags_and_length & MPI_SGE_CHAIN_OFFSET_MASK) >> MPI_SGE_CHAIN_OFFSET_SHIFT;
+ next_chain_addr = sgaddr + chain_offset * sizeof(uint32_t);
+ }
+ }
+ return 0;
+}
+
+static void mptsas_free_request(MPTSASRequest *req)
+{
+ if (req->sreq != NULL) {
+ req->sreq->hba_private = NULL;
+ scsi_req_unref(req->sreq);
+ req->sreq = NULL;
+ }
+ qemu_sglist_destroy(&req->qsg);
+ g_free(req);
+}
+
+static int mptsas_scsi_device_find(MPTSASState *s, int bus, int target,
+ uint8_t *lun, SCSIDevice **sdev)
+{
+ if (bus != 0) {
+ return MPI_IOCSTATUS_SCSI_INVALID_BUS;
+ }
+
+ if (target >= s->max_devices) {
+ return MPI_IOCSTATUS_SCSI_INVALID_TARGETID;
+ }
+
+ *sdev = scsi_device_find(&s->bus, bus, target, lun[1]);
+ if (!*sdev) {
+ return MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE;
+ }
+
+ return 0;
+}
+
+static int mptsas_process_scsi_io_request(MPTSASState *s,
+ MPIMsgSCSIIORequest *scsi_io,
+ hwaddr addr)
+{
+ MPTSASRequest *req;
+ MPIMsgSCSIIOReply reply;
+ SCSIDevice *sdev;
+ int status;
+
+ mptsas_fix_scsi_io_endianness(scsi_io);
+
+ trace_mptsas_process_scsi_io_request(s, scsi_io->Bus, scsi_io->TargetID,
+ scsi_io->LUN[1], scsi_io->DataLength);
+
+ status = mptsas_scsi_device_find(s, scsi_io->Bus, scsi_io->TargetID,
+ scsi_io->LUN, &sdev);
+ if (status) {
+ goto bad;
+ }
+
+ req = g_new0(MPTSASRequest, 1);
+ req->scsi_io = *scsi_io;
+ req->dev = s;
+
+ status = mptsas_build_sgl(s, req, addr);
+ if (status) {
+ goto free_bad;
+ }
+
+ if (req->qsg.size < scsi_io->DataLength) {
+ trace_mptsas_sgl_overflow(s, scsi_io->MsgContext, scsi_io->DataLength,
+ req->qsg.size);
+ status = MPI_IOCSTATUS_INVALID_SGL;
+ goto free_bad;
+ }
+
+ req->sreq = scsi_req_new(sdev, scsi_io->MsgContext,
+ scsi_io->LUN[1], scsi_io->CDB,
+ scsi_io->CDBLength, req);
+
+ if (req->sreq->cmd.xfer > scsi_io->DataLength) {
+ goto overrun;
+ }
+ switch (scsi_io->Control & MPI_SCSIIO_CONTROL_DATADIRECTION_MASK) {
+ case MPI_SCSIIO_CONTROL_NODATATRANSFER:
+ if (req->sreq->cmd.mode != SCSI_XFER_NONE) {
+ goto overrun;
+ }
+ break;
+
+ case MPI_SCSIIO_CONTROL_WRITE:
+ if (req->sreq->cmd.mode != SCSI_XFER_TO_DEV) {
+ goto overrun;
+ }
+ break;
+
+ case MPI_SCSIIO_CONTROL_READ:
+ if (req->sreq->cmd.mode != SCSI_XFER_FROM_DEV) {
+ goto overrun;
+ }
+ break;
+ }
+
+ if (scsi_req_enqueue(req->sreq)) {
+ scsi_req_continue(req->sreq);
+ }
+ return 0;
+
+overrun:
+ trace_mptsas_scsi_overflow(s, scsi_io->MsgContext, req->sreq->cmd.xfer,
+ scsi_io->DataLength);
+ status = MPI_IOCSTATUS_SCSI_DATA_OVERRUN;
+free_bad:
+ mptsas_free_request(req);
+bad:
+ memset(&reply, 0, sizeof(reply));
+ reply.TargetID = scsi_io->TargetID;
+ reply.Bus = scsi_io->Bus;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = scsi_io->Function;
+ reply.CDBLength = scsi_io->CDBLength;
+ reply.SenseBufferLength = scsi_io->SenseBufferLength;
+ reply.MsgContext = scsi_io->MsgContext;
+ reply.SCSIState = MPI_SCSI_STATE_NO_SCSI_STATUS;
+ reply.IOCStatus = status;
+
+ mptsas_fix_scsi_io_reply_endianness(&reply);
+ mptsas_reply(s, (MPIDefaultReply *)&reply);
+
+ return 0;
+}
+
+typedef struct {
+ Notifier notifier;
+ MPTSASState *s;
+ MPIMsgSCSITaskMgmtReply *reply;
+} MPTSASCancelNotifier;
+
+static void mptsas_cancel_notify(Notifier *notifier, void *data)
+{
+ MPTSASCancelNotifier *n = container_of(notifier,
+ MPTSASCancelNotifier,
+ notifier);
+
+ /* Abusing IOCLogInfo to store the expected number of requests... */
+ if (++n->reply->TerminationCount == n->reply->IOCLogInfo) {
+ n->reply->IOCLogInfo = 0;
+ mptsas_fix_scsi_task_mgmt_reply_endianness(n->reply);
+ mptsas_post_reply(n->s, (MPIDefaultReply *)n->reply);
+ g_free(n->reply);
+ }
+ g_free(n);
+}
+
+static void mptsas_process_scsi_task_mgmt(MPTSASState *s, MPIMsgSCSITaskMgmt *req)
+{
+ MPIMsgSCSITaskMgmtReply reply;
+ MPIMsgSCSITaskMgmtReply *reply_async;
+ int status, count;
+ SCSIDevice *sdev;
+ SCSIRequest *r, *next;
+ BusChild *kid;
+
+ mptsas_fix_scsi_task_mgmt_endianness(req);
+
+ QEMU_BUILD_BUG_ON(MPTSAS_MAX_REQUEST_SIZE < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
+
+ memset(&reply, 0, sizeof(reply));
+ reply.TargetID = req->TargetID;
+ reply.Bus = req->Bus;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = req->Function;
+ reply.TaskType = req->TaskType;
+ reply.MsgContext = req->MsgContext;
+
+ switch (req->TaskType) {
+ case MPI_SCSITASKMGMT_TASKTYPE_ABORT_TASK:
+ case MPI_SCSITASKMGMT_TASKTYPE_QUERY_TASK:
+ status = mptsas_scsi_device_find(s, req->Bus, req->TargetID,
+ req->LUN, &sdev);
+ if (status) {
+ reply.IOCStatus = status;
+ goto out;
+ }
+ if (sdev->lun != req->LUN[1]) {
+ reply.ResponseCode = MPI_SCSITASKMGMT_RSP_TM_INVALID_LUN;
+ goto out;
+ }
+
+ QTAILQ_FOREACH_SAFE(r, &sdev->requests, next, next) {
+ MPTSASRequest *cmd_req = r->hba_private;
+ if (cmd_req && cmd_req->scsi_io.MsgContext == req->TaskMsgContext) {
+ break;
+ }
+ }
+ if (r) {
+ /*
+ * Assert that the request has not been completed yet, we
+ * check for it in the loop above.
+ */
+ assert(r->hba_private);
+ if (req->TaskType == MPI_SCSITASKMGMT_TASKTYPE_QUERY_TASK) {
+ /* "If the specified command is present in the task set, then
+ * return a service response set to FUNCTION SUCCEEDED".
+ */
+ reply.ResponseCode = MPI_SCSITASKMGMT_RSP_TM_SUCCEEDED;
+ } else {
+ MPTSASCancelNotifier *notifier;
+
+ reply_async = g_memdup(&reply, sizeof(MPIMsgSCSITaskMgmtReply));
+ reply_async->IOCLogInfo = INT_MAX;
+
+ count = 1;
+ notifier = g_new(MPTSASCancelNotifier, 1);
+ notifier->s = s;
+ notifier->reply = reply_async;
+ notifier->notifier.notify = mptsas_cancel_notify;
+ scsi_req_cancel_async(r, &notifier->notifier);
+ goto reply_maybe_async;
+ }
+ }
+ break;
+
+ case MPI_SCSITASKMGMT_TASKTYPE_ABRT_TASK_SET:
+ case MPI_SCSITASKMGMT_TASKTYPE_CLEAR_TASK_SET:
+ status = mptsas_scsi_device_find(s, req->Bus, req->TargetID,
+ req->LUN, &sdev);
+ if (status) {
+ reply.IOCStatus = status;
+ goto out;
+ }
+ if (sdev->lun != req->LUN[1]) {
+ reply.ResponseCode = MPI_SCSITASKMGMT_RSP_TM_INVALID_LUN;
+ goto out;
+ }
+
+ reply_async = g_memdup(&reply, sizeof(MPIMsgSCSITaskMgmtReply));
+ reply_async->IOCLogInfo = INT_MAX;
+
+ count = 0;
+ QTAILQ_FOREACH_SAFE(r, &sdev->requests, next, next) {
+ if (r->hba_private) {
+ MPTSASCancelNotifier *notifier;
+
+ count++;
+ notifier = g_new(MPTSASCancelNotifier, 1);
+ notifier->s = s;
+ notifier->reply = reply_async;
+ notifier->notifier.notify = mptsas_cancel_notify;
+ scsi_req_cancel_async(r, &notifier->notifier);
+ }
+ }
+
+reply_maybe_async:
+ if (reply_async->TerminationCount < count) {
+ reply_async->IOCLogInfo = count;
+ return;
+ }
+ g_free(reply_async);
+ reply.TerminationCount = count;
+ break;
+
+ case MPI_SCSITASKMGMT_TASKTYPE_LOGICAL_UNIT_RESET:
+ status = mptsas_scsi_device_find(s, req->Bus, req->TargetID,
+ req->LUN, &sdev);
+ if (status) {
+ reply.IOCStatus = status;
+ goto out;
+ }
+ if (sdev->lun != req->LUN[1]) {
+ reply.ResponseCode = MPI_SCSITASKMGMT_RSP_TM_INVALID_LUN;
+ goto out;
+ }
+ device_cold_reset(&sdev->qdev);
+ break;
+
+ case MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET:
+ if (req->Bus != 0) {
+ reply.IOCStatus = MPI_IOCSTATUS_SCSI_INVALID_BUS;
+ goto out;
+ }
+ if (req->TargetID > s->max_devices) {
+ reply.IOCStatus = MPI_IOCSTATUS_SCSI_INVALID_TARGETID;
+ goto out;
+ }
+
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ sdev = SCSI_DEVICE(kid->child);
+ if (sdev->channel == 0 && sdev->id == req->TargetID) {
+ device_cold_reset(kid->child);
+ }
+ }
+ break;
+
+ case MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS:
+ bus_cold_reset(BUS(&s->bus));
+ break;
+
+ default:
+ reply.ResponseCode = MPI_SCSITASKMGMT_RSP_TM_NOT_SUPPORTED;
+ break;
+ }
+
+out:
+ mptsas_fix_scsi_task_mgmt_reply_endianness(&reply);
+ mptsas_post_reply(s, (MPIDefaultReply *)&reply);
+}
+
+static void mptsas_process_ioc_init(MPTSASState *s, MPIMsgIOCInit *req)
+{
+ MPIMsgIOCInitReply reply;
+
+ mptsas_fix_ioc_init_endianness(req);
+
+ QEMU_BUILD_BUG_ON(MPTSAS_MAX_REQUEST_SIZE < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
+
+ s->who_init = req->WhoInit;
+ s->reply_frame_size = req->ReplyFrameSize;
+ s->max_buses = req->MaxBuses;
+ s->max_devices = req->MaxDevices ? req->MaxDevices : 256;
+ s->host_mfa_high_addr = (hwaddr)req->HostMfaHighAddr << 32;
+ s->sense_buffer_high_addr = (hwaddr)req->SenseBufferHighAddr << 32;
+
+ if (s->state == MPI_IOC_STATE_READY) {
+ s->state = MPI_IOC_STATE_OPERATIONAL;
+ }
+
+ memset(&reply, 0, sizeof(reply));
+ reply.WhoInit = s->who_init;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = req->Function;
+ reply.MaxDevices = s->max_devices;
+ reply.MaxBuses = s->max_buses;
+ reply.MsgContext = req->MsgContext;
+
+ mptsas_fix_ioc_init_reply_endianness(&reply);
+ mptsas_reply(s, (MPIDefaultReply *)&reply);
+}
+
+static void mptsas_process_ioc_facts(MPTSASState *s,
+ MPIMsgIOCFacts *req)
+{
+ MPIMsgIOCFactsReply reply;
+
+ mptsas_fix_ioc_facts_endianness(req);
+
+ QEMU_BUILD_BUG_ON(MPTSAS_MAX_REQUEST_SIZE < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
+
+ memset(&reply, 0, sizeof(reply));
+ reply.MsgVersion = 0x0105;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = req->Function;
+ reply.MsgContext = req->MsgContext;
+ reply.MaxChainDepth = MPTSAS_MAXIMUM_CHAIN_DEPTH;
+ reply.WhoInit = s->who_init;
+ reply.BlockSize = MPTSAS_MAX_REQUEST_SIZE / sizeof(uint32_t);
+ reply.ReplyQueueDepth = ARRAY_SIZE(s->reply_post) - 1;
+ QEMU_BUILD_BUG_ON(ARRAY_SIZE(s->reply_post) != ARRAY_SIZE(s->reply_free));
+
+ reply.RequestFrameSize = 128;
+ reply.ProductID = MPTSAS1068_PRODUCT_ID;
+ reply.CurrentHostMfaHighAddr = s->host_mfa_high_addr >> 32;
+ reply.GlobalCredits = ARRAY_SIZE(s->request_post) - 1;
+ reply.NumberOfPorts = MPTSAS_NUM_PORTS;
+ reply.CurrentSenseBufferHighAddr = s->sense_buffer_high_addr >> 32;
+ reply.CurReplyFrameSize = s->reply_frame_size;
+ reply.MaxDevices = s->max_devices;
+ reply.MaxBuses = s->max_buses;
+ reply.FWVersionDev = 0;
+ reply.FWVersionUnit = 0x92;
+ reply.FWVersionMinor = 0x32;
+ reply.FWVersionMajor = 0x1;
+
+ mptsas_fix_ioc_facts_reply_endianness(&reply);
+ mptsas_reply(s, (MPIDefaultReply *)&reply);
+}
+
+static void mptsas_process_port_facts(MPTSASState *s,
+ MPIMsgPortFacts *req)
+{
+ MPIMsgPortFactsReply reply;
+
+ mptsas_fix_port_facts_endianness(req);
+
+ QEMU_BUILD_BUG_ON(MPTSAS_MAX_REQUEST_SIZE < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
+
+ memset(&reply, 0, sizeof(reply));
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = req->Function;
+ reply.PortNumber = req->PortNumber;
+ reply.MsgContext = req->MsgContext;
+
+ if (req->PortNumber < MPTSAS_NUM_PORTS) {
+ reply.PortType = MPI_PORTFACTS_PORTTYPE_SAS;
+ reply.MaxDevices = MPTSAS_NUM_PORTS;
+ reply.PortSCSIID = MPTSAS_NUM_PORTS;
+ reply.ProtocolFlags = MPI_PORTFACTS_PROTOCOL_LOGBUSADDR | MPI_PORTFACTS_PROTOCOL_INITIATOR;
+ }
+
+ mptsas_fix_port_facts_reply_endianness(&reply);
+ mptsas_reply(s, (MPIDefaultReply *)&reply);
+}
+
+static void mptsas_process_port_enable(MPTSASState *s,
+ MPIMsgPortEnable *req)
+{
+ MPIMsgPortEnableReply reply;
+
+ mptsas_fix_port_enable_endianness(req);
+
+ QEMU_BUILD_BUG_ON(MPTSAS_MAX_REQUEST_SIZE < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
+
+ memset(&reply, 0, sizeof(reply));
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.PortNumber = req->PortNumber;
+ reply.Function = req->Function;
+ reply.MsgContext = req->MsgContext;
+
+ mptsas_fix_port_enable_reply_endianness(&reply);
+ mptsas_reply(s, (MPIDefaultReply *)&reply);
+}
+
+static void mptsas_process_event_notification(MPTSASState *s,
+ MPIMsgEventNotify *req)
+{
+ MPIMsgEventNotifyReply reply;
+
+ mptsas_fix_event_notification_endianness(req);
+
+ QEMU_BUILD_BUG_ON(MPTSAS_MAX_REQUEST_SIZE < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_msg) < sizeof(*req));
+ QEMU_BUILD_BUG_ON(sizeof(s->doorbell_reply) < sizeof(reply));
+
+ /* Don't even bother storing whether event notification is enabled,
+ * since it is not accessible.
+ */
+
+ memset(&reply, 0, sizeof(reply));
+ reply.EventDataLength = sizeof(reply.Data) / 4;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = req->Function;
+
+ /* This is set because events are sent through the reply FIFOs. */
+ reply.MsgFlags = MPI_MSGFLAGS_CONTINUATION_REPLY;
+
+ reply.MsgContext = req->MsgContext;
+ reply.Event = MPI_EVENT_EVENT_CHANGE;
+ reply.Data[0] = !!req->Switch;
+
+ mptsas_fix_event_notification_reply_endianness(&reply);
+ mptsas_reply(s, (MPIDefaultReply *)&reply);
+}
+
+static void mptsas_process_message(MPTSASState *s, MPIRequestHeader *req)
+{
+ trace_mptsas_process_message(s, req->Function, req->MsgContext);
+ switch (req->Function) {
+ case MPI_FUNCTION_SCSI_TASK_MGMT:
+ mptsas_process_scsi_task_mgmt(s, (MPIMsgSCSITaskMgmt *)req);
+ break;
+
+ case MPI_FUNCTION_IOC_INIT:
+ mptsas_process_ioc_init(s, (MPIMsgIOCInit *)req);
+ break;
+
+ case MPI_FUNCTION_IOC_FACTS:
+ mptsas_process_ioc_facts(s, (MPIMsgIOCFacts *)req);
+ break;
+
+ case MPI_FUNCTION_PORT_FACTS:
+ mptsas_process_port_facts(s, (MPIMsgPortFacts *)req);
+ break;
+
+ case MPI_FUNCTION_PORT_ENABLE:
+ mptsas_process_port_enable(s, (MPIMsgPortEnable *)req);
+ break;
+
+ case MPI_FUNCTION_EVENT_NOTIFICATION:
+ mptsas_process_event_notification(s, (MPIMsgEventNotify *)req);
+ break;
+
+ case MPI_FUNCTION_CONFIG:
+ mptsas_process_config(s, (MPIMsgConfig *)req);
+ break;
+
+ default:
+ trace_mptsas_unhandled_cmd(s, req->Function, 0);
+ mptsas_set_fault(s, MPI_IOCSTATUS_INVALID_FUNCTION);
+ break;
+ }
+}
+
+static void mptsas_fetch_request(MPTSASState *s)
+{
+ PCIDevice *pci = (PCIDevice *) s;
+ char req[MPTSAS_MAX_REQUEST_SIZE];
+ MPIRequestHeader *hdr = (MPIRequestHeader *)req;
+ hwaddr addr;
+ int size;
+
+ /* Read the message header from the guest first. */
+ addr = s->host_mfa_high_addr | MPTSAS_FIFO_GET(s, request_post);
+ pci_dma_read(pci, addr, req, sizeof(*hdr));
+
+ if (hdr->Function < ARRAY_SIZE(mpi_request_sizes) &&
+ mpi_request_sizes[hdr->Function]) {
+ /* Read the rest of the request based on the type. Do not
+ * reread everything, as that could cause a TOC/TOU mismatch
+ * and leak data from the QEMU stack.
+ */
+ size = mpi_request_sizes[hdr->Function];
+ assert(size <= MPTSAS_MAX_REQUEST_SIZE);
+ pci_dma_read(pci, addr + sizeof(*hdr), &req[sizeof(*hdr)],
+ size - sizeof(*hdr));
+ }
+
+ if (hdr->Function == MPI_FUNCTION_SCSI_IO_REQUEST) {
+ /* SCSI I/O requests are separate from mptsas_process_message
+ * because they cannot be sent through the doorbell yet.
+ */
+ mptsas_process_scsi_io_request(s, (MPIMsgSCSIIORequest *)req, addr);
+ } else {
+ mptsas_process_message(s, (MPIRequestHeader *)req);
+ }
+}
+
+static void mptsas_fetch_requests(void *opaque)
+{
+ MPTSASState *s = opaque;
+
+ if (s->state != MPI_IOC_STATE_OPERATIONAL) {
+ mptsas_set_fault(s, MPI_IOCSTATUS_INVALID_STATE);
+ return;
+ }
+ while (!MPTSAS_FIFO_EMPTY(s, request_post)) {
+ mptsas_fetch_request(s);
+ }
+}
+
+static void mptsas_soft_reset(MPTSASState *s)
+{
+ uint32_t save_mask;
+
+ trace_mptsas_reset(s);
+
+ /* Temporarily disable interrupts */
+ save_mask = s->intr_mask;
+ s->intr_mask = MPI_HIM_DIM | MPI_HIM_RIM;
+ mptsas_update_interrupt(s);
+
+ bus_cold_reset(BUS(&s->bus));
+ s->intr_status = 0;
+ s->intr_mask = save_mask;
+
+ s->reply_free_tail = 0;
+ s->reply_free_head = 0;
+ s->reply_post_tail = 0;
+ s->reply_post_head = 0;
+ s->request_post_tail = 0;
+ s->request_post_head = 0;
+ qemu_bh_cancel(s->request_bh);
+
+ s->state = MPI_IOC_STATE_READY;
+}
+
+static uint32_t mptsas_doorbell_read(MPTSASState *s)
+{
+ uint32_t ret;
+
+ ret = (s->who_init << MPI_DOORBELL_WHO_INIT_SHIFT) & MPI_DOORBELL_WHO_INIT_MASK;
+ ret |= s->state;
+ switch (s->doorbell_state) {
+ case DOORBELL_NONE:
+ break;
+
+ case DOORBELL_WRITE:
+ ret |= MPI_DOORBELL_ACTIVE;
+ break;
+
+ case DOORBELL_READ:
+ /* Get rid of the IOC fault code. */
+ ret &= ~MPI_DOORBELL_DATA_MASK;
+
+ assert(s->intr_status & MPI_HIS_DOORBELL_INTERRUPT);
+ assert(s->doorbell_reply_idx <= s->doorbell_reply_size);
+
+ ret |= MPI_DOORBELL_ACTIVE;
+ if (s->doorbell_reply_idx < s->doorbell_reply_size) {
+ /* For more information about this endian switch, see the
+ * commit message for commit 36b62ae ("fw_cfg: fix endianness in
+ * fw_cfg_data_mem_read() / _write()", 2015-01-16).
+ */
+ ret |= le16_to_cpu(s->doorbell_reply[s->doorbell_reply_idx++]);
+ }
+ break;
+
+ default:
+ abort();
+ }
+
+ return ret;
+}
+
+static void mptsas_doorbell_write(MPTSASState *s, uint32_t val)
+{
+ if (s->doorbell_state == DOORBELL_WRITE) {
+ if (s->doorbell_idx < s->doorbell_cnt) {
+ /* For more information about this endian switch, see the
+ * commit message for commit 36b62ae ("fw_cfg: fix endianness in
+ * fw_cfg_data_mem_read() / _write()", 2015-01-16).
+ */
+ s->doorbell_msg[s->doorbell_idx++] = cpu_to_le32(val);
+ if (s->doorbell_idx == s->doorbell_cnt) {
+ mptsas_process_message(s, (MPIRequestHeader *)s->doorbell_msg);
+ }
+ }
+ return;
+ }
+
+ switch ((val & MPI_DOORBELL_FUNCTION_MASK) >> MPI_DOORBELL_FUNCTION_SHIFT) {
+ case MPI_FUNCTION_IOC_MESSAGE_UNIT_RESET:
+ mptsas_soft_reset(s);
+ break;
+ case MPI_FUNCTION_IO_UNIT_RESET:
+ break;
+ case MPI_FUNCTION_HANDSHAKE:
+ s->doorbell_state = DOORBELL_WRITE;
+ s->doorbell_idx = 0;
+ s->doorbell_cnt = (val & MPI_DOORBELL_ADD_DWORDS_MASK)
+ >> MPI_DOORBELL_ADD_DWORDS_SHIFT;
+ s->intr_status |= MPI_HIS_DOORBELL_INTERRUPT;
+ mptsas_update_interrupt(s);
+ break;
+ default:
+ trace_mptsas_unhandled_doorbell_cmd(s, val);
+ break;
+ }
+}
+
+static void mptsas_write_sequence_write(MPTSASState *s, uint32_t val)
+{
+ /* If the diagnostic register is enabled, any write to this register
+ * will disable it. Otherwise, the guest has to do a magic five-write
+ * sequence.
+ */
+ if (s->diagnostic & MPI_DIAG_DRWE) {
+ goto disable;
+ }
+
+ switch (s->diagnostic_idx) {
+ case 0:
+ if ((val & MPI_WRSEQ_KEY_VALUE_MASK) != MPI_WRSEQ_1ST_KEY_VALUE) {
+ goto disable;
+ }
+ break;
+ case 1:
+ if ((val & MPI_WRSEQ_KEY_VALUE_MASK) != MPI_WRSEQ_2ND_KEY_VALUE) {
+ goto disable;
+ }
+ break;
+ case 2:
+ if ((val & MPI_WRSEQ_KEY_VALUE_MASK) != MPI_WRSEQ_3RD_KEY_VALUE) {
+ goto disable;
+ }
+ break;
+ case 3:
+ if ((val & MPI_WRSEQ_KEY_VALUE_MASK) != MPI_WRSEQ_4TH_KEY_VALUE) {
+ goto disable;
+ }
+ break;
+ case 4:
+ if ((val & MPI_WRSEQ_KEY_VALUE_MASK) != MPI_WRSEQ_5TH_KEY_VALUE) {
+ goto disable;
+ }
+ /* Prepare Spaceball One for departure, and change the
+ * combination on my luggage!
+ */
+ s->diagnostic |= MPI_DIAG_DRWE;
+ break;
+ }
+ s->diagnostic_idx++;
+ return;
+
+disable:
+ s->diagnostic &= ~MPI_DIAG_DRWE;
+ s->diagnostic_idx = 0;
+}
+
+static int mptsas_hard_reset(MPTSASState *s)
+{
+ mptsas_soft_reset(s);
+
+ s->intr_mask = MPI_HIM_DIM | MPI_HIM_RIM;
+
+ s->host_mfa_high_addr = 0;
+ s->sense_buffer_high_addr = 0;
+ s->reply_frame_size = 0;
+ s->max_devices = MPTSAS_NUM_PORTS;
+ s->max_buses = 1;
+
+ return 0;
+}
+
+static void mptsas_interrupt_status_write(MPTSASState *s)
+{
+ switch (s->doorbell_state) {
+ case DOORBELL_NONE:
+ case DOORBELL_WRITE:
+ s->intr_status &= ~MPI_HIS_DOORBELL_INTERRUPT;
+ break;
+
+ case DOORBELL_READ:
+ /* The reply can be read continuously, so leave the interrupt up. */
+ assert(s->intr_status & MPI_HIS_DOORBELL_INTERRUPT);
+ if (s->doorbell_reply_idx == s->doorbell_reply_size) {
+ s->doorbell_state = DOORBELL_NONE;
+ }
+ break;
+
+ default:
+ abort();
+ }
+ mptsas_update_interrupt(s);
+}
+
+static uint32_t mptsas_reply_post_read(MPTSASState *s)
+{
+ uint32_t ret;
+
+ if (!MPTSAS_FIFO_EMPTY(s, reply_post)) {
+ ret = MPTSAS_FIFO_GET(s, reply_post);
+ } else {
+ ret = -1;
+ s->intr_status &= ~MPI_HIS_REPLY_MESSAGE_INTERRUPT;
+ mptsas_update_interrupt(s);
+ }
+
+ return ret;
+}
+
+static uint64_t mptsas_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MPTSASState *s = opaque;
+ uint32_t ret = 0;
+
+ switch (addr & ~3) {
+ case MPI_DOORBELL_OFFSET:
+ ret = mptsas_doorbell_read(s);
+ break;
+
+ case MPI_DIAGNOSTIC_OFFSET:
+ ret = s->diagnostic;
+ break;
+
+ case MPI_HOST_INTERRUPT_STATUS_OFFSET:
+ ret = s->intr_status;
+ break;
+
+ case MPI_HOST_INTERRUPT_MASK_OFFSET:
+ ret = s->intr_mask;
+ break;
+
+ case MPI_REPLY_POST_FIFO_OFFSET:
+ ret = mptsas_reply_post_read(s);
+ break;
+
+ default:
+ trace_mptsas_mmio_unhandled_read(s, addr);
+ break;
+ }
+ trace_mptsas_mmio_read(s, addr, ret);
+ return ret;
+}
+
+static void mptsas_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MPTSASState *s = opaque;
+
+ trace_mptsas_mmio_write(s, addr, val);
+ switch (addr) {
+ case MPI_DOORBELL_OFFSET:
+ mptsas_doorbell_write(s, val);
+ break;
+
+ case MPI_WRITE_SEQUENCE_OFFSET:
+ mptsas_write_sequence_write(s, val);
+ break;
+
+ case MPI_DIAGNOSTIC_OFFSET:
+ if (val & MPI_DIAG_RESET_ADAPTER) {
+ mptsas_hard_reset(s);
+ }
+ break;
+
+ case MPI_HOST_INTERRUPT_STATUS_OFFSET:
+ mptsas_interrupt_status_write(s);
+ break;
+
+ case MPI_HOST_INTERRUPT_MASK_OFFSET:
+ s->intr_mask = val & (MPI_HIM_RIM | MPI_HIM_DIM);
+ mptsas_update_interrupt(s);
+ break;
+
+ case MPI_REQUEST_POST_FIFO_OFFSET:
+ if (MPTSAS_FIFO_FULL(s, request_post)) {
+ mptsas_set_fault(s, MPI_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ } else {
+ MPTSAS_FIFO_PUT(s, request_post, val & ~0x03);
+ qemu_bh_schedule(s->request_bh);
+ }
+ break;
+
+ case MPI_REPLY_FREE_FIFO_OFFSET:
+ if (MPTSAS_FIFO_FULL(s, reply_free)) {
+ mptsas_set_fault(s, MPI_IOCSTATUS_INSUFFICIENT_RESOURCES);
+ } else {
+ MPTSAS_FIFO_PUT(s, reply_free, val);
+ }
+ break;
+
+ default:
+ trace_mptsas_mmio_unhandled_write(s, addr, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps mptsas_mmio_ops = {
+ .read = mptsas_mmio_read,
+ .write = mptsas_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static const MemoryRegionOps mptsas_port_ops = {
+ .read = mptsas_mmio_read,
+ .write = mptsas_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static uint64_t mptsas_diag_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MPTSASState *s = opaque;
+ trace_mptsas_diag_read(s, addr, 0);
+ return 0;
+}
+
+static void mptsas_diag_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ MPTSASState *s = opaque;
+ trace_mptsas_diag_write(s, addr, val);
+}
+
+static const MemoryRegionOps mptsas_diag_ops = {
+ .read = mptsas_diag_read,
+ .write = mptsas_diag_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static QEMUSGList *mptsas_get_sg_list(SCSIRequest *sreq)
+{
+ MPTSASRequest *req = sreq->hba_private;
+
+ return &req->qsg;
+}
+
+static void mptsas_command_complete(SCSIRequest *sreq,
+ size_t resid)
+{
+ MPTSASRequest *req = sreq->hba_private;
+ MPTSASState *s = req->dev;
+ uint8_t sense_buf[SCSI_SENSE_BUF_SIZE];
+ uint8_t sense_len;
+
+ hwaddr sense_buffer_addr = req->dev->sense_buffer_high_addr |
+ req->scsi_io.SenseBufferLowAddr;
+
+ trace_mptsas_command_complete(s, req->scsi_io.MsgContext,
+ sreq->status, resid);
+
+ sense_len = scsi_req_get_sense(sreq, sense_buf, SCSI_SENSE_BUF_SIZE);
+ if (sense_len > 0) {
+ pci_dma_write(PCI_DEVICE(s), sense_buffer_addr, sense_buf,
+ MIN(req->scsi_io.SenseBufferLength, sense_len));
+ }
+
+ if (sreq->status != GOOD || resid ||
+ req->dev->doorbell_state == DOORBELL_WRITE) {
+ MPIMsgSCSIIOReply reply;
+
+ memset(&reply, 0, sizeof(reply));
+ reply.TargetID = req->scsi_io.TargetID;
+ reply.Bus = req->scsi_io.Bus;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = req->scsi_io.Function;
+ reply.CDBLength = req->scsi_io.CDBLength;
+ reply.SenseBufferLength = req->scsi_io.SenseBufferLength;
+ reply.MsgFlags = req->scsi_io.MsgFlags;
+ reply.MsgContext = req->scsi_io.MsgContext;
+ reply.SCSIStatus = sreq->status;
+ if (sreq->status == GOOD) {
+ reply.TransferCount = req->scsi_io.DataLength - resid;
+ if (resid) {
+ reply.IOCStatus = MPI_IOCSTATUS_SCSI_DATA_UNDERRUN;
+ }
+ } else {
+ reply.SCSIState = MPI_SCSI_STATE_AUTOSENSE_VALID;
+ reply.SenseCount = sense_len;
+ reply.IOCStatus = MPI_IOCSTATUS_SCSI_DATA_UNDERRUN;
+ }
+
+ mptsas_fix_scsi_io_reply_endianness(&reply);
+ mptsas_post_reply(req->dev, (MPIDefaultReply *)&reply);
+ } else {
+ mptsas_turbo_reply(req->dev, req->scsi_io.MsgContext);
+ }
+
+ mptsas_free_request(req);
+}
+
+static void mptsas_request_cancelled(SCSIRequest *sreq)
+{
+ MPTSASRequest *req = sreq->hba_private;
+ MPIMsgSCSIIOReply reply;
+
+ memset(&reply, 0, sizeof(reply));
+ reply.TargetID = req->scsi_io.TargetID;
+ reply.Bus = req->scsi_io.Bus;
+ reply.MsgLength = sizeof(reply) / 4;
+ reply.Function = req->scsi_io.Function;
+ reply.CDBLength = req->scsi_io.CDBLength;
+ reply.SenseBufferLength = req->scsi_io.SenseBufferLength;
+ reply.MsgFlags = req->scsi_io.MsgFlags;
+ reply.MsgContext = req->scsi_io.MsgContext;
+ reply.SCSIState = MPI_SCSI_STATE_NO_SCSI_STATUS;
+ reply.IOCStatus = MPI_IOCSTATUS_SCSI_TASK_TERMINATED;
+
+ mptsas_fix_scsi_io_reply_endianness(&reply);
+ mptsas_post_reply(req->dev, (MPIDefaultReply *)&reply);
+ mptsas_free_request(req);
+}
+
+static void mptsas_save_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ MPTSASRequest *req = sreq->hba_private;
+ int i;
+
+ qemu_put_buffer(f, (unsigned char *)&req->scsi_io, sizeof(req->scsi_io));
+ qemu_put_be32(f, req->qsg.nsg);
+ for (i = 0; i < req->qsg.nsg; i++) {
+ qemu_put_be64(f, req->qsg.sg[i].base);
+ qemu_put_be64(f, req->qsg.sg[i].len);
+ }
+}
+
+static void *mptsas_load_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ SCSIBus *bus = sreq->bus;
+ MPTSASState *s = container_of(bus, MPTSASState, bus);
+ PCIDevice *pci = PCI_DEVICE(s);
+ MPTSASRequest *req;
+ int i, n;
+
+ req = g_new(MPTSASRequest, 1);
+ qemu_get_buffer(f, (unsigned char *)&req->scsi_io, sizeof(req->scsi_io));
+
+ n = qemu_get_be32(f);
+ /* TODO: add a way for SCSIBusInfo's load_request to fail,
+ * and fail migration instead of asserting here.
+ * This is just one thing (there are probably more) that must be
+ * fixed before we can allow NDEBUG compilation.
+ */
+ assert(n >= 0);
+
+ pci_dma_sglist_init(&req->qsg, pci, n);
+ for (i = 0; i < n; i++) {
+ uint64_t base = qemu_get_be64(f);
+ uint64_t len = qemu_get_be64(f);
+ qemu_sglist_add(&req->qsg, base, len);
+ }
+
+ scsi_req_ref(sreq);
+ req->sreq = sreq;
+ req->dev = s;
+
+ return req;
+}
+
+static const struct SCSIBusInfo mptsas_scsi_info = {
+ .tcq = true,
+ .max_target = MPTSAS_NUM_PORTS,
+ .max_lun = 1,
+
+ .get_sg_list = mptsas_get_sg_list,
+ .complete = mptsas_command_complete,
+ .cancel = mptsas_request_cancelled,
+ .save_request = mptsas_save_request,
+ .load_request = mptsas_load_request,
+};
+
+static void mptsas_scsi_realize(PCIDevice *dev, Error **errp)
+{
+ MPTSASState *s = MPT_SAS(dev);
+ Error *err = NULL;
+ int ret;
+
+ dev->config[PCI_LATENCY_TIMER] = 0;
+ dev->config[PCI_INTERRUPT_PIN] = 0x01;
+
+ if (s->msi != ON_OFF_AUTO_OFF) {
+ ret = msi_init(dev, 0, 1, true, false, &err);
+ /* Any error other than -ENOTSUP(board's MSI support is broken)
+ * is a programming error */
+ assert(!ret || ret == -ENOTSUP);
+ if (ret && s->msi == ON_OFF_AUTO_ON) {
+ /* Can't satisfy user's explicit msi=on request, fail */
+ error_append_hint(&err, "You have to use msi=auto (default) or "
+ "msi=off with this machine type.\n");
+ error_propagate(errp, err);
+ return;
+ }
+ assert(!err || s->msi == ON_OFF_AUTO_AUTO);
+ /* With msi=auto, we fall back to MSI off silently */
+ error_free(err);
+
+ /* Only used for migration. */
+ s->msi_in_use = (ret == 0);
+ }
+
+ memory_region_init_io(&s->mmio_io, OBJECT(s), &mptsas_mmio_ops, s,
+ "mptsas-mmio", 0x4000);
+ memory_region_init_io(&s->port_io, OBJECT(s), &mptsas_port_ops, s,
+ "mptsas-io", 256);
+ memory_region_init_io(&s->diag_io, OBJECT(s), &mptsas_diag_ops, s,
+ "mptsas-diag", 0x10000);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->port_io);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_TYPE_32, &s->mmio_io);
+ pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_TYPE_32, &s->diag_io);
+
+ if (!s->sas_addr) {
+ s->sas_addr = ((NAA_LOCALLY_ASSIGNED_ID << 24) |
+ IEEE_COMPANY_LOCALLY_ASSIGNED) << 36;
+ s->sas_addr |= (pci_dev_bus_num(dev) << 16);
+ s->sas_addr |= (PCI_SLOT(dev->devfn) << 8);
+ s->sas_addr |= PCI_FUNC(dev->devfn);
+ }
+ s->max_devices = MPTSAS_NUM_PORTS;
+
+ s->request_bh = qemu_bh_new(mptsas_fetch_requests, s);
+
+ scsi_bus_init(&s->bus, sizeof(s->bus), &dev->qdev, &mptsas_scsi_info);
+}
+
+static void mptsas_scsi_uninit(PCIDevice *dev)
+{
+ MPTSASState *s = MPT_SAS(dev);
+
+ qemu_bh_delete(s->request_bh);
+ msi_uninit(dev);
+}
+
+static void mptsas_reset(DeviceState *dev)
+{
+ MPTSASState *s = MPT_SAS(dev);
+
+ mptsas_hard_reset(s);
+}
+
+static int mptsas_post_load(void *opaque, int version_id)
+{
+ MPTSASState *s = opaque;
+
+ if (s->doorbell_idx > s->doorbell_cnt ||
+ s->doorbell_cnt > ARRAY_SIZE(s->doorbell_msg) ||
+ s->doorbell_reply_idx > s->doorbell_reply_size ||
+ s->doorbell_reply_size > ARRAY_SIZE(s->doorbell_reply) ||
+ MPTSAS_FIFO_INVALID(s, request_post) ||
+ MPTSAS_FIFO_INVALID(s, reply_post) ||
+ MPTSAS_FIFO_INVALID(s, reply_free) ||
+ s->diagnostic_idx > 4) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_mptsas = {
+ .name = "mptsas",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = mptsas_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, MPTSASState),
+ VMSTATE_BOOL(msi_in_use, MPTSASState),
+ VMSTATE_UINT32(state, MPTSASState),
+ VMSTATE_UINT8(who_init, MPTSASState),
+ VMSTATE_UINT8(doorbell_state, MPTSASState),
+ VMSTATE_UINT32_ARRAY(doorbell_msg, MPTSASState, 256),
+ VMSTATE_INT32(doorbell_idx, MPTSASState),
+ VMSTATE_INT32(doorbell_cnt, MPTSASState),
+
+ VMSTATE_UINT16_ARRAY(doorbell_reply, MPTSASState, 256),
+ VMSTATE_INT32(doorbell_reply_idx, MPTSASState),
+ VMSTATE_INT32(doorbell_reply_size, MPTSASState),
+
+ VMSTATE_UINT32(diagnostic, MPTSASState),
+ VMSTATE_UINT8(diagnostic_idx, MPTSASState),
+
+ VMSTATE_UINT32(intr_status, MPTSASState),
+ VMSTATE_UINT32(intr_mask, MPTSASState),
+
+ VMSTATE_UINT32_ARRAY(request_post, MPTSASState,
+ MPTSAS_REQUEST_QUEUE_DEPTH + 1),
+ VMSTATE_UINT16(request_post_head, MPTSASState),
+ VMSTATE_UINT16(request_post_tail, MPTSASState),
+
+ VMSTATE_UINT32_ARRAY(reply_post, MPTSASState,
+ MPTSAS_REPLY_QUEUE_DEPTH + 1),
+ VMSTATE_UINT16(reply_post_head, MPTSASState),
+ VMSTATE_UINT16(reply_post_tail, MPTSASState),
+
+ VMSTATE_UINT32_ARRAY(reply_free, MPTSASState,
+ MPTSAS_REPLY_QUEUE_DEPTH + 1),
+ VMSTATE_UINT16(reply_free_head, MPTSASState),
+ VMSTATE_UINT16(reply_free_tail, MPTSASState),
+
+ VMSTATE_UINT16(max_buses, MPTSASState),
+ VMSTATE_UINT16(max_devices, MPTSASState),
+ VMSTATE_UINT16(reply_frame_size, MPTSASState),
+ VMSTATE_UINT64(host_mfa_high_addr, MPTSASState),
+ VMSTATE_UINT64(sense_buffer_high_addr, MPTSASState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property mptsas_properties[] = {
+ DEFINE_PROP_UINT64("sas_address", MPTSASState, sas_addr, 0),
+ /* TODO: test MSI support under Windows */
+ DEFINE_PROP_ON_OFF_AUTO("msi", MPTSASState, msi, ON_OFF_AUTO_AUTO),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void mptsas1068_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(oc);
+
+ pc->realize = mptsas_scsi_realize;
+ pc->exit = mptsas_scsi_uninit;
+ pc->romfile = 0;
+ pc->vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ pc->device_id = PCI_DEVICE_ID_LSI_SAS1068;
+ pc->subsystem_vendor_id = PCI_VENDOR_ID_LSI_LOGIC;
+ pc->subsystem_id = 0x8000;
+ pc->class_id = PCI_CLASS_STORAGE_SCSI;
+ device_class_set_props(dc, mptsas_properties);
+ dc->reset = mptsas_reset;
+ dc->vmsd = &vmstate_mptsas;
+ dc->desc = "LSI SAS 1068";
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static const TypeInfo mptsas_info = {
+ .name = TYPE_MPTSAS1068,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MPTSASState),
+ .class_init = mptsas1068_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void mptsas_register_types(void)
+{
+ type_register(&mptsas_info);
+}
+
+type_init(mptsas_register_types)
diff --git a/hw/scsi/mptsas.h b/hw/scsi/mptsas.h
new file mode 100644
index 00000000..c046497d
--- /dev/null
+++ b/hw/scsi/mptsas.h
@@ -0,0 +1,105 @@
+#ifndef MPTSAS_H
+#define MPTSAS_H
+
+#include "mpi.h"
+#include "qom/object.h"
+
+#define MPTSAS_NUM_PORTS 8
+#define MPTSAS_MAX_FRAMES 2048 /* Firmware limit at 65535 */
+
+#define MPTSAS_REQUEST_QUEUE_DEPTH 128
+#define MPTSAS_REPLY_QUEUE_DEPTH 128
+
+#define MPTSAS_MAXIMUM_CHAIN_DEPTH 0x22
+
+typedef struct MPTSASRequest MPTSASRequest;
+
+#define TYPE_MPTSAS1068 "mptsas1068"
+typedef struct MPTSASState MPTSASState;
+DECLARE_INSTANCE_CHECKER(MPTSASState, MPT_SAS,
+ TYPE_MPTSAS1068)
+
+enum {
+ DOORBELL_NONE,
+ DOORBELL_WRITE,
+ DOORBELL_READ
+};
+
+struct MPTSASState {
+ PCIDevice dev;
+ MemoryRegion mmio_io;
+ MemoryRegion port_io;
+ MemoryRegion diag_io;
+ QEMUBH *request_bh;
+
+ /* properties */
+ OnOffAuto msi;
+ uint64_t sas_addr;
+
+ bool msi_in_use;
+
+ /* Doorbell register */
+ uint32_t state;
+ uint8_t who_init;
+ uint8_t doorbell_state;
+
+ /* Buffer for requests that are sent through the doorbell register. */
+ uint32_t doorbell_msg[256];
+ int doorbell_idx;
+ int doorbell_cnt;
+
+ uint16_t doorbell_reply[256];
+ int doorbell_reply_idx;
+ int doorbell_reply_size;
+
+ /* Other registers */
+ uint8_t diagnostic_idx;
+ uint32_t diagnostic;
+ uint32_t intr_mask;
+ uint32_t intr_status;
+
+ /* Request queues */
+ uint32_t request_post[MPTSAS_REQUEST_QUEUE_DEPTH + 1];
+ uint16_t request_post_head;
+ uint16_t request_post_tail;
+
+ uint32_t reply_post[MPTSAS_REPLY_QUEUE_DEPTH + 1];
+ uint16_t reply_post_head;
+ uint16_t reply_post_tail;
+
+ uint32_t reply_free[MPTSAS_REPLY_QUEUE_DEPTH + 1];
+ uint16_t reply_free_head;
+ uint16_t reply_free_tail;
+
+ /* IOC Facts */
+ hwaddr host_mfa_high_addr;
+ hwaddr sense_buffer_high_addr;
+ uint16_t max_devices;
+ uint16_t max_buses;
+ uint16_t reply_frame_size;
+
+ SCSIBus bus;
+};
+
+void mptsas_fix_scsi_io_endianness(MPIMsgSCSIIORequest *req);
+void mptsas_fix_scsi_io_reply_endianness(MPIMsgSCSIIOReply *reply);
+void mptsas_fix_scsi_task_mgmt_endianness(MPIMsgSCSITaskMgmt *req);
+void mptsas_fix_scsi_task_mgmt_reply_endianness(MPIMsgSCSITaskMgmtReply *reply);
+void mptsas_fix_ioc_init_endianness(MPIMsgIOCInit *req);
+void mptsas_fix_ioc_init_reply_endianness(MPIMsgIOCInitReply *reply);
+void mptsas_fix_ioc_facts_endianness(MPIMsgIOCFacts *req);
+void mptsas_fix_ioc_facts_reply_endianness(MPIMsgIOCFactsReply *reply);
+void mptsas_fix_config_endianness(MPIMsgConfig *req);
+void mptsas_fix_config_reply_endianness(MPIMsgConfigReply *reply);
+void mptsas_fix_port_facts_endianness(MPIMsgPortFacts *req);
+void mptsas_fix_port_facts_reply_endianness(MPIMsgPortFactsReply *reply);
+void mptsas_fix_port_enable_endianness(MPIMsgPortEnable *req);
+void mptsas_fix_port_enable_reply_endianness(MPIMsgPortEnableReply *reply);
+void mptsas_fix_event_notification_endianness(MPIMsgEventNotify *req);
+void mptsas_fix_event_notification_reply_endianness(MPIMsgEventNotifyReply *reply);
+
+void mptsas_reply(MPTSASState *s, MPIDefaultReply *reply);
+
+void mptsas_process_config(MPTSASState *s, MPIMsgConfig *req);
+
+#endif /* MPTSAS_H */
diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c
new file mode 100644
index 00000000..ceceafb2
--- /dev/null
+++ b/hw/scsi/scsi-bus.c
@@ -0,0 +1,1887 @@
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/hw-version.h"
+#include "hw/qdev-properties.h"
+#include "hw/scsi/scsi.h"
+#include "migration/qemu-file-types.h"
+#include "migration/vmstate.h"
+#include "scsi/constants.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/runstate.h"
+#include "trace.h"
+#include "sysemu/dma.h"
+#include "qemu/cutils.h"
+
+static char *scsibus_get_dev_path(DeviceState *dev);
+static char *scsibus_get_fw_dev_path(DeviceState *dev);
+static void scsi_req_dequeue(SCSIRequest *req);
+static uint8_t *scsi_target_alloc_buf(SCSIRequest *req, size_t len);
+static void scsi_target_free_buf(SCSIRequest *req);
+
+static int next_scsi_bus;
+
+static SCSIDevice *do_scsi_device_find(SCSIBus *bus,
+ int channel, int id, int lun,
+ bool include_unrealized)
+{
+ BusChild *kid;
+ SCSIDevice *retval = NULL;
+
+ QTAILQ_FOREACH_RCU(kid, &bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->channel == channel && dev->id == id) {
+ if (dev->lun == lun) {
+ retval = dev;
+ break;
+ }
+
+ /*
+ * If we don't find exact match (channel/bus/lun),
+ * we will return the first device which matches channel/bus
+ */
+
+ if (!retval) {
+ retval = dev;
+ }
+ }
+ }
+
+ /*
+ * This function might run on the IO thread and we might race against
+ * main thread hot-plugging the device.
+ * We assume that as soon as .realized is set to true we can let
+ * the user access the device.
+ */
+
+ if (retval && !include_unrealized &&
+ !qatomic_load_acquire(&retval->qdev.realized)) {
+ retval = NULL;
+ }
+
+ return retval;
+}
+
+SCSIDevice *scsi_device_find(SCSIBus *bus, int channel, int id, int lun)
+{
+ RCU_READ_LOCK_GUARD();
+ return do_scsi_device_find(bus, channel, id, lun, false);
+}
+
+SCSIDevice *scsi_device_get(SCSIBus *bus, int channel, int id, int lun)
+{
+ SCSIDevice *d;
+ RCU_READ_LOCK_GUARD();
+ d = do_scsi_device_find(bus, channel, id, lun, false);
+ if (d) {
+ object_ref(d);
+ }
+ return d;
+}
+
+static void scsi_device_realize(SCSIDevice *s, Error **errp)
+{
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
+ if (sc->realize) {
+ sc->realize(s, errp);
+ }
+}
+
+static void scsi_device_unrealize(SCSIDevice *s)
+{
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
+ if (sc->unrealize) {
+ sc->unrealize(s);
+ }
+}
+
+int scsi_bus_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf,
+ size_t buf_len, void *hba_private)
+{
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
+ int rc;
+
+ assert(cmd->len == 0);
+ rc = scsi_req_parse_cdb(dev, cmd, buf, buf_len);
+ if (bus->info->parse_cdb) {
+ rc = bus->info->parse_cdb(dev, cmd, buf, buf_len, hba_private);
+ }
+ return rc;
+}
+
+static SCSIRequest *scsi_device_alloc_req(SCSIDevice *s, uint32_t tag, uint32_t lun,
+ uint8_t *buf, void *hba_private)
+{
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
+ if (sc->alloc_req) {
+ return sc->alloc_req(s, tag, lun, buf, hba_private);
+ }
+
+ return NULL;
+}
+
+void scsi_device_unit_attention_reported(SCSIDevice *s)
+{
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(s);
+ if (sc->unit_attention_reported) {
+ sc->unit_attention_reported(s);
+ }
+}
+
+/* Create a scsi bus, and attach devices to it. */
+void scsi_bus_init_named(SCSIBus *bus, size_t bus_size, DeviceState *host,
+ const SCSIBusInfo *info, const char *bus_name)
+{
+ qbus_init(bus, bus_size, TYPE_SCSI_BUS, host, bus_name);
+ bus->busnr = next_scsi_bus++;
+ bus->info = info;
+ qbus_set_bus_hotplug_handler(BUS(bus));
+}
+
+static void scsi_dma_restart_bh(void *opaque)
+{
+ SCSIDevice *s = opaque;
+ SCSIRequest *req, *next;
+
+ qemu_bh_delete(s->bh);
+ s->bh = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->conf.blk));
+ QTAILQ_FOREACH_SAFE(req, &s->requests, next, next) {
+ scsi_req_ref(req);
+ if (req->retry) {
+ req->retry = false;
+ switch (req->cmd.mode) {
+ case SCSI_XFER_FROM_DEV:
+ case SCSI_XFER_TO_DEV:
+ scsi_req_continue(req);
+ break;
+ case SCSI_XFER_NONE:
+ scsi_req_dequeue(req);
+ scsi_req_enqueue(req);
+ break;
+ }
+ }
+ scsi_req_unref(req);
+ }
+ aio_context_release(blk_get_aio_context(s->conf.blk));
+ /* Drop the reference that was acquired in scsi_dma_restart_cb */
+ object_unref(OBJECT(s));
+}
+
+void scsi_req_retry(SCSIRequest *req)
+{
+ /* No need to save a reference, because scsi_dma_restart_bh just
+ * looks at the request list. */
+ req->retry = true;
+}
+
+static void scsi_dma_restart_cb(void *opaque, bool running, RunState state)
+{
+ SCSIDevice *s = opaque;
+
+ if (!running) {
+ return;
+ }
+ if (!s->bh) {
+ AioContext *ctx = blk_get_aio_context(s->conf.blk);
+ /* The reference is dropped in scsi_dma_restart_bh.*/
+ object_ref(OBJECT(s));
+ s->bh = aio_bh_new(ctx, scsi_dma_restart_bh, s);
+ qemu_bh_schedule(s->bh);
+ }
+}
+
+static bool scsi_bus_is_address_free(SCSIBus *bus,
+ int channel, int target, int lun,
+ SCSIDevice **p_dev)
+{
+ SCSIDevice *d;
+
+ RCU_READ_LOCK_GUARD();
+ d = do_scsi_device_find(bus, channel, target, lun, true);
+ if (d && d->lun == lun) {
+ if (p_dev) {
+ *p_dev = d;
+ }
+ return false;
+ }
+ if (p_dev) {
+ *p_dev = NULL;
+ }
+ return true;
+}
+
+static bool scsi_bus_check_address(BusState *qbus, DeviceState *qdev, Error **errp)
+{
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+ SCSIBus *bus = SCSI_BUS(qbus);
+
+ if (dev->channel > bus->info->max_channel) {
+ error_setg(errp, "bad scsi channel id: %d", dev->channel);
+ return false;
+ }
+ if (dev->id != -1 && dev->id > bus->info->max_target) {
+ error_setg(errp, "bad scsi device id: %d", dev->id);
+ return false;
+ }
+ if (dev->lun != -1 && dev->lun > bus->info->max_lun) {
+ error_setg(errp, "bad scsi device lun: %d", dev->lun);
+ return false;
+ }
+
+ if (dev->id != -1 && dev->lun != -1) {
+ SCSIDevice *d;
+ if (!scsi_bus_is_address_free(bus, dev->channel, dev->id, dev->lun, &d)) {
+ error_setg(errp, "lun already used by '%s'", d->qdev.id);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void scsi_qdev_realize(DeviceState *qdev, Error **errp)
+{
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
+ bool is_free;
+ Error *local_err = NULL;
+
+ if (dev->id == -1) {
+ int id = -1;
+ if (dev->lun == -1) {
+ dev->lun = 0;
+ }
+ do {
+ is_free = scsi_bus_is_address_free(bus, dev->channel, ++id, dev->lun, NULL);
+ } while (!is_free && id < bus->info->max_target);
+ if (!is_free) {
+ error_setg(errp, "no free target");
+ return;
+ }
+ dev->id = id;
+ } else if (dev->lun == -1) {
+ int lun = -1;
+ do {
+ is_free = scsi_bus_is_address_free(bus, dev->channel, dev->id, ++lun, NULL);
+ } while (!is_free && lun < bus->info->max_lun);
+ if (!is_free) {
+ error_setg(errp, "no free lun");
+ return;
+ }
+ dev->lun = lun;
+ }
+
+ QTAILQ_INIT(&dev->requests);
+ scsi_device_realize(dev, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+ dev->vmsentry = qdev_add_vm_change_state_handler(DEVICE(dev),
+ scsi_dma_restart_cb, dev);
+}
+
+static void scsi_qdev_unrealize(DeviceState *qdev)
+{
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->vmsentry) {
+ qemu_del_vm_change_state_handler(dev->vmsentry);
+ }
+
+ scsi_device_purge_requests(dev, SENSE_CODE(NO_SENSE));
+
+ scsi_device_unrealize(dev);
+
+ blockdev_mark_auto_del(dev->conf.blk);
+}
+
+/* handle legacy '-drive if=scsi,...' cmd line args */
+SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockBackend *blk,
+ int unit, bool removable, int bootindex,
+ bool share_rw,
+ BlockdevOnError rerror,
+ BlockdevOnError werror,
+ const char *serial, Error **errp)
+{
+ const char *driver;
+ char *name;
+ DeviceState *dev;
+ DriveInfo *dinfo;
+
+ if (blk_is_sg(blk)) {
+ driver = "scsi-generic";
+ } else {
+ dinfo = blk_legacy_dinfo(blk);
+ if (dinfo && dinfo->media_cd) {
+ driver = "scsi-cd";
+ } else {
+ driver = "scsi-hd";
+ }
+ }
+ dev = qdev_new(driver);
+ name = g_strdup_printf("legacy[%d]", unit);
+ object_property_add_child(OBJECT(bus), name, OBJECT(dev));
+ g_free(name);
+
+ qdev_prop_set_uint32(dev, "scsi-id", unit);
+ if (bootindex >= 0) {
+ object_property_set_int(OBJECT(dev), "bootindex", bootindex,
+ &error_abort);
+ }
+ if (object_property_find(OBJECT(dev), "removable")) {
+ qdev_prop_set_bit(dev, "removable", removable);
+ }
+ if (serial && object_property_find(OBJECT(dev), "serial")) {
+ qdev_prop_set_string(dev, "serial", serial);
+ }
+ if (!qdev_prop_set_drive_err(dev, "drive", blk, errp)) {
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ if (!object_property_set_bool(OBJECT(dev), "share-rw", share_rw, errp)) {
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+
+ qdev_prop_set_enum(dev, "rerror", rerror);
+ qdev_prop_set_enum(dev, "werror", werror);
+
+ if (!qdev_realize_and_unref(dev, &bus->qbus, errp)) {
+ object_unparent(OBJECT(dev));
+ return NULL;
+ }
+ return SCSI_DEVICE(dev);
+}
+
+void scsi_bus_legacy_handle_cmdline(SCSIBus *bus)
+{
+ Location loc;
+ DriveInfo *dinfo;
+ int unit;
+
+ loc_push_none(&loc);
+ for (unit = 0; unit <= bus->info->max_target; unit++) {
+ dinfo = drive_get(IF_SCSI, bus->busnr, unit);
+ if (dinfo == NULL) {
+ continue;
+ }
+ qemu_opts_loc_restore(dinfo->opts);
+ scsi_bus_legacy_add_drive(bus, blk_by_legacy_dinfo(dinfo),
+ unit, false, -1, false,
+ BLOCKDEV_ON_ERROR_AUTO,
+ BLOCKDEV_ON_ERROR_AUTO,
+ NULL, &error_fatal);
+ }
+ loc_pop(&loc);
+}
+
+static int32_t scsi_invalid_field(SCSIRequest *req, uint8_t *buf)
+{
+ scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+}
+
+static const struct SCSIReqOps reqops_invalid_field = {
+ .size = sizeof(SCSIRequest),
+ .send_command = scsi_invalid_field
+};
+
+/* SCSIReqOps implementation for invalid commands. */
+
+static int32_t scsi_invalid_command(SCSIRequest *req, uint8_t *buf)
+{
+ scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+}
+
+static const struct SCSIReqOps reqops_invalid_opcode = {
+ .size = sizeof(SCSIRequest),
+ .send_command = scsi_invalid_command
+};
+
+/* SCSIReqOps implementation for unit attention conditions. */
+
+static int32_t scsi_unit_attention(SCSIRequest *req, uint8_t *buf)
+{
+ if (req->dev->unit_attention.key == UNIT_ATTENTION) {
+ scsi_req_build_sense(req, req->dev->unit_attention);
+ } else if (req->bus->unit_attention.key == UNIT_ATTENTION) {
+ scsi_req_build_sense(req, req->bus->unit_attention);
+ }
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+}
+
+static const struct SCSIReqOps reqops_unit_attention = {
+ .size = sizeof(SCSIRequest),
+ .send_command = scsi_unit_attention
+};
+
+/* SCSIReqOps implementation for REPORT LUNS and for commands sent to
+ an invalid LUN. */
+
+typedef struct SCSITargetReq SCSITargetReq;
+
+struct SCSITargetReq {
+ SCSIRequest req;
+ int len;
+ uint8_t *buf;
+ int buf_len;
+};
+
+static void store_lun(uint8_t *outbuf, int lun)
+{
+ if (lun < 256) {
+ /* Simple logical unit addressing method*/
+ outbuf[0] = 0;
+ outbuf[1] = lun;
+ } else {
+ /* Flat space addressing method */
+ outbuf[0] = 0x40 | (lun >> 8);
+ outbuf[1] = (lun & 255);
+ }
+}
+
+static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
+{
+ BusChild *kid;
+ int channel, id;
+ uint8_t tmp[8] = {0};
+ int len = 0;
+ GByteArray *buf;
+
+ if (r->req.cmd.xfer < 16) {
+ return false;
+ }
+ if (r->req.cmd.buf[2] > 2) {
+ return false;
+ }
+
+ /* reserve space for 63 LUNs*/
+ buf = g_byte_array_sized_new(512);
+
+ channel = r->req.dev->channel;
+ id = r->req.dev->id;
+
+ /* add size (will be updated later to correct value */
+ g_byte_array_append(buf, tmp, 8);
+ len += 8;
+
+ /* add LUN0 */
+ g_byte_array_append(buf, tmp, 8);
+ len += 8;
+
+ WITH_RCU_READ_LOCK_GUARD() {
+ QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->channel == channel && dev->id == id && dev->lun != 0) {
+ store_lun(tmp, dev->lun);
+ g_byte_array_append(buf, tmp, 8);
+ len += 8;
+ }
+ }
+ }
+
+ r->buf_len = len;
+ r->buf = g_byte_array_free(buf, FALSE);
+ r->len = MIN(len, r->req.cmd.xfer & ~7);
+
+ /* store the LUN list length */
+ stl_be_p(&r->buf[0], len - 8);
+ return true;
+}
+
+static bool scsi_target_emulate_inquiry(SCSITargetReq *r)
+{
+ assert(r->req.dev->lun != r->req.lun);
+
+ scsi_target_alloc_buf(&r->req, SCSI_INQUIRY_LEN);
+
+ if (r->req.cmd.buf[1] & 0x2) {
+ /* Command support data - optional, not implemented */
+ return false;
+ }
+
+ if (r->req.cmd.buf[1] & 0x1) {
+ /* Vital product data */
+ uint8_t page_code = r->req.cmd.buf[2];
+ r->buf[r->len++] = page_code ; /* this page */
+ r->buf[r->len++] = 0x00;
+
+ switch (page_code) {
+ case 0x00: /* Supported page codes, mandatory */
+ {
+ int pages;
+ pages = r->len++;
+ r->buf[r->len++] = 0x00; /* list of supported pages (this page) */
+ r->buf[pages] = r->len - pages - 1; /* number of pages */
+ break;
+ }
+ default:
+ return false;
+ }
+ /* done with EVPD */
+ assert(r->len < r->buf_len);
+ r->len = MIN(r->req.cmd.xfer, r->len);
+ return true;
+ }
+
+ /* Standard INQUIRY data */
+ if (r->req.cmd.buf[2] != 0) {
+ return false;
+ }
+
+ /* PAGE CODE == 0 */
+ r->len = MIN(r->req.cmd.xfer, SCSI_INQUIRY_LEN);
+ memset(r->buf, 0, r->len);
+ if (r->req.lun != 0) {
+ r->buf[0] = TYPE_NO_LUN;
+ } else {
+ r->buf[0] = TYPE_NOT_PRESENT | TYPE_INACTIVE;
+ r->buf[2] = 5; /* Version */
+ r->buf[3] = 2 | 0x10; /* HiSup, response data format */
+ r->buf[4] = r->len - 5; /* Additional Length = (Len - 1) - 4 */
+ r->buf[7] = 0x10 | (r->req.bus->info->tcq ? 0x02 : 0); /* Sync, TCQ. */
+ memcpy(&r->buf[8], "QEMU ", 8);
+ memcpy(&r->buf[16], "QEMU TARGET ", 16);
+ pstrcpy((char *) &r->buf[32], 4, qemu_hw_version());
+ }
+ return true;
+}
+
+static size_t scsi_sense_len(SCSIRequest *req)
+{
+ if (req->dev->type == TYPE_SCANNER)
+ return SCSI_SENSE_LEN_SCANNER;
+ else
+ return SCSI_SENSE_LEN;
+}
+
+static int32_t scsi_target_send_command(SCSIRequest *req, uint8_t *buf)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+ int fixed_sense = (req->cmd.buf[1] & 1) == 0;
+
+ if (req->lun != 0 &&
+ buf[0] != INQUIRY && buf[0] != REQUEST_SENSE) {
+ scsi_req_build_sense(req, SENSE_CODE(LUN_NOT_SUPPORTED));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+ }
+ switch (buf[0]) {
+ case REPORT_LUNS:
+ if (!scsi_target_emulate_report_luns(r)) {
+ goto illegal_request;
+ }
+ break;
+ case INQUIRY:
+ if (!scsi_target_emulate_inquiry(r)) {
+ goto illegal_request;
+ }
+ break;
+ case REQUEST_SENSE:
+ scsi_target_alloc_buf(&r->req, scsi_sense_len(req));
+ if (req->lun != 0) {
+ const struct SCSISense sense = SENSE_CODE(LUN_NOT_SUPPORTED);
+
+ r->len = scsi_build_sense_buf(r->buf, req->cmd.xfer,
+ sense, fixed_sense);
+ } else {
+ r->len = scsi_device_get_sense(r->req.dev, r->buf,
+ MIN(req->cmd.xfer, r->buf_len),
+ fixed_sense);
+ }
+ if (r->req.dev->sense_is_ua) {
+ scsi_device_unit_attention_reported(req->dev);
+ r->req.dev->sense_len = 0;
+ r->req.dev->sense_is_ua = false;
+ }
+ break;
+ case TEST_UNIT_READY:
+ break;
+ default:
+ scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+ illegal_request:
+ scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD));
+ scsi_req_complete(req, CHECK_CONDITION);
+ return 0;
+ }
+
+ if (!r->len) {
+ scsi_req_complete(req, GOOD);
+ }
+ return r->len;
+}
+
+static void scsi_target_read_data(SCSIRequest *req)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+ uint32_t n;
+
+ n = r->len;
+ if (n > 0) {
+ r->len = 0;
+ scsi_req_data(&r->req, n);
+ } else {
+ scsi_req_complete(&r->req, GOOD);
+ }
+}
+
+static uint8_t *scsi_target_get_buf(SCSIRequest *req)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+ return r->buf;
+}
+
+static uint8_t *scsi_target_alloc_buf(SCSIRequest *req, size_t len)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+ r->buf = g_malloc(len);
+ r->buf_len = len;
+
+ return r->buf;
+}
+
+static void scsi_target_free_buf(SCSIRequest *req)
+{
+ SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req);
+
+ g_free(r->buf);
+}
+
+static const struct SCSIReqOps reqops_target_command = {
+ .size = sizeof(SCSITargetReq),
+ .send_command = scsi_target_send_command,
+ .read_data = scsi_target_read_data,
+ .get_buf = scsi_target_get_buf,
+ .free_req = scsi_target_free_buf,
+};
+
+
+SCSIRequest *scsi_req_alloc(const SCSIReqOps *reqops, SCSIDevice *d,
+ uint32_t tag, uint32_t lun, void *hba_private)
+{
+ SCSIRequest *req;
+ SCSIBus *bus = scsi_bus_from_device(d);
+ BusState *qbus = BUS(bus);
+ const int memset_off = offsetof(SCSIRequest, sense)
+ + sizeof(req->sense);
+
+ req = g_malloc(reqops->size);
+ memset((uint8_t *)req + memset_off, 0, reqops->size - memset_off);
+ req->refcount = 1;
+ req->bus = bus;
+ req->dev = d;
+ req->tag = tag;
+ req->lun = lun;
+ req->hba_private = hba_private;
+ req->status = -1;
+ req->host_status = -1;
+ req->ops = reqops;
+ object_ref(OBJECT(d));
+ object_ref(OBJECT(qbus->parent));
+ notifier_list_init(&req->cancel_notifiers);
+ trace_scsi_req_alloc(req->dev->id, req->lun, req->tag);
+ return req;
+}
+
+SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun,
+ uint8_t *buf, size_t buf_len, void *hba_private)
+{
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, d->qdev.parent_bus);
+ const SCSIReqOps *ops;
+ SCSIDeviceClass *sc = SCSI_DEVICE_GET_CLASS(d);
+ SCSIRequest *req;
+ SCSICommand cmd = { .len = 0 };
+ int ret;
+
+ if (buf_len == 0) {
+ trace_scsi_req_parse_bad(d->id, lun, tag, 0);
+ goto invalid_opcode;
+ }
+
+ if ((d->unit_attention.key == UNIT_ATTENTION ||
+ bus->unit_attention.key == UNIT_ATTENTION) &&
+ (buf[0] != INQUIRY &&
+ buf[0] != REPORT_LUNS &&
+ buf[0] != GET_CONFIGURATION &&
+ buf[0] != GET_EVENT_STATUS_NOTIFICATION &&
+
+ /*
+ * If we already have a pending unit attention condition,
+ * report this one before triggering another one.
+ */
+ !(buf[0] == REQUEST_SENSE && d->sense_is_ua))) {
+ ops = &reqops_unit_attention;
+ } else if (lun != d->lun ||
+ buf[0] == REPORT_LUNS ||
+ (buf[0] == REQUEST_SENSE && d->sense_len)) {
+ ops = &reqops_target_command;
+ } else {
+ ops = NULL;
+ }
+
+ if (ops != NULL || !sc->parse_cdb) {
+ ret = scsi_req_parse_cdb(d, &cmd, buf, buf_len);
+ } else {
+ ret = sc->parse_cdb(d, &cmd, buf, buf_len, hba_private);
+ }
+
+ if (ret != 0) {
+ trace_scsi_req_parse_bad(d->id, lun, tag, buf[0]);
+invalid_opcode:
+ req = scsi_req_alloc(&reqops_invalid_opcode, d, tag, lun, hba_private);
+ } else {
+ assert(cmd.len != 0);
+ trace_scsi_req_parsed(d->id, lun, tag, buf[0],
+ cmd.mode, cmd.xfer);
+ if (cmd.lba != -1) {
+ trace_scsi_req_parsed_lba(d->id, lun, tag, buf[0],
+ cmd.lba);
+ }
+
+ if (cmd.xfer > INT32_MAX) {
+ req = scsi_req_alloc(&reqops_invalid_field, d, tag, lun, hba_private);
+ } else if (ops) {
+ req = scsi_req_alloc(ops, d, tag, lun, hba_private);
+ } else {
+ req = scsi_device_alloc_req(d, tag, lun, buf, hba_private);
+ }
+ }
+
+ req->cmd = cmd;
+ req->residual = req->cmd.xfer;
+
+ switch (buf[0]) {
+ case INQUIRY:
+ trace_scsi_inquiry(d->id, lun, tag, cmd.buf[1], cmd.buf[2]);
+ break;
+ case TEST_UNIT_READY:
+ trace_scsi_test_unit_ready(d->id, lun, tag);
+ break;
+ case REPORT_LUNS:
+ trace_scsi_report_luns(d->id, lun, tag);
+ break;
+ case REQUEST_SENSE:
+ trace_scsi_request_sense(d->id, lun, tag);
+ break;
+ default:
+ break;
+ }
+
+ return req;
+}
+
+uint8_t *scsi_req_get_buf(SCSIRequest *req)
+{
+ return req->ops->get_buf(req);
+}
+
+static void scsi_clear_unit_attention(SCSIRequest *req)
+{
+ SCSISense *ua;
+ if (req->dev->unit_attention.key != UNIT_ATTENTION &&
+ req->bus->unit_attention.key != UNIT_ATTENTION) {
+ return;
+ }
+
+ /*
+ * If an INQUIRY command enters the enabled command state,
+ * the device server shall [not] clear any unit attention condition;
+ * See also MMC-6, paragraphs 6.5 and 6.6.2.
+ */
+ if (req->cmd.buf[0] == INQUIRY ||
+ req->cmd.buf[0] == GET_CONFIGURATION ||
+ req->cmd.buf[0] == GET_EVENT_STATUS_NOTIFICATION) {
+ return;
+ }
+
+ if (req->dev->unit_attention.key == UNIT_ATTENTION) {
+ ua = &req->dev->unit_attention;
+ } else {
+ ua = &req->bus->unit_attention;
+ }
+
+ /*
+ * If a REPORT LUNS command enters the enabled command state, [...]
+ * the device server shall clear any pending unit attention condition
+ * with an additional sense code of REPORTED LUNS DATA HAS CHANGED.
+ */
+ if (req->cmd.buf[0] == REPORT_LUNS &&
+ !(ua->asc == SENSE_CODE(REPORTED_LUNS_CHANGED).asc &&
+ ua->ascq == SENSE_CODE(REPORTED_LUNS_CHANGED).ascq)) {
+ return;
+ }
+
+ *ua = SENSE_CODE(NO_SENSE);
+}
+
+int scsi_req_get_sense(SCSIRequest *req, uint8_t *buf, int len)
+{
+ int ret;
+
+ assert(len >= 14);
+ if (!req->sense_len) {
+ return 0;
+ }
+
+ ret = scsi_convert_sense(req->sense, req->sense_len, buf, len, true);
+
+ /*
+ * FIXME: clearing unit attention conditions upon autosense should be done
+ * only if the UA_INTLCK_CTRL field in the Control mode page is set to 00b
+ * (SAM-5, 5.14).
+ *
+ * We assume UA_INTLCK_CTRL to be 00b for HBAs that support autosense, and
+ * 10b for HBAs that do not support it (do not call scsi_req_get_sense).
+ * Here we handle unit attention clearing for UA_INTLCK_CTRL == 00b.
+ */
+ if (req->dev->sense_is_ua) {
+ scsi_device_unit_attention_reported(req->dev);
+ req->dev->sense_len = 0;
+ req->dev->sense_is_ua = false;
+ }
+ return ret;
+}
+
+int scsi_device_get_sense(SCSIDevice *dev, uint8_t *buf, int len, bool fixed)
+{
+ return scsi_convert_sense(dev->sense, dev->sense_len, buf, len, fixed);
+}
+
+void scsi_req_build_sense(SCSIRequest *req, SCSISense sense)
+{
+ trace_scsi_req_build_sense(req->dev->id, req->lun, req->tag,
+ sense.key, sense.asc, sense.ascq);
+ req->sense_len = scsi_build_sense(req->sense, sense);
+}
+
+static void scsi_req_enqueue_internal(SCSIRequest *req)
+{
+ assert(!req->enqueued);
+ scsi_req_ref(req);
+ if (req->bus->info->get_sg_list) {
+ req->sg = req->bus->info->get_sg_list(req);
+ } else {
+ req->sg = NULL;
+ }
+ req->enqueued = true;
+ QTAILQ_INSERT_TAIL(&req->dev->requests, req, next);
+}
+
+int32_t scsi_req_enqueue(SCSIRequest *req)
+{
+ int32_t rc;
+
+ assert(!req->retry);
+ scsi_req_enqueue_internal(req);
+ scsi_req_ref(req);
+ rc = req->ops->send_command(req, req->cmd.buf);
+ scsi_req_unref(req);
+ return rc;
+}
+
+static void scsi_req_dequeue(SCSIRequest *req)
+{
+ trace_scsi_req_dequeue(req->dev->id, req->lun, req->tag);
+ req->retry = false;
+ if (req->enqueued) {
+ QTAILQ_REMOVE(&req->dev->requests, req, next);
+ req->enqueued = false;
+ scsi_req_unref(req);
+ }
+}
+
+static int scsi_get_performance_length(int num_desc, int type, int data_type)
+{
+ /* MMC-6, paragraph 6.7. */
+ switch (type) {
+ case 0:
+ if ((data_type & 3) == 0) {
+ /* Each descriptor is as in Table 295 - Nominal performance. */
+ return 16 * num_desc + 8;
+ } else {
+ /* Each descriptor is as in Table 296 - Exceptions. */
+ return 6 * num_desc + 8;
+ }
+ case 1:
+ case 4:
+ case 5:
+ return 8 * num_desc + 8;
+ case 2:
+ return 2048 * num_desc + 8;
+ case 3:
+ return 16 * num_desc + 8;
+ default:
+ return 8;
+ }
+}
+
+static int ata_passthrough_xfer_unit(SCSIDevice *dev, uint8_t *buf)
+{
+ int byte_block = (buf[2] >> 2) & 0x1;
+ int type = (buf[2] >> 4) & 0x1;
+ int xfer_unit;
+
+ if (byte_block) {
+ if (type) {
+ xfer_unit = dev->blocksize;
+ } else {
+ xfer_unit = 512;
+ }
+ } else {
+ xfer_unit = 1;
+ }
+
+ return xfer_unit;
+}
+
+static int ata_passthrough_12_xfer(SCSIDevice *dev, uint8_t *buf)
+{
+ int length = buf[2] & 0x3;
+ int xfer;
+ int unit = ata_passthrough_xfer_unit(dev, buf);
+
+ switch (length) {
+ case 0:
+ case 3: /* USB-specific. */
+ default:
+ xfer = 0;
+ break;
+ case 1:
+ xfer = buf[3];
+ break;
+ case 2:
+ xfer = buf[4];
+ break;
+ }
+
+ return xfer * unit;
+}
+
+static int ata_passthrough_16_xfer(SCSIDevice *dev, uint8_t *buf)
+{
+ int extend = buf[1] & 0x1;
+ int length = buf[2] & 0x3;
+ int xfer;
+ int unit = ata_passthrough_xfer_unit(dev, buf);
+
+ switch (length) {
+ case 0:
+ case 3: /* USB-specific. */
+ default:
+ xfer = 0;
+ break;
+ case 1:
+ xfer = buf[4];
+ xfer |= (extend ? buf[3] << 8 : 0);
+ break;
+ case 2:
+ xfer = buf[6];
+ xfer |= (extend ? buf[5] << 8 : 0);
+ break;
+ }
+
+ return xfer * unit;
+}
+
+static int scsi_req_xfer(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf)
+{
+ cmd->xfer = scsi_cdb_xfer(buf);
+ switch (buf[0]) {
+ case TEST_UNIT_READY:
+ case REWIND:
+ case START_STOP:
+ case SET_CAPACITY:
+ case WRITE_FILEMARKS:
+ case WRITE_FILEMARKS_16:
+ case SPACE:
+ case RESERVE:
+ case RELEASE:
+ case ERASE:
+ case ALLOW_MEDIUM_REMOVAL:
+ case SEEK_10:
+ case SYNCHRONIZE_CACHE:
+ case SYNCHRONIZE_CACHE_16:
+ case LOCATE_16:
+ case LOCK_UNLOCK_CACHE:
+ case SET_CD_SPEED:
+ case SET_LIMITS:
+ case WRITE_LONG_10:
+ case UPDATE_BLOCK:
+ case RESERVE_TRACK:
+ case SET_READ_AHEAD:
+ case PRE_FETCH:
+ case PRE_FETCH_16:
+ case ALLOW_OVERWRITE:
+ cmd->xfer = 0;
+ break;
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ if ((buf[1] & 2) == 0) {
+ cmd->xfer = 0;
+ } else if ((buf[1] & 4) != 0) {
+ cmd->xfer = 1;
+ }
+ cmd->xfer *= dev->blocksize;
+ break;
+ case MODE_SENSE:
+ break;
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ cmd->xfer = buf[1] & 1 ? 0 : dev->blocksize;
+ break;
+ case READ_CAPACITY_10:
+ cmd->xfer = 8;
+ break;
+ case READ_BLOCK_LIMITS:
+ cmd->xfer = 6;
+ break;
+ case SEND_VOLUME_TAG:
+ /* GPCMD_SET_STREAMING from multimedia commands. */
+ if (dev->type == TYPE_ROM) {
+ cmd->xfer = buf[10] | (buf[9] << 8);
+ } else {
+ cmd->xfer = buf[9] | (buf[8] << 8);
+ }
+ break;
+ case WRITE_6:
+ /* length 0 means 256 blocks */
+ if (cmd->xfer == 0) {
+ cmd->xfer = 256;
+ }
+ /* fall through */
+ case WRITE_10:
+ case WRITE_VERIFY_10:
+ case WRITE_12:
+ case WRITE_VERIFY_12:
+ case WRITE_16:
+ case WRITE_VERIFY_16:
+ cmd->xfer *= dev->blocksize;
+ break;
+ case READ_6:
+ case READ_REVERSE:
+ /* length 0 means 256 blocks */
+ if (cmd->xfer == 0) {
+ cmd->xfer = 256;
+ }
+ /* fall through */
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ cmd->xfer *= dev->blocksize;
+ break;
+ case FORMAT_UNIT:
+ /* MMC mandates the parameter list to be 12-bytes long. Parameters
+ * for block devices are restricted to the header right now. */
+ if (dev->type == TYPE_ROM && (buf[1] & 16)) {
+ cmd->xfer = 12;
+ } else {
+ cmd->xfer = (buf[1] & 16) == 0 ? 0 : (buf[1] & 32 ? 8 : 4);
+ }
+ break;
+ case INQUIRY:
+ case RECEIVE_DIAGNOSTIC:
+ case SEND_DIAGNOSTIC:
+ cmd->xfer = buf[4] | (buf[3] << 8);
+ break;
+ case READ_CD:
+ case READ_BUFFER:
+ case WRITE_BUFFER:
+ case SEND_CUE_SHEET:
+ cmd->xfer = buf[8] | (buf[7] << 8) | (buf[6] << 16);
+ break;
+ case PERSISTENT_RESERVE_OUT:
+ cmd->xfer = ldl_be_p(&buf[5]) & 0xffffffffULL;
+ break;
+ case ERASE_12:
+ if (dev->type == TYPE_ROM) {
+ /* MMC command GET PERFORMANCE. */
+ cmd->xfer = scsi_get_performance_length(buf[9] | (buf[8] << 8),
+ buf[10], buf[1] & 0x1f);
+ }
+ break;
+ case MECHANISM_STATUS:
+ case READ_DVD_STRUCTURE:
+ case SEND_DVD_STRUCTURE:
+ case MAINTENANCE_OUT:
+ case MAINTENANCE_IN:
+ if (dev->type == TYPE_ROM) {
+ /* GPCMD_REPORT_KEY and GPCMD_SEND_KEY from multi media commands */
+ cmd->xfer = buf[9] | (buf[8] << 8);
+ }
+ break;
+ case ATA_PASSTHROUGH_12:
+ if (dev->type == TYPE_ROM) {
+ /* BLANK command of MMC */
+ cmd->xfer = 0;
+ } else {
+ cmd->xfer = ata_passthrough_12_xfer(dev, buf);
+ }
+ break;
+ case ATA_PASSTHROUGH_16:
+ cmd->xfer = ata_passthrough_16_xfer(dev, buf);
+ break;
+ }
+ return 0;
+}
+
+static int scsi_req_stream_xfer(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf)
+{
+ switch (buf[0]) {
+ /* stream commands */
+ case ERASE_12:
+ case ERASE_16:
+ cmd->xfer = 0;
+ break;
+ case READ_6:
+ case READ_REVERSE:
+ case RECOVER_BUFFERED_DATA:
+ case WRITE_6:
+ cmd->xfer = buf[4] | (buf[3] << 8) | (buf[2] << 16);
+ if (buf[1] & 0x01) { /* fixed */
+ cmd->xfer *= dev->blocksize;
+ }
+ break;
+ case READ_16:
+ case READ_REVERSE_16:
+ case VERIFY_16:
+ case WRITE_16:
+ cmd->xfer = buf[14] | (buf[13] << 8) | (buf[12] << 16);
+ if (buf[1] & 0x01) { /* fixed */
+ cmd->xfer *= dev->blocksize;
+ }
+ break;
+ case REWIND:
+ case LOAD_UNLOAD:
+ cmd->xfer = 0;
+ break;
+ case SPACE_16:
+ cmd->xfer = buf[13] | (buf[12] << 8);
+ break;
+ case READ_POSITION:
+ switch (buf[1] & 0x1f) /* operation code */ {
+ case SHORT_FORM_BLOCK_ID:
+ case SHORT_FORM_VENDOR_SPECIFIC:
+ cmd->xfer = 20;
+ break;
+ case LONG_FORM:
+ cmd->xfer = 32;
+ break;
+ case EXTENDED_FORM:
+ cmd->xfer = buf[8] | (buf[7] << 8);
+ break;
+ default:
+ return -1;
+ }
+
+ break;
+ case FORMAT_UNIT:
+ cmd->xfer = buf[4] | (buf[3] << 8);
+ break;
+ /* generic commands */
+ default:
+ return scsi_req_xfer(cmd, dev, buf);
+ }
+ return 0;
+}
+
+static int scsi_req_medium_changer_xfer(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf)
+{
+ switch (buf[0]) {
+ /* medium changer commands */
+ case EXCHANGE_MEDIUM:
+ case INITIALIZE_ELEMENT_STATUS:
+ case INITIALIZE_ELEMENT_STATUS_WITH_RANGE:
+ case MOVE_MEDIUM:
+ case POSITION_TO_ELEMENT:
+ cmd->xfer = 0;
+ break;
+ case READ_ELEMENT_STATUS:
+ cmd->xfer = buf[9] | (buf[8] << 8) | (buf[7] << 16);
+ break;
+
+ /* generic commands */
+ default:
+ return scsi_req_xfer(cmd, dev, buf);
+ }
+ return 0;
+}
+
+static int scsi_req_scanner_length(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf)
+{
+ switch (buf[0]) {
+ /* Scanner commands */
+ case OBJECT_POSITION:
+ cmd->xfer = 0;
+ break;
+ case SCAN:
+ cmd->xfer = buf[4];
+ break;
+ case READ_10:
+ case SEND:
+ case GET_WINDOW:
+ case SET_WINDOW:
+ cmd->xfer = buf[8] | (buf[7] << 8) | (buf[6] << 16);
+ break;
+ default:
+ /* GET_DATA_BUFFER_STATUS xfer handled by scsi_req_xfer */
+ return scsi_req_xfer(cmd, dev, buf);
+ }
+
+ return 0;
+}
+
+static void scsi_cmd_xfer_mode(SCSICommand *cmd)
+{
+ if (!cmd->xfer) {
+ cmd->mode = SCSI_XFER_NONE;
+ return;
+ }
+ switch (cmd->buf[0]) {
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_VERIFY_10:
+ case WRITE_12:
+ case WRITE_VERIFY_12:
+ case WRITE_16:
+ case WRITE_VERIFY_16:
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ case COPY:
+ case COPY_VERIFY:
+ case COMPARE:
+ case CHANGE_DEFINITION:
+ case LOG_SELECT:
+ case MODE_SELECT:
+ case MODE_SELECT_10:
+ case SEND_DIAGNOSTIC:
+ case WRITE_BUFFER:
+ case FORMAT_UNIT:
+ case REASSIGN_BLOCKS:
+ case SEARCH_EQUAL:
+ case SEARCH_HIGH:
+ case SEARCH_LOW:
+ case UPDATE_BLOCK:
+ case WRITE_LONG_10:
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ case UNMAP:
+ case SEARCH_HIGH_12:
+ case SEARCH_EQUAL_12:
+ case SEARCH_LOW_12:
+ case MEDIUM_SCAN:
+ case SEND_VOLUME_TAG:
+ case SEND_CUE_SHEET:
+ case SEND_DVD_STRUCTURE:
+ case PERSISTENT_RESERVE_OUT:
+ case MAINTENANCE_OUT:
+ case SET_WINDOW:
+ case SCAN:
+ /* SCAN conflicts with START_STOP. START_STOP has cmd->xfer set to 0 for
+ * non-scanner devices, so we only get here for SCAN and not for START_STOP.
+ */
+ cmd->mode = SCSI_XFER_TO_DEV;
+ break;
+ case ATA_PASSTHROUGH_12:
+ case ATA_PASSTHROUGH_16:
+ /* T_DIR */
+ cmd->mode = (cmd->buf[2] & 0x8) ?
+ SCSI_XFER_FROM_DEV : SCSI_XFER_TO_DEV;
+ break;
+ default:
+ cmd->mode = SCSI_XFER_FROM_DEV;
+ break;
+ }
+}
+
+int scsi_req_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf,
+ size_t buf_len)
+{
+ int rc;
+ int len;
+
+ cmd->lba = -1;
+ len = scsi_cdb_length(buf);
+ if (len < 0 || len > buf_len) {
+ return -1;
+ }
+
+ cmd->len = len;
+ switch (dev->type) {
+ case TYPE_TAPE:
+ rc = scsi_req_stream_xfer(cmd, dev, buf);
+ break;
+ case TYPE_MEDIUM_CHANGER:
+ rc = scsi_req_medium_changer_xfer(cmd, dev, buf);
+ break;
+ case TYPE_SCANNER:
+ rc = scsi_req_scanner_length(cmd, dev, buf);
+ break;
+ default:
+ rc = scsi_req_xfer(cmd, dev, buf);
+ break;
+ }
+
+ if (rc != 0)
+ return rc;
+
+ memcpy(cmd->buf, buf, cmd->len);
+ scsi_cmd_xfer_mode(cmd);
+ cmd->lba = scsi_cmd_lba(cmd);
+ return 0;
+}
+
+void scsi_device_report_change(SCSIDevice *dev, SCSISense sense)
+{
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus);
+
+ scsi_device_set_ua(dev, sense);
+ if (bus->info->change) {
+ bus->info->change(bus, dev, sense);
+ }
+}
+
+SCSIRequest *scsi_req_ref(SCSIRequest *req)
+{
+ assert(req->refcount > 0);
+ req->refcount++;
+ return req;
+}
+
+void scsi_req_unref(SCSIRequest *req)
+{
+ assert(req->refcount > 0);
+ if (--req->refcount == 0) {
+ BusState *qbus = req->dev->qdev.parent_bus;
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, qbus);
+
+ if (bus->info->free_request && req->hba_private) {
+ bus->info->free_request(bus, req->hba_private);
+ }
+ if (req->ops->free_req) {
+ req->ops->free_req(req);
+ }
+ object_unref(OBJECT(req->dev));
+ object_unref(OBJECT(qbus->parent));
+ g_free(req);
+ }
+}
+
+/* Tell the device that we finished processing this chunk of I/O. It
+ will start the next chunk or complete the command. */
+void scsi_req_continue(SCSIRequest *req)
+{
+ if (req->io_canceled) {
+ trace_scsi_req_continue_canceled(req->dev->id, req->lun, req->tag);
+ return;
+ }
+ trace_scsi_req_continue(req->dev->id, req->lun, req->tag);
+ if (req->cmd.mode == SCSI_XFER_TO_DEV) {
+ req->ops->write_data(req);
+ } else {
+ req->ops->read_data(req);
+ }
+}
+
+/* Called by the devices when data is ready for the HBA. The HBA should
+ start a DMA operation to read or fill the device's data buffer.
+ Once it completes, calling scsi_req_continue will restart I/O. */
+void scsi_req_data(SCSIRequest *req, int len)
+{
+ uint8_t *buf;
+ if (req->io_canceled) {
+ trace_scsi_req_data_canceled(req->dev->id, req->lun, req->tag, len);
+ return;
+ }
+ trace_scsi_req_data(req->dev->id, req->lun, req->tag, len);
+ assert(req->cmd.mode != SCSI_XFER_NONE);
+ if (!req->sg) {
+ req->residual -= len;
+ req->bus->info->transfer_data(req, len);
+ return;
+ }
+
+ /* If the device calls scsi_req_data and the HBA specified a
+ * scatter/gather list, the transfer has to happen in a single
+ * step. */
+ assert(!req->dma_started);
+ req->dma_started = true;
+
+ buf = scsi_req_get_buf(req);
+ if (req->cmd.mode == SCSI_XFER_FROM_DEV) {
+ dma_buf_read(buf, len, &req->residual, req->sg,
+ MEMTXATTRS_UNSPECIFIED);
+ } else {
+ dma_buf_write(buf, len, &req->residual, req->sg,
+ MEMTXATTRS_UNSPECIFIED);
+ }
+ scsi_req_continue(req);
+}
+
+void scsi_req_print(SCSIRequest *req)
+{
+ FILE *fp = stderr;
+ int i;
+
+ fprintf(fp, "[%s id=%d] %s",
+ req->dev->qdev.parent_bus->name,
+ req->dev->id,
+ scsi_command_name(req->cmd.buf[0]));
+ for (i = 1; i < req->cmd.len; i++) {
+ fprintf(fp, " 0x%02x", req->cmd.buf[i]);
+ }
+ switch (req->cmd.mode) {
+ case SCSI_XFER_NONE:
+ fprintf(fp, " - none\n");
+ break;
+ case SCSI_XFER_FROM_DEV:
+ fprintf(fp, " - from-dev len=%zd\n", req->cmd.xfer);
+ break;
+ case SCSI_XFER_TO_DEV:
+ fprintf(fp, " - to-dev len=%zd\n", req->cmd.xfer);
+ break;
+ default:
+ fprintf(fp, " - Oops\n");
+ break;
+ }
+}
+
+void scsi_req_complete_failed(SCSIRequest *req, int host_status)
+{
+ SCSISense sense;
+ int status;
+
+ assert(req->status == -1 && req->host_status == -1);
+ assert(req->ops != &reqops_unit_attention);
+
+ if (!req->bus->info->fail) {
+ status = scsi_sense_from_host_status(req->host_status, &sense);
+ if (status == CHECK_CONDITION) {
+ scsi_req_build_sense(req, sense);
+ }
+ scsi_req_complete(req, status);
+ return;
+ }
+
+ req->host_status = host_status;
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->bus->info->fail(req);
+
+ /* Cancelled requests might end up being completed instead of cancelled */
+ notifier_list_notify(&req->cancel_notifiers, req);
+ scsi_req_unref(req);
+}
+
+void scsi_req_complete(SCSIRequest *req, int status)
+{
+ assert(req->status == -1 && req->host_status == -1);
+ req->status = status;
+ req->host_status = SCSI_HOST_OK;
+
+ assert(req->sense_len <= sizeof(req->sense));
+ if (status == GOOD) {
+ req->sense_len = 0;
+ }
+
+ if (req->sense_len) {
+ memcpy(req->dev->sense, req->sense, req->sense_len);
+ req->dev->sense_len = req->sense_len;
+ req->dev->sense_is_ua = (req->ops == &reqops_unit_attention);
+ } else {
+ req->dev->sense_len = 0;
+ req->dev->sense_is_ua = false;
+ }
+
+ /*
+ * Unit attention state is now stored in the device's sense buffer
+ * if the HBA didn't do autosense. Clear the pending unit attention
+ * flags.
+ */
+ scsi_clear_unit_attention(req);
+
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->bus->info->complete(req, req->residual);
+
+ /* Cancelled requests might end up being completed instead of cancelled */
+ notifier_list_notify(&req->cancel_notifiers, req);
+ scsi_req_unref(req);
+}
+
+/* Called by the devices when the request is canceled. */
+void scsi_req_cancel_complete(SCSIRequest *req)
+{
+ assert(req->io_canceled);
+ if (req->bus->info->cancel) {
+ req->bus->info->cancel(req);
+ }
+ notifier_list_notify(&req->cancel_notifiers, req);
+ scsi_req_unref(req);
+}
+
+/* Cancel @req asynchronously. @notifier is added to @req's cancellation
+ * notifier list, the bus will be notified the requests cancellation is
+ * completed.
+ * */
+void scsi_req_cancel_async(SCSIRequest *req, Notifier *notifier)
+{
+ trace_scsi_req_cancel(req->dev->id, req->lun, req->tag);
+ if (notifier) {
+ notifier_list_add(&req->cancel_notifiers, notifier);
+ }
+ if (req->io_canceled) {
+ /* A blk_aio_cancel_async is pending; when it finishes,
+ * scsi_req_cancel_complete will be called and will
+ * call the notifier we just added. Just wait for that.
+ */
+ assert(req->aiocb);
+ return;
+ }
+ /* Dropped in scsi_req_cancel_complete. */
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->io_canceled = true;
+ if (req->aiocb) {
+ blk_aio_cancel_async(req->aiocb);
+ } else {
+ scsi_req_cancel_complete(req);
+ }
+}
+
+void scsi_req_cancel(SCSIRequest *req)
+{
+ trace_scsi_req_cancel(req->dev->id, req->lun, req->tag);
+ if (!req->enqueued) {
+ return;
+ }
+ assert(!req->io_canceled);
+ /* Dropped in scsi_req_cancel_complete. */
+ scsi_req_ref(req);
+ scsi_req_dequeue(req);
+ req->io_canceled = true;
+ if (req->aiocb) {
+ blk_aio_cancel(req->aiocb);
+ } else {
+ scsi_req_cancel_complete(req);
+ }
+}
+
+static int scsi_ua_precedence(SCSISense sense)
+{
+ if (sense.key != UNIT_ATTENTION) {
+ return INT_MAX;
+ }
+ if (sense.asc == 0x29 && sense.ascq == 0x04) {
+ /* DEVICE INTERNAL RESET goes with POWER ON OCCURRED */
+ return 1;
+ } else if (sense.asc == 0x3F && sense.ascq == 0x01) {
+ /* MICROCODE HAS BEEN CHANGED goes with SCSI BUS RESET OCCURRED */
+ return 2;
+ } else if (sense.asc == 0x29 && (sense.ascq == 0x05 || sense.ascq == 0x06)) {
+ /* These two go with "all others". */
+ ;
+ } else if (sense.asc == 0x29 && sense.ascq <= 0x07) {
+ /* POWER ON, RESET OR BUS DEVICE RESET OCCURRED = 0
+ * POWER ON OCCURRED = 1
+ * SCSI BUS RESET OCCURRED = 2
+ * BUS DEVICE RESET FUNCTION OCCURRED = 3
+ * I_T NEXUS LOSS OCCURRED = 7
+ */
+ return sense.ascq;
+ } else if (sense.asc == 0x2F && sense.ascq == 0x01) {
+ /* COMMANDS CLEARED BY POWER LOSS NOTIFICATION */
+ return 8;
+ }
+ return (sense.asc << 8) | sense.ascq;
+}
+
+void scsi_bus_set_ua(SCSIBus *bus, SCSISense sense)
+{
+ int prec1, prec2;
+ if (sense.key != UNIT_ATTENTION) {
+ return;
+ }
+
+ /*
+ * Override a pre-existing unit attention condition, except for a more
+ * important reset condition.
+ */
+ prec1 = scsi_ua_precedence(bus->unit_attention);
+ prec2 = scsi_ua_precedence(sense);
+ if (prec2 < prec1) {
+ bus->unit_attention = sense;
+ }
+}
+
+void scsi_device_set_ua(SCSIDevice *sdev, SCSISense sense)
+{
+ int prec1, prec2;
+ if (sense.key != UNIT_ATTENTION) {
+ return;
+ }
+ trace_scsi_device_set_ua(sdev->id, sdev->lun, sense.key,
+ sense.asc, sense.ascq);
+
+ /*
+ * Override a pre-existing unit attention condition, except for a more
+ * important reset condition.
+ */
+ prec1 = scsi_ua_precedence(sdev->unit_attention);
+ prec2 = scsi_ua_precedence(sense);
+ if (prec2 < prec1) {
+ sdev->unit_attention = sense;
+ }
+}
+
+void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense)
+{
+ SCSIRequest *req;
+
+ aio_context_acquire(blk_get_aio_context(sdev->conf.blk));
+ while (!QTAILQ_EMPTY(&sdev->requests)) {
+ req = QTAILQ_FIRST(&sdev->requests);
+ scsi_req_cancel_async(req, NULL);
+ }
+ blk_drain(sdev->conf.blk);
+ aio_context_release(blk_get_aio_context(sdev->conf.blk));
+ scsi_device_set_ua(sdev, sense);
+}
+
+static char *scsibus_get_dev_path(DeviceState *dev)
+{
+ SCSIDevice *d = SCSI_DEVICE(dev);
+ DeviceState *hba = dev->parent_bus->parent;
+ char *id;
+ char *path;
+
+ id = qdev_get_dev_path(hba);
+ if (id) {
+ path = g_strdup_printf("%s/%d:%d:%d", id, d->channel, d->id, d->lun);
+ } else {
+ path = g_strdup_printf("%d:%d:%d", d->channel, d->id, d->lun);
+ }
+ g_free(id);
+ return path;
+}
+
+static char *scsibus_get_fw_dev_path(DeviceState *dev)
+{
+ SCSIDevice *d = SCSI_DEVICE(dev);
+ return g_strdup_printf("channel@%x/%s@%x,%x", d->channel,
+ qdev_fw_name(dev), d->id, d->lun);
+}
+
+/* SCSI request list. For simplicity, pv points to the whole device */
+
+static int put_scsi_requests(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc)
+{
+ SCSIDevice *s = pv;
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, s->qdev.parent_bus);
+ SCSIRequest *req;
+
+ QTAILQ_FOREACH(req, &s->requests, next) {
+ assert(!req->io_canceled);
+ assert(req->status == -1 && req->host_status == -1);
+ assert(req->enqueued);
+
+ qemu_put_sbyte(f, req->retry ? 1 : 2);
+ qemu_put_buffer(f, req->cmd.buf, sizeof(req->cmd.buf));
+ qemu_put_be32s(f, &req->tag);
+ qemu_put_be32s(f, &req->lun);
+ if (bus->info->save_request) {
+ bus->info->save_request(f, req);
+ }
+ if (req->ops->save_request) {
+ req->ops->save_request(f, req);
+ }
+ }
+ qemu_put_sbyte(f, 0);
+
+ return 0;
+}
+
+static int get_scsi_requests(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field)
+{
+ SCSIDevice *s = pv;
+ SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, s->qdev.parent_bus);
+ int8_t sbyte;
+
+ while ((sbyte = qemu_get_sbyte(f)) > 0) {
+ uint8_t buf[SCSI_CMD_BUF_SIZE];
+ uint32_t tag;
+ uint32_t lun;
+ SCSIRequest *req;
+
+ qemu_get_buffer(f, buf, sizeof(buf));
+ qemu_get_be32s(f, &tag);
+ qemu_get_be32s(f, &lun);
+ /*
+ * A too-short CDB would have been rejected by scsi_req_new, so just use
+ * SCSI_CMD_BUF_SIZE as the CDB length.
+ */
+ req = scsi_req_new(s, tag, lun, buf, sizeof(buf), NULL);
+ req->retry = (sbyte == 1);
+ if (bus->info->load_request) {
+ req->hba_private = bus->info->load_request(f, req);
+ }
+ if (req->ops->load_request) {
+ req->ops->load_request(f, req);
+ }
+
+ /* Just restart it later. */
+ scsi_req_enqueue_internal(req);
+
+ /* At this point, the request will be kept alive by the reference
+ * added by scsi_req_enqueue_internal, so we can release our reference.
+ * The HBA of course will add its own reference in the load_request
+ * callback if it needs to hold on the SCSIRequest.
+ */
+ scsi_req_unref(req);
+ }
+
+ return 0;
+}
+
+static const VMStateInfo vmstate_info_scsi_requests = {
+ .name = "scsi-requests",
+ .get = get_scsi_requests,
+ .put = put_scsi_requests,
+};
+
+static bool scsi_sense_state_needed(void *opaque)
+{
+ SCSIDevice *s = opaque;
+
+ return s->sense_len > SCSI_SENSE_BUF_SIZE_OLD;
+}
+
+static const VMStateDescription vmstate_scsi_sense_state = {
+ .name = "SCSIDevice/sense",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = scsi_sense_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_SUB_ARRAY(sense, SCSIDevice,
+ SCSI_SENSE_BUF_SIZE_OLD,
+ SCSI_SENSE_BUF_SIZE - SCSI_SENSE_BUF_SIZE_OLD),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_scsi_device = {
+ .name = "SCSIDevice",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(unit_attention.key, SCSIDevice),
+ VMSTATE_UINT8(unit_attention.asc, SCSIDevice),
+ VMSTATE_UINT8(unit_attention.ascq, SCSIDevice),
+ VMSTATE_BOOL(sense_is_ua, SCSIDevice),
+ VMSTATE_UINT8_SUB_ARRAY(sense, SCSIDevice, 0, SCSI_SENSE_BUF_SIZE_OLD),
+ VMSTATE_UINT32(sense_len, SCSIDevice),
+ {
+ .name = "requests",
+ .version_id = 0,
+ .field_exists = NULL,
+ .size = 0, /* ouch */
+ .info = &vmstate_info_scsi_requests,
+ .flags = VMS_SINGLE,
+ .offset = 0,
+ },
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_scsi_sense_state,
+ NULL
+ }
+};
+
+static Property scsi_props[] = {
+ DEFINE_PROP_UINT32("channel", SCSIDevice, channel, 0),
+ DEFINE_PROP_UINT32("scsi-id", SCSIDevice, id, -1),
+ DEFINE_PROP_UINT32("lun", SCSIDevice, lun, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scsi_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ set_bit(DEVICE_CATEGORY_STORAGE, k->categories);
+ k->bus_type = TYPE_SCSI_BUS;
+ k->realize = scsi_qdev_realize;
+ k->unrealize = scsi_qdev_unrealize;
+ device_class_set_props(k, scsi_props);
+}
+
+static void scsi_dev_instance_init(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ SCSIDevice *s = SCSI_DEVICE(dev);
+
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", NULL,
+ &s->qdev);
+}
+
+static const TypeInfo scsi_device_type_info = {
+ .name = TYPE_SCSI_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(SCSIDevice),
+ .abstract = true,
+ .class_size = sizeof(SCSIDeviceClass),
+ .class_init = scsi_device_class_init,
+ .instance_init = scsi_dev_instance_init,
+};
+
+static void scsi_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ k->get_dev_path = scsibus_get_dev_path;
+ k->get_fw_dev_path = scsibus_get_fw_dev_path;
+ k->check_address = scsi_bus_check_address;
+ hc->unplug = qdev_simple_device_unplug_cb;
+}
+
+static const TypeInfo scsi_bus_info = {
+ .name = TYPE_SCSI_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(SCSIBus),
+ .class_init = scsi_bus_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void scsi_register_types(void)
+{
+ type_register_static(&scsi_bus_info);
+ type_register_static(&scsi_device_type_info);
+}
+
+type_init(scsi_register_types)
diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c
new file mode 100644
index 00000000..e493c288
--- /dev/null
+++ b/hw/scsi/scsi-disk.c
@@ -0,0 +1,3260 @@
+/*
+ * SCSI Device emulation
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Based on code by Fabrice Bellard
+ *
+ * Written by Paul Brook
+ * Modifications:
+ * 2009-Dec-12 Artyom Tarasenko : implemented stamdard inquiry for the case
+ * when the allocation length of CDB is smaller
+ * than 36.
+ * 2009-Oct-13 Artyom Tarasenko : implemented the block descriptor in the
+ * MODE SENSE response.
+ *
+ * This code is licensed under the LGPL.
+ *
+ * Note that this file only handles the SCSI architecture model and device
+ * commands. Emulation of interface/link layer protocols is handled by
+ * the host adapter emulator.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/hw-version.h"
+#include "qemu/memalign.h"
+#include "hw/scsi/scsi.h"
+#include "migration/qemu-file-types.h"
+#include "migration/vmstate.h"
+#include "hw/scsi/emulation.h"
+#include "scsi/constants.h"
+#include "sysemu/block-backend.h"
+#include "sysemu/blockdev.h"
+#include "hw/block/block.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "sysemu/dma.h"
+#include "sysemu/sysemu.h"
+#include "qemu/cutils.h"
+#include "trace.h"
+#include "qom/object.h"
+
+#ifdef __linux
+#include <scsi/sg.h>
+#endif
+
+#define SCSI_WRITE_SAME_MAX (512 * KiB)
+#define SCSI_DMA_BUF_SIZE (128 * KiB)
+#define SCSI_MAX_INQUIRY_LEN 256
+#define SCSI_MAX_MODE_LEN 256
+
+#define DEFAULT_DISCARD_GRANULARITY (4 * KiB)
+#define DEFAULT_MAX_UNMAP_SIZE (1 * GiB)
+#define DEFAULT_MAX_IO_SIZE INT_MAX /* 2 GB - 1 block */
+
+#define TYPE_SCSI_DISK_BASE "scsi-disk-base"
+
+OBJECT_DECLARE_TYPE(SCSIDiskState, SCSIDiskClass, SCSI_DISK_BASE)
+
+struct SCSIDiskClass {
+ SCSIDeviceClass parent_class;
+ DMAIOFunc *dma_readv;
+ DMAIOFunc *dma_writev;
+ bool (*need_fua_emulation)(SCSICommand *cmd);
+ void (*update_sense)(SCSIRequest *r);
+};
+
+typedef struct SCSIDiskReq {
+ SCSIRequest req;
+ /* Both sector and sector_count are in terms of BDRV_SECTOR_SIZE bytes. */
+ uint64_t sector;
+ uint32_t sector_count;
+ uint32_t buflen;
+ bool started;
+ bool need_fua_emulation;
+ struct iovec iov;
+ QEMUIOVector qiov;
+ BlockAcctCookie acct;
+} SCSIDiskReq;
+
+#define SCSI_DISK_F_REMOVABLE 0
+#define SCSI_DISK_F_DPOFUA 1
+#define SCSI_DISK_F_NO_REMOVABLE_DEVOPS 2
+
+struct SCSIDiskState {
+ SCSIDevice qdev;
+ uint32_t features;
+ bool media_changed;
+ bool media_event;
+ bool eject_request;
+ uint16_t port_index;
+ uint64_t max_unmap_size;
+ uint64_t max_io_size;
+ uint32_t quirks;
+ QEMUBH *bh;
+ char *version;
+ char *serial;
+ char *vendor;
+ char *product;
+ char *device_id;
+ bool tray_open;
+ bool tray_locked;
+ /*
+ * 0x0000 - rotation rate not reported
+ * 0x0001 - non-rotating medium (SSD)
+ * 0x0002-0x0400 - reserved
+ * 0x0401-0xffe - rotations per minute
+ * 0xffff - reserved
+ */
+ uint16_t rotation_rate;
+};
+
+static void scsi_free_request(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ qemu_vfree(r->iov.iov_base);
+}
+
+/* Helper function for command completion with sense. */
+static void scsi_check_condition(SCSIDiskReq *r, SCSISense sense)
+{
+ trace_scsi_disk_check_condition(r->req.tag, sense.key, sense.asc,
+ sense.ascq);
+ scsi_req_build_sense(&r->req, sense);
+ scsi_req_complete(&r->req, CHECK_CONDITION);
+}
+
+static void scsi_init_iovec(SCSIDiskReq *r, size_t size)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ if (!r->iov.iov_base) {
+ r->buflen = size;
+ r->iov.iov_base = blk_blockalign(s->qdev.conf.blk, r->buflen);
+ }
+ r->iov.iov_len = MIN(r->sector_count * BDRV_SECTOR_SIZE, r->buflen);
+ qemu_iovec_init_external(&r->qiov, &r->iov, 1);
+}
+
+static void scsi_disk_save_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ qemu_put_be64s(f, &r->sector);
+ qemu_put_be32s(f, &r->sector_count);
+ qemu_put_be32s(f, &r->buflen);
+ if (r->buflen) {
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ qemu_put_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ } else if (!req->retry) {
+ uint32_t len = r->iov.iov_len;
+ qemu_put_be32s(f, &len);
+ qemu_put_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ }
+ }
+}
+
+static void scsi_disk_load_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ qemu_get_be64s(f, &r->sector);
+ qemu_get_be32s(f, &r->sector_count);
+ qemu_get_be32s(f, &r->buflen);
+ if (r->buflen) {
+ scsi_init_iovec(r, r->buflen);
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ qemu_get_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ } else if (!r->req.retry) {
+ uint32_t len;
+ qemu_get_be32s(f, &len);
+ r->iov.iov_len = len;
+ assert(r->iov.iov_len <= r->buflen);
+ qemu_get_buffer(f, r->iov.iov_base, r->iov.iov_len);
+ }
+ }
+
+ qemu_iovec_init_external(&r->qiov, &r->iov, 1);
+}
+
+/*
+ * scsi_handle_rw_error has two return values. False means that the error
+ * must be ignored, true means that the error has been processed and the
+ * caller should not do anything else for this request. Note that
+ * scsi_handle_rw_error always manages its reference counts, independent
+ * of the return value.
+ */
+static bool scsi_handle_rw_error(SCSIDiskReq *r, int ret, bool acct_failed)
+{
+ bool is_read = (r->req.cmd.mode == SCSI_XFER_FROM_DEV);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ SCSIDiskClass *sdc = (SCSIDiskClass *) object_get_class(OBJECT(s));
+ SCSISense sense = SENSE_CODE(NO_SENSE);
+ int error = 0;
+ bool req_has_sense = false;
+ BlockErrorAction action;
+ int status;
+
+ if (ret < 0) {
+ status = scsi_sense_from_errno(-ret, &sense);
+ error = -ret;
+ } else {
+ /* A passthrough command has completed with nonzero status. */
+ status = ret;
+ if (status == CHECK_CONDITION) {
+ req_has_sense = true;
+ error = scsi_sense_buf_to_errno(r->req.sense, sizeof(r->req.sense));
+ } else {
+ error = EINVAL;
+ }
+ }
+
+ /*
+ * Check whether the error has to be handled by the guest or should
+ * rather follow the rerror=/werror= settings. Guest-handled errors
+ * are usually retried immediately, so do not post them to QMP and
+ * do not account them as failed I/O.
+ */
+ if (req_has_sense &&
+ scsi_sense_buf_is_guest_recoverable(r->req.sense, sizeof(r->req.sense))) {
+ action = BLOCK_ERROR_ACTION_REPORT;
+ acct_failed = false;
+ } else {
+ action = blk_get_error_action(s->qdev.conf.blk, is_read, error);
+ blk_error_action(s->qdev.conf.blk, action, is_read, error);
+ }
+
+ switch (action) {
+ case BLOCK_ERROR_ACTION_REPORT:
+ if (acct_failed) {
+ block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ }
+ if (req_has_sense) {
+ sdc->update_sense(&r->req);
+ } else if (status == CHECK_CONDITION) {
+ scsi_req_build_sense(&r->req, sense);
+ }
+ scsi_req_complete(&r->req, status);
+ return true;
+
+ case BLOCK_ERROR_ACTION_IGNORE:
+ return false;
+
+ case BLOCK_ERROR_ACTION_STOP:
+ scsi_req_retry(&r->req);
+ return true;
+
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static bool scsi_disk_req_check_error(SCSIDiskReq *r, int ret, bool acct_failed)
+{
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ return true;
+ }
+
+ if (ret < 0) {
+ return scsi_handle_rw_error(r, ret, acct_failed);
+ }
+
+ return false;
+}
+
+static void scsi_aio_complete(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+ aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
+ if (scsi_disk_req_check_error(r, ret, true)) {
+ goto done;
+ }
+
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+ scsi_req_unref(&r->req);
+}
+
+static bool scsi_is_cmd_fua(SCSICommand *cmd)
+{
+ switch (cmd->buf[0]) {
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ return (cmd->buf[1] & 8) != 0;
+
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ case WRITE_VERIFY_10:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ return true;
+
+ case READ_6:
+ case WRITE_6:
+ default:
+ return false;
+ }
+}
+
+static void scsi_write_do_fua(SCSIDiskReq *r)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb == NULL);
+ assert(!r->req.io_canceled);
+
+ if (r->need_fua_emulation) {
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_aio_complete, r);
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_dma_complete_noio(SCSIDiskReq *r, int ret)
+{
+ assert(r->req.aiocb == NULL);
+ if (scsi_disk_req_check_error(r, ret, false)) {
+ goto done;
+ }
+
+ r->sector += r->sector_count;
+ r->sector_count = 0;
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ scsi_write_do_fua(r);
+ return;
+ } else {
+ scsi_req_complete(&r->req, GOOD);
+ }
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_dma_complete(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
+ if (ret < 0) {
+ block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ } else {
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ }
+ scsi_dma_complete_noio(r, ret);
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+}
+
+static void scsi_read_complete_noio(SCSIDiskReq *r, int ret)
+{
+ uint32_t n;
+
+ assert(r->req.aiocb == NULL);
+ if (scsi_disk_req_check_error(r, ret, false)) {
+ goto done;
+ }
+
+ n = r->qiov.size / BDRV_SECTOR_SIZE;
+ r->sector += n;
+ r->sector_count -= n;
+ scsi_req_data(&r->req, r->qiov.size);
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_read_complete(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
+ if (ret < 0) {
+ block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ } else {
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ trace_scsi_disk_read_complete(r->req.tag, r->qiov.size);
+ }
+ scsi_read_complete_noio(r, ret);
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+}
+
+/* Actually issue a read to the block device. */
+static void scsi_do_read(SCSIDiskReq *r, int ret)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ SCSIDiskClass *sdc = (SCSIDiskClass *) object_get_class(OBJECT(s));
+
+ assert (r->req.aiocb == NULL);
+ if (scsi_disk_req_check_error(r, ret, false)) {
+ goto done;
+ }
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+
+ if (r->req.sg) {
+ dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_READ);
+ r->req.residual -= r->req.sg->size;
+ r->req.aiocb = dma_blk_io(blk_get_aio_context(s->qdev.conf.blk),
+ r->req.sg, r->sector << BDRV_SECTOR_BITS,
+ BDRV_SECTOR_SIZE,
+ sdc->dma_readv, r, scsi_dma_complete, r,
+ DMA_DIRECTION_FROM_DEVICE);
+ } else {
+ scsi_init_iovec(r, SCSI_DMA_BUF_SIZE);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ r->qiov.size, BLOCK_ACCT_READ);
+ r->req.aiocb = sdc->dma_readv(r->sector << BDRV_SECTOR_BITS, &r->qiov,
+ scsi_read_complete, r, r);
+ }
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_do_read_cb(void *opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert (r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
+ if (ret < 0) {
+ block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ } else {
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ }
+ scsi_do_read(opaque, ret);
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+}
+
+/* Read more data from scsi device into buffer. */
+static void scsi_read_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ bool first;
+
+ trace_scsi_disk_read_data_count(r->sector_count);
+ if (r->sector_count == 0) {
+ /* This also clears the sense buffer for REQUEST SENSE. */
+ scsi_req_complete(&r->req, GOOD);
+ return;
+ }
+
+ /* No data transfer may already be in progress */
+ assert(r->req.aiocb == NULL);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ trace_scsi_disk_read_data_invalid();
+ scsi_read_complete_noio(r, -EINVAL);
+ return;
+ }
+
+ if (!blk_is_available(req->dev->conf.blk)) {
+ scsi_read_complete_noio(r, -ENOMEDIUM);
+ return;
+ }
+
+ first = !r->started;
+ r->started = true;
+ if (first && r->need_fua_emulation) {
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_do_read_cb, r);
+ } else {
+ scsi_do_read(r, 0);
+ }
+}
+
+static void scsi_write_complete_noio(SCSIDiskReq *r, int ret)
+{
+ uint32_t n;
+
+ assert (r->req.aiocb == NULL);
+ if (scsi_disk_req_check_error(r, ret, false)) {
+ goto done;
+ }
+
+ n = r->qiov.size / BDRV_SECTOR_SIZE;
+ r->sector += n;
+ r->sector_count -= n;
+ if (r->sector_count == 0) {
+ scsi_write_do_fua(r);
+ return;
+ } else {
+ scsi_init_iovec(r, SCSI_DMA_BUF_SIZE);
+ trace_scsi_disk_write_complete_noio(r->req.tag, r->qiov.size);
+ scsi_req_data(&r->req, r->qiov.size);
+ }
+
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_write_complete(void * opaque, int ret)
+{
+ SCSIDiskReq *r = (SCSIDiskReq *)opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert (r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
+ if (ret < 0) {
+ block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ } else {
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ }
+ scsi_write_complete_noio(r, ret);
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+}
+
+static void scsi_write_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ SCSIDiskClass *sdc = (SCSIDiskClass *) object_get_class(OBJECT(s));
+
+ /* No data transfer may already be in progress */
+ assert(r->req.aiocb == NULL);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ if (r->req.cmd.mode != SCSI_XFER_TO_DEV) {
+ trace_scsi_disk_write_data_invalid();
+ scsi_write_complete_noio(r, -EINVAL);
+ return;
+ }
+
+ if (!r->req.sg && !r->qiov.size) {
+ /* Called for the first time. Ask the driver to send us more data. */
+ r->started = true;
+ scsi_write_complete_noio(r, 0);
+ return;
+ }
+ if (!blk_is_available(req->dev->conf.blk)) {
+ scsi_write_complete_noio(r, -ENOMEDIUM);
+ return;
+ }
+
+ if (r->req.cmd.buf[0] == VERIFY_10 || r->req.cmd.buf[0] == VERIFY_12 ||
+ r->req.cmd.buf[0] == VERIFY_16) {
+ if (r->req.sg) {
+ scsi_dma_complete_noio(r, 0);
+ } else {
+ scsi_write_complete_noio(r, 0);
+ }
+ return;
+ }
+
+ if (r->req.sg) {
+ dma_acct_start(s->qdev.conf.blk, &r->acct, r->req.sg, BLOCK_ACCT_WRITE);
+ r->req.residual -= r->req.sg->size;
+ r->req.aiocb = dma_blk_io(blk_get_aio_context(s->qdev.conf.blk),
+ r->req.sg, r->sector << BDRV_SECTOR_BITS,
+ BDRV_SECTOR_SIZE,
+ sdc->dma_writev, r, scsi_dma_complete, r,
+ DMA_DIRECTION_TO_DEVICE);
+ } else {
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ r->qiov.size, BLOCK_ACCT_WRITE);
+ r->req.aiocb = sdc->dma_writev(r->sector << BDRV_SECTOR_BITS, &r->qiov,
+ scsi_write_complete, r, r);
+ }
+}
+
+/* Return a pointer to the data buffer. */
+static uint8_t *scsi_get_buf(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ return (uint8_t *)r->iov.iov_base;
+}
+
+static int scsi_disk_emulate_vpd_page(SCSIRequest *req, uint8_t *outbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ uint8_t page_code = req->cmd.buf[2];
+ int start, buflen = 0;
+
+ outbuf[buflen++] = s->qdev.type & 0x1f;
+ outbuf[buflen++] = page_code;
+ outbuf[buflen++] = 0x00;
+ outbuf[buflen++] = 0x00;
+ start = buflen;
+
+ switch (page_code) {
+ case 0x00: /* Supported page codes, mandatory */
+ {
+ trace_scsi_disk_emulate_vpd_page_00(req->cmd.xfer);
+ outbuf[buflen++] = 0x00; /* list of supported pages (this page) */
+ if (s->serial) {
+ outbuf[buflen++] = 0x80; /* unit serial number */
+ }
+ outbuf[buflen++] = 0x83; /* device identification */
+ if (s->qdev.type == TYPE_DISK) {
+ outbuf[buflen++] = 0xb0; /* block limits */
+ outbuf[buflen++] = 0xb1; /* block device characteristics */
+ outbuf[buflen++] = 0xb2; /* thin provisioning */
+ }
+ break;
+ }
+ case 0x80: /* Device serial number, optional */
+ {
+ int l;
+
+ if (!s->serial) {
+ trace_scsi_disk_emulate_vpd_page_80_not_supported();
+ return -1;
+ }
+
+ l = strlen(s->serial);
+ if (l > 36) {
+ l = 36;
+ }
+
+ trace_scsi_disk_emulate_vpd_page_80(req->cmd.xfer);
+ memcpy(outbuf + buflen, s->serial, l);
+ buflen += l;
+ break;
+ }
+
+ case 0x83: /* Device identification page, mandatory */
+ {
+ int id_len = s->device_id ? MIN(strlen(s->device_id), 255 - 8) : 0;
+
+ trace_scsi_disk_emulate_vpd_page_83(req->cmd.xfer);
+
+ if (id_len) {
+ outbuf[buflen++] = 0x2; /* ASCII */
+ outbuf[buflen++] = 0; /* not officially assigned */
+ outbuf[buflen++] = 0; /* reserved */
+ outbuf[buflen++] = id_len; /* length of data following */
+ memcpy(outbuf + buflen, s->device_id, id_len);
+ buflen += id_len;
+ }
+
+ if (s->qdev.wwn) {
+ outbuf[buflen++] = 0x1; /* Binary */
+ outbuf[buflen++] = 0x3; /* NAA */
+ outbuf[buflen++] = 0; /* reserved */
+ outbuf[buflen++] = 8;
+ stq_be_p(&outbuf[buflen], s->qdev.wwn);
+ buflen += 8;
+ }
+
+ if (s->qdev.port_wwn) {
+ outbuf[buflen++] = 0x61; /* SAS / Binary */
+ outbuf[buflen++] = 0x93; /* PIV / Target port / NAA */
+ outbuf[buflen++] = 0; /* reserved */
+ outbuf[buflen++] = 8;
+ stq_be_p(&outbuf[buflen], s->qdev.port_wwn);
+ buflen += 8;
+ }
+
+ if (s->port_index) {
+ outbuf[buflen++] = 0x61; /* SAS / Binary */
+
+ /* PIV/Target port/relative target port */
+ outbuf[buflen++] = 0x94;
+
+ outbuf[buflen++] = 0; /* reserved */
+ outbuf[buflen++] = 4;
+ stw_be_p(&outbuf[buflen + 2], s->port_index);
+ buflen += 4;
+ }
+ break;
+ }
+ case 0xb0: /* block limits */
+ {
+ SCSIBlockLimits bl = {};
+
+ if (s->qdev.type == TYPE_ROM) {
+ trace_scsi_disk_emulate_vpd_page_b0_not_supported();
+ return -1;
+ }
+ bl.wsnz = 1;
+ bl.unmap_sectors =
+ s->qdev.conf.discard_granularity / s->qdev.blocksize;
+ bl.min_io_size =
+ s->qdev.conf.min_io_size / s->qdev.blocksize;
+ bl.opt_io_size =
+ s->qdev.conf.opt_io_size / s->qdev.blocksize;
+ bl.max_unmap_sectors =
+ s->max_unmap_size / s->qdev.blocksize;
+ bl.max_io_sectors =
+ s->max_io_size / s->qdev.blocksize;
+ /* 255 descriptors fit in 4 KiB with an 8-byte header */
+ bl.max_unmap_descr = 255;
+
+ if (s->qdev.type == TYPE_DISK) {
+ int max_transfer_blk = blk_get_max_transfer(s->qdev.conf.blk);
+ int max_io_sectors_blk =
+ max_transfer_blk / s->qdev.blocksize;
+
+ bl.max_io_sectors =
+ MIN_NON_ZERO(max_io_sectors_blk, bl.max_io_sectors);
+ }
+ buflen += scsi_emulate_block_limits(outbuf + buflen, &bl);
+ break;
+ }
+ case 0xb1: /* block device characteristics */
+ {
+ buflen = 0x40;
+ outbuf[4] = (s->rotation_rate >> 8) & 0xff;
+ outbuf[5] = s->rotation_rate & 0xff;
+ outbuf[6] = 0; /* PRODUCT TYPE */
+ outbuf[7] = 0; /* WABEREQ | WACEREQ | NOMINAL FORM FACTOR */
+ outbuf[8] = 0; /* VBULS */
+ break;
+ }
+ case 0xb2: /* thin provisioning */
+ {
+ buflen = 8;
+ outbuf[4] = 0;
+ outbuf[5] = 0xe0; /* unmap & write_same 10/16 all supported */
+ outbuf[6] = s->qdev.conf.discard_granularity ? 2 : 1;
+ outbuf[7] = 0;
+ break;
+ }
+ default:
+ return -1;
+ }
+ /* done with EVPD */
+ assert(buflen - start <= 255);
+ outbuf[start - 1] = buflen - start;
+ return buflen;
+}
+
+static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ int buflen = 0;
+
+ if (req->cmd.buf[1] & 0x1) {
+ /* Vital product data */
+ return scsi_disk_emulate_vpd_page(req, outbuf);
+ }
+
+ /* Standard INQUIRY data */
+ if (req->cmd.buf[2] != 0) {
+ return -1;
+ }
+
+ /* PAGE CODE == 0 */
+ buflen = req->cmd.xfer;
+ if (buflen > SCSI_MAX_INQUIRY_LEN) {
+ buflen = SCSI_MAX_INQUIRY_LEN;
+ }
+
+ outbuf[0] = s->qdev.type & 0x1f;
+ outbuf[1] = (s->features & (1 << SCSI_DISK_F_REMOVABLE)) ? 0x80 : 0;
+
+ strpadcpy((char *) &outbuf[16], 16, s->product, ' ');
+ strpadcpy((char *) &outbuf[8], 8, s->vendor, ' ');
+
+ memset(&outbuf[32], 0, 4);
+ memcpy(&outbuf[32], s->version, MIN(4, strlen(s->version)));
+ /*
+ * We claim conformance to SPC-3, which is required for guests
+ * to ask for modern features like READ CAPACITY(16) or the
+ * block characteristics VPD page by default. Not all of SPC-3
+ * is actually implemented, but we're good enough.
+ */
+ outbuf[2] = s->qdev.default_scsi_version;
+ outbuf[3] = 2 | 0x10; /* Format 2, HiSup */
+
+ if (buflen > 36) {
+ outbuf[4] = buflen - 5; /* Additional Length = (Len - 1) - 4 */
+ } else {
+ /* If the allocation length of CDB is too small,
+ the additional length is not adjusted */
+ outbuf[4] = 36 - 5;
+ }
+
+ /* Sync data transfer and TCQ. */
+ outbuf[7] = 0x10 | (req->bus->info->tcq ? 0x02 : 0);
+ return buflen;
+}
+
+static inline bool media_is_dvd(SCSIDiskState *s)
+{
+ uint64_t nb_sectors;
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ if (!blk_is_available(s->qdev.conf.blk)) {
+ return false;
+ }
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ return nb_sectors > CD_MAX_SECTORS;
+}
+
+static inline bool media_is_cd(SCSIDiskState *s)
+{
+ uint64_t nb_sectors;
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ if (!blk_is_available(s->qdev.conf.blk)) {
+ return false;
+ }
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ return nb_sectors <= CD_MAX_SECTORS;
+}
+
+static int scsi_read_disc_information(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ uint8_t type = r->req.cmd.buf[1] & 7;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+
+ /* Types 1/2 are only defined for Blu-Ray. */
+ if (type != 0) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return -1;
+ }
+
+ memset(outbuf, 0, 34);
+ outbuf[1] = 32;
+ outbuf[2] = 0xe; /* last session complete, disc finalized */
+ outbuf[3] = 1; /* first track on disc */
+ outbuf[4] = 1; /* # of sessions */
+ outbuf[5] = 1; /* first track of last session */
+ outbuf[6] = 1; /* last track of last session */
+ outbuf[7] = 0x20; /* unrestricted use */
+ outbuf[8] = 0x00; /* CD-ROM or DVD-ROM */
+ /* 9-10-11: most significant byte corresponding bytes 4-5-6 */
+ /* 12-23: not meaningful for CD-ROM or DVD-ROM */
+ /* 24-31: disc bar code */
+ /* 32: disc application code */
+ /* 33: number of OPC tables */
+
+ return 34;
+}
+
+static int scsi_read_dvd_structure(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ static const int rds_caps_size[5] = {
+ [0] = 2048 + 4,
+ [1] = 4 + 4,
+ [3] = 188 + 4,
+ [4] = 2048 + 4,
+ };
+
+ uint8_t media = r->req.cmd.buf[1];
+ uint8_t layer = r->req.cmd.buf[6];
+ uint8_t format = r->req.cmd.buf[7];
+ int size = -1;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ if (media != 0) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return -1;
+ }
+
+ if (format != 0xff) {
+ if (!blk_is_available(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ return -1;
+ }
+ if (media_is_cd(s)) {
+ scsi_check_condition(r, SENSE_CODE(INCOMPATIBLE_FORMAT));
+ return -1;
+ }
+ if (format >= ARRAY_SIZE(rds_caps_size)) {
+ return -1;
+ }
+ size = rds_caps_size[format];
+ memset(outbuf, 0, size);
+ }
+
+ switch (format) {
+ case 0x00: {
+ /* Physical format information */
+ uint64_t nb_sectors;
+ if (layer != 0) {
+ goto fail;
+ }
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+
+ outbuf[4] = 1; /* DVD-ROM, part version 1 */
+ outbuf[5] = 0xf; /* 120mm disc, minimum rate unspecified */
+ outbuf[6] = 1; /* one layer, read-only (per MMC-2 spec) */
+ outbuf[7] = 0; /* default densities */
+
+ stl_be_p(&outbuf[12], (nb_sectors >> 2) - 1); /* end sector */
+ stl_be_p(&outbuf[16], (nb_sectors >> 2) - 1); /* l0 end sector */
+ break;
+ }
+
+ case 0x01: /* DVD copyright information, all zeros */
+ break;
+
+ case 0x03: /* BCA information - invalid field for no BCA info */
+ return -1;
+
+ case 0x04: /* DVD disc manufacturing information, all zeros */
+ break;
+
+ case 0xff: { /* List capabilities */
+ int i;
+ size = 4;
+ for (i = 0; i < ARRAY_SIZE(rds_caps_size); i++) {
+ if (!rds_caps_size[i]) {
+ continue;
+ }
+ outbuf[size] = i;
+ outbuf[size + 1] = 0x40; /* Not writable, readable */
+ stw_be_p(&outbuf[size + 2], rds_caps_size[i]);
+ size += 4;
+ }
+ break;
+ }
+
+ default:
+ return -1;
+ }
+
+ /* Size of buffer, not including 2 byte size field */
+ stw_be_p(outbuf, size - 2);
+ return size;
+
+fail:
+ return -1;
+}
+
+static int scsi_event_status_media(SCSIDiskState *s, uint8_t *outbuf)
+{
+ uint8_t event_code, media_status;
+
+ media_status = 0;
+ if (s->tray_open) {
+ media_status = MS_TRAY_OPEN;
+ } else if (blk_is_inserted(s->qdev.conf.blk)) {
+ media_status = MS_MEDIA_PRESENT;
+ }
+
+ /* Event notification descriptor */
+ event_code = MEC_NO_CHANGE;
+ if (media_status != MS_TRAY_OPEN) {
+ if (s->media_event) {
+ event_code = MEC_NEW_MEDIA;
+ s->media_event = false;
+ } else if (s->eject_request) {
+ event_code = MEC_EJECT_REQUESTED;
+ s->eject_request = false;
+ }
+ }
+
+ outbuf[0] = event_code;
+ outbuf[1] = media_status;
+
+ /* These fields are reserved, just clear them. */
+ outbuf[2] = 0;
+ outbuf[3] = 0;
+ return 4;
+}
+
+static int scsi_get_event_status_notification(SCSIDiskState *s, SCSIDiskReq *r,
+ uint8_t *outbuf)
+{
+ int size;
+ uint8_t *buf = r->req.cmd.buf;
+ uint8_t notification_class_request = buf[4];
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ if ((buf[1] & 1) == 0) {
+ /* asynchronous */
+ return -1;
+ }
+
+ size = 4;
+ outbuf[0] = outbuf[1] = 0;
+ outbuf[3] = 1 << GESN_MEDIA; /* supported events */
+ if (notification_class_request & (1 << GESN_MEDIA)) {
+ outbuf[2] = GESN_MEDIA;
+ size += scsi_event_status_media(s, &outbuf[size]);
+ } else {
+ outbuf[2] = 0x80;
+ }
+ stw_be_p(outbuf, size - 4);
+ return size;
+}
+
+static int scsi_get_configuration(SCSIDiskState *s, uint8_t *outbuf)
+{
+ int current;
+
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+
+ if (media_is_dvd(s)) {
+ current = MMC_PROFILE_DVD_ROM;
+ } else if (media_is_cd(s)) {
+ current = MMC_PROFILE_CD_ROM;
+ } else {
+ current = MMC_PROFILE_NONE;
+ }
+
+ memset(outbuf, 0, 40);
+ stl_be_p(&outbuf[0], 36); /* Bytes after the data length field */
+ stw_be_p(&outbuf[6], current);
+ /* outbuf[8] - outbuf[19]: Feature 0 - Profile list */
+ outbuf[10] = 0x03; /* persistent, current */
+ outbuf[11] = 8; /* two profiles */
+ stw_be_p(&outbuf[12], MMC_PROFILE_DVD_ROM);
+ outbuf[14] = (current == MMC_PROFILE_DVD_ROM);
+ stw_be_p(&outbuf[16], MMC_PROFILE_CD_ROM);
+ outbuf[18] = (current == MMC_PROFILE_CD_ROM);
+ /* outbuf[20] - outbuf[31]: Feature 1 - Core feature */
+ stw_be_p(&outbuf[20], 1);
+ outbuf[22] = 0x08 | 0x03; /* version 2, persistent, current */
+ outbuf[23] = 8;
+ stl_be_p(&outbuf[24], 1); /* SCSI */
+ outbuf[28] = 1; /* DBE = 1, mandatory */
+ /* outbuf[32] - outbuf[39]: Feature 3 - Removable media feature */
+ stw_be_p(&outbuf[32], 3);
+ outbuf[34] = 0x08 | 0x03; /* version 2, persistent, current */
+ outbuf[35] = 4;
+ outbuf[36] = 0x39; /* tray, load=1, eject=1, unlocked at powerup, lock=1 */
+ /* TODO: Random readable, CD read, DVD read, drive serial number,
+ power management */
+ return 40;
+}
+
+static int scsi_emulate_mechanism_status(SCSIDiskState *s, uint8_t *outbuf)
+{
+ if (s->qdev.type != TYPE_ROM) {
+ return -1;
+ }
+ memset(outbuf, 0, 8);
+ outbuf[5] = 1; /* CD-ROM */
+ return 8;
+}
+
+static int mode_sense_page(SCSIDiskState *s, int page, uint8_t **p_outbuf,
+ int page_control)
+{
+ static const int mode_sense_valid[0x3f] = {
+ [MODE_PAGE_VENDOR_SPECIFIC] = (1 << TYPE_DISK) | (1 << TYPE_ROM),
+ [MODE_PAGE_HD_GEOMETRY] = (1 << TYPE_DISK),
+ [MODE_PAGE_FLEXIBLE_DISK_GEOMETRY] = (1 << TYPE_DISK),
+ [MODE_PAGE_CACHING] = (1 << TYPE_DISK) | (1 << TYPE_ROM),
+ [MODE_PAGE_R_W_ERROR] = (1 << TYPE_DISK) | (1 << TYPE_ROM),
+ [MODE_PAGE_AUDIO_CTL] = (1 << TYPE_ROM),
+ [MODE_PAGE_CAPABILITIES] = (1 << TYPE_ROM),
+ [MODE_PAGE_APPLE_VENDOR] = (1 << TYPE_ROM),
+ };
+
+ uint8_t *p = *p_outbuf + 2;
+ int length;
+
+ assert(page < ARRAY_SIZE(mode_sense_valid));
+ if ((mode_sense_valid[page] & (1 << s->qdev.type)) == 0) {
+ return -1;
+ }
+
+ /*
+ * If Changeable Values are requested, a mask denoting those mode parameters
+ * that are changeable shall be returned. As we currently don't support
+ * parameter changes via MODE_SELECT all bits are returned set to zero.
+ * The buffer was already menset to zero by the caller of this function.
+ *
+ * The offsets here are off by two compared to the descriptions in the
+ * SCSI specs, because those include a 2-byte header. This is unfortunate,
+ * but it is done so that offsets are consistent within our implementation
+ * of MODE SENSE and MODE SELECT. MODE SELECT has to deal with both
+ * 2-byte and 4-byte headers.
+ */
+ switch (page) {
+ case MODE_PAGE_HD_GEOMETRY:
+ length = 0x16;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+ /* if a geometry hint is available, use it */
+ p[0] = (s->qdev.conf.cyls >> 16) & 0xff;
+ p[1] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[2] = s->qdev.conf.cyls & 0xff;
+ p[3] = s->qdev.conf.heads & 0xff;
+ /* Write precomp start cylinder, disabled */
+ p[4] = (s->qdev.conf.cyls >> 16) & 0xff;
+ p[5] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[6] = s->qdev.conf.cyls & 0xff;
+ /* Reduced current start cylinder, disabled */
+ p[7] = (s->qdev.conf.cyls >> 16) & 0xff;
+ p[8] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[9] = s->qdev.conf.cyls & 0xff;
+ /* Device step rate [ns], 200ns */
+ p[10] = 0;
+ p[11] = 200;
+ /* Landing zone cylinder */
+ p[12] = 0xff;
+ p[13] = 0xff;
+ p[14] = 0xff;
+ /* Medium rotation rate [rpm], 5400 rpm */
+ p[18] = (5400 >> 8) & 0xff;
+ p[19] = 5400 & 0xff;
+ break;
+
+ case MODE_PAGE_FLEXIBLE_DISK_GEOMETRY:
+ length = 0x1e;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+ /* Transfer rate [kbit/s], 5Mbit/s */
+ p[0] = 5000 >> 8;
+ p[1] = 5000 & 0xff;
+ /* if a geometry hint is available, use it */
+ p[2] = s->qdev.conf.heads & 0xff;
+ p[3] = s->qdev.conf.secs & 0xff;
+ p[4] = s->qdev.blocksize >> 8;
+ p[6] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[7] = s->qdev.conf.cyls & 0xff;
+ /* Write precomp start cylinder, disabled */
+ p[8] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[9] = s->qdev.conf.cyls & 0xff;
+ /* Reduced current start cylinder, disabled */
+ p[10] = (s->qdev.conf.cyls >> 8) & 0xff;
+ p[11] = s->qdev.conf.cyls & 0xff;
+ /* Device step rate [100us], 100us */
+ p[12] = 0;
+ p[13] = 1;
+ /* Device step pulse width [us], 1us */
+ p[14] = 1;
+ /* Device head settle delay [100us], 100us */
+ p[15] = 0;
+ p[16] = 1;
+ /* Motor on delay [0.1s], 0.1s */
+ p[17] = 1;
+ /* Motor off delay [0.1s], 0.1s */
+ p[18] = 1;
+ /* Medium rotation rate [rpm], 5400 rpm */
+ p[26] = (5400 >> 8) & 0xff;
+ p[27] = 5400 & 0xff;
+ break;
+
+ case MODE_PAGE_CACHING:
+ length = 0x12;
+ if (page_control == 1 || /* Changeable Values */
+ blk_enable_write_cache(s->qdev.conf.blk)) {
+ p[0] = 4; /* WCE */
+ }
+ break;
+
+ case MODE_PAGE_R_W_ERROR:
+ length = 10;
+ if (page_control == 1) { /* Changeable Values */
+ if (s->qdev.type == TYPE_ROM) {
+ /* Automatic Write Reallocation Enabled */
+ p[0] = 0x80;
+ }
+ break;
+ }
+ p[0] = 0x80; /* Automatic Write Reallocation Enabled */
+ if (s->qdev.type == TYPE_ROM) {
+ p[1] = 0x20; /* Read Retry Count */
+ }
+ break;
+
+ case MODE_PAGE_AUDIO_CTL:
+ length = 14;
+ break;
+
+ case MODE_PAGE_CAPABILITIES:
+ length = 0x14;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+
+ p[0] = 0x3b; /* CD-R & CD-RW read */
+ p[1] = 0; /* Writing not supported */
+ p[2] = 0x7f; /* Audio, composite, digital out,
+ mode 2 form 1&2, multi session */
+ p[3] = 0xff; /* CD DA, DA accurate, RW supported,
+ RW corrected, C2 errors, ISRC,
+ UPC, Bar code */
+ p[4] = 0x2d | (s->tray_locked ? 2 : 0);
+ /* Locking supported, jumper present, eject, tray */
+ p[5] = 0; /* no volume & mute control, no
+ changer */
+ p[6] = (50 * 176) >> 8; /* 50x read speed */
+ p[7] = (50 * 176) & 0xff;
+ p[8] = 2 >> 8; /* Two volume levels */
+ p[9] = 2 & 0xff;
+ p[10] = 2048 >> 8; /* 2M buffer */
+ p[11] = 2048 & 0xff;
+ p[12] = (16 * 176) >> 8; /* 16x read speed current */
+ p[13] = (16 * 176) & 0xff;
+ p[16] = (16 * 176) >> 8; /* 16x write speed */
+ p[17] = (16 * 176) & 0xff;
+ p[18] = (16 * 176) >> 8; /* 16x write speed current */
+ p[19] = (16 * 176) & 0xff;
+ break;
+
+ case MODE_PAGE_APPLE_VENDOR:
+ if (s->quirks & (1 << SCSI_DISK_QUIRK_MODE_PAGE_APPLE_VENDOR)) {
+ length = 0x1e;
+ if (page_control == 1) { /* Changeable Values */
+ break;
+ }
+
+ memset(p, 0, length);
+ strcpy((char *)p + 8, "APPLE COMPUTER, INC ");
+ break;
+ } else {
+ return -1;
+ }
+
+ case MODE_PAGE_VENDOR_SPECIFIC:
+ if (s->qdev.type == TYPE_DISK && (s->quirks &
+ (1 << SCSI_DISK_QUIRK_MODE_PAGE_VENDOR_SPECIFIC_APPLE))) {
+ length = 0x2;
+ if (page_control == 1) { /* Changeable Values */
+ p[0] = 0xff;
+ p[1] = 0xff;
+ break;
+ }
+ p[0] = 0;
+ p[1] = 0;
+ break;
+ } else {
+ return -1;
+ }
+
+ default:
+ return -1;
+ }
+
+ assert(length < 256);
+ (*p_outbuf)[0] = page;
+ (*p_outbuf)[1] = length;
+ *p_outbuf += length + 2;
+ return length + 2;
+}
+
+static int scsi_disk_emulate_mode_sense(SCSIDiskReq *r, uint8_t *outbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint64_t nb_sectors;
+ bool dbd;
+ int page, buflen, ret, page_control;
+ uint8_t *p;
+ uint8_t dev_specific_param;
+
+ dbd = (r->req.cmd.buf[1] & 0x8) != 0;
+ page = r->req.cmd.buf[2] & 0x3f;
+ page_control = (r->req.cmd.buf[2] & 0xc0) >> 6;
+
+ trace_scsi_disk_emulate_mode_sense((r->req.cmd.buf[0] == MODE_SENSE) ? 6 :
+ 10, page, r->req.cmd.xfer, page_control);
+ memset(outbuf, 0, r->req.cmd.xfer);
+ p = outbuf;
+
+ if (s->qdev.type == TYPE_DISK) {
+ dev_specific_param = s->features & (1 << SCSI_DISK_F_DPOFUA) ? 0x10 : 0;
+ if (!blk_is_writable(s->qdev.conf.blk)) {
+ dev_specific_param |= 0x80; /* Readonly. */
+ }
+ } else {
+ if (s->quirks & (1 << SCSI_DISK_QUIRK_MODE_SENSE_ROM_USE_DBD)) {
+ /* Use DBD from the request... */
+ dev_specific_param = 0x00;
+
+ /*
+ * ... unless we receive a request for MODE_PAGE_APPLE_VENDOR
+ * which should never return a block descriptor even though DBD is
+ * not set, otherwise CDROM detection fails in MacOS
+ */
+ if (s->quirks & (1 << SCSI_DISK_QUIRK_MODE_PAGE_APPLE_VENDOR) &&
+ page == MODE_PAGE_APPLE_VENDOR) {
+ dbd = true;
+ }
+ } else {
+ /*
+ * MMC prescribes that CD/DVD drives have no block descriptors,
+ * and defines no device-specific parameter.
+ */
+ dev_specific_param = 0x00;
+ dbd = true;
+ }
+ }
+
+ if (r->req.cmd.buf[0] == MODE_SENSE) {
+ p[1] = 0; /* Default media type. */
+ p[2] = dev_specific_param;
+ p[3] = 0; /* Block descriptor length. */
+ p += 4;
+ } else { /* MODE_SENSE_10 */
+ p[2] = 0; /* Default media type. */
+ p[3] = dev_specific_param;
+ p[6] = p[7] = 0; /* Block descriptor length. */
+ p += 8;
+ }
+
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ if (!dbd && nb_sectors) {
+ if (r->req.cmd.buf[0] == MODE_SENSE) {
+ outbuf[3] = 8; /* Block descriptor length */
+ } else { /* MODE_SENSE_10 */
+ outbuf[7] = 8; /* Block descriptor length */
+ }
+ nb_sectors /= (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ if (nb_sectors > 0xffffff) {
+ nb_sectors = 0;
+ }
+ p[0] = 0; /* media density code */
+ p[1] = (nb_sectors >> 16) & 0xff;
+ p[2] = (nb_sectors >> 8) & 0xff;
+ p[3] = nb_sectors & 0xff;
+ p[4] = 0; /* reserved */
+ p[5] = 0; /* bytes 5-7 are the sector size in bytes */
+ p[6] = s->qdev.blocksize >> 8;
+ p[7] = 0;
+ p += 8;
+ }
+
+ if (page_control == 3) {
+ /* Saved Values */
+ scsi_check_condition(r, SENSE_CODE(SAVING_PARAMS_NOT_SUPPORTED));
+ return -1;
+ }
+
+ if (page == 0x3f) {
+ for (page = 0; page <= 0x3e; page++) {
+ mode_sense_page(s, page, &p, page_control);
+ }
+ } else {
+ ret = mode_sense_page(s, page, &p, page_control);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ buflen = p - outbuf;
+ /*
+ * The mode data length field specifies the length in bytes of the
+ * following data that is available to be transferred. The mode data
+ * length does not include itself.
+ */
+ if (r->req.cmd.buf[0] == MODE_SENSE) {
+ outbuf[0] = buflen - 1;
+ } else { /* MODE_SENSE_10 */
+ outbuf[0] = ((buflen - 2) >> 8) & 0xff;
+ outbuf[1] = (buflen - 2) & 0xff;
+ }
+ return buflen;
+}
+
+static int scsi_disk_emulate_read_toc(SCSIRequest *req, uint8_t *outbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ int start_track, format, msf, toclen;
+ uint64_t nb_sectors;
+
+ msf = req->cmd.buf[1] & 2;
+ format = req->cmd.buf[2] & 0xf;
+ start_track = req->cmd.buf[6];
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ trace_scsi_disk_emulate_read_toc(start_track, format, msf >> 1);
+ nb_sectors /= s->qdev.blocksize / BDRV_SECTOR_SIZE;
+ switch (format) {
+ case 0:
+ toclen = cdrom_read_toc(nb_sectors, outbuf, msf, start_track);
+ break;
+ case 1:
+ /* multi session : only a single session defined */
+ toclen = 12;
+ memset(outbuf, 0, 12);
+ outbuf[1] = 0x0a;
+ outbuf[2] = 0x01;
+ outbuf[3] = 0x01;
+ break;
+ case 2:
+ toclen = cdrom_read_toc_raw(nb_sectors, outbuf, msf, start_track);
+ break;
+ default:
+ return -1;
+ }
+ return toclen;
+}
+
+static int scsi_disk_emulate_start_stop(SCSIDiskReq *r)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ bool start = req->cmd.buf[4] & 1;
+ bool loej = req->cmd.buf[4] & 2; /* load on start, eject on !start */
+ int pwrcnd = req->cmd.buf[4] & 0xf0;
+
+ if (pwrcnd) {
+ /* eject/load only happens for power condition == 0 */
+ return 0;
+ }
+
+ if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) && loej) {
+ if (!start && !s->tray_open && s->tray_locked) {
+ scsi_check_condition(r,
+ blk_is_inserted(s->qdev.conf.blk)
+ ? SENSE_CODE(ILLEGAL_REQ_REMOVAL_PREVENTED)
+ : SENSE_CODE(NOT_READY_REMOVAL_PREVENTED));
+ return -1;
+ }
+
+ if (s->tray_open != !start) {
+ blk_eject(s->qdev.conf.blk, !start);
+ s->tray_open = !start;
+ }
+ }
+ return 0;
+}
+
+static void scsi_disk_emulate_read_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ int buflen = r->iov.iov_len;
+
+ if (buflen) {
+ trace_scsi_disk_emulate_read_data(buflen);
+ r->iov.iov_len = 0;
+ r->started = true;
+ scsi_req_data(&r->req, buflen);
+ return;
+ }
+
+ /* This also clears the sense buffer for REQUEST SENSE. */
+ scsi_req_complete(&r->req, GOOD);
+}
+
+static int scsi_disk_check_mode_select(SCSIDiskState *s, int page,
+ uint8_t *inbuf, int inlen)
+{
+ uint8_t mode_current[SCSI_MAX_MODE_LEN];
+ uint8_t mode_changeable[SCSI_MAX_MODE_LEN];
+ uint8_t *p;
+ int len, expected_len, changeable_len, i;
+
+ /* The input buffer does not include the page header, so it is
+ * off by 2 bytes.
+ */
+ expected_len = inlen + 2;
+ if (expected_len > SCSI_MAX_MODE_LEN) {
+ return -1;
+ }
+
+ /* MODE_PAGE_ALLS is only valid for MODE SENSE commands */
+ if (page == MODE_PAGE_ALLS) {
+ return -1;
+ }
+
+ p = mode_current;
+ memset(mode_current, 0, inlen + 2);
+ len = mode_sense_page(s, page, &p, 0);
+ if (len < 0 || len != expected_len) {
+ return -1;
+ }
+
+ p = mode_changeable;
+ memset(mode_changeable, 0, inlen + 2);
+ changeable_len = mode_sense_page(s, page, &p, 1);
+ assert(changeable_len == len);
+
+ /* Check that unchangeable bits are the same as what MODE SENSE
+ * would return.
+ */
+ for (i = 2; i < len; i++) {
+ if (((mode_current[i] ^ inbuf[i - 2]) & ~mode_changeable[i]) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void scsi_disk_apply_mode_select(SCSIDiskState *s, int page, uint8_t *p)
+{
+ switch (page) {
+ case MODE_PAGE_CACHING:
+ blk_set_enable_write_cache(s->qdev.conf.blk, (p[0] & 4) != 0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int mode_select_pages(SCSIDiskReq *r, uint8_t *p, int len, bool change)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ while (len > 0) {
+ int page, subpage, page_len;
+
+ /* Parse both possible formats for the mode page headers. */
+ page = p[0] & 0x3f;
+ if (p[0] & 0x40) {
+ if (len < 4) {
+ goto invalid_param_len;
+ }
+ subpage = p[1];
+ page_len = lduw_be_p(&p[2]);
+ p += 4;
+ len -= 4;
+ } else {
+ if (len < 2) {
+ goto invalid_param_len;
+ }
+ subpage = 0;
+ page_len = p[1];
+ p += 2;
+ len -= 2;
+ }
+
+ if (subpage) {
+ goto invalid_param;
+ }
+ if (page_len > len) {
+ if (!(s->quirks & SCSI_DISK_QUIRK_MODE_PAGE_TRUNCATED)) {
+ goto invalid_param_len;
+ }
+ trace_scsi_disk_mode_select_page_truncated(page, page_len, len);
+ }
+
+ if (!change) {
+ if (scsi_disk_check_mode_select(s, page, p, page_len) < 0) {
+ goto invalid_param;
+ }
+ } else {
+ scsi_disk_apply_mode_select(s, page, p);
+ }
+
+ p += page_len;
+ len -= page_len;
+ }
+ return 0;
+
+invalid_param:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM));
+ return -1;
+
+invalid_param_len:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return -1;
+}
+
+static void scsi_disk_emulate_mode_select(SCSIDiskReq *r, uint8_t *inbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint8_t *p = inbuf;
+ int cmd = r->req.cmd.buf[0];
+ int len = r->req.cmd.xfer;
+ int hdr_len = (cmd == MODE_SELECT ? 4 : 8);
+ int bd_len, bs;
+ int pass;
+
+ if ((r->req.cmd.buf[1] & 0x11) != 0x10) {
+ if (!(s->quirks &
+ (1 << SCSI_DISK_QUIRK_MODE_PAGE_VENDOR_SPECIFIC_APPLE))) {
+ /* We only support PF=1, SP=0. */
+ goto invalid_field;
+ }
+ }
+
+ if (len < hdr_len) {
+ goto invalid_param_len;
+ }
+
+ bd_len = (cmd == MODE_SELECT ? p[3] : lduw_be_p(&p[6]));
+ len -= hdr_len;
+ p += hdr_len;
+ if (len < bd_len) {
+ goto invalid_param_len;
+ }
+ if (bd_len != 0 && bd_len != 8) {
+ goto invalid_param;
+ }
+
+ /* Allow changing the block size */
+ if (bd_len) {
+ bs = p[5] << 16 | p[6] << 8 | p[7];
+
+ /*
+ * Since the existing code only checks/updates bits 8-15 of the block
+ * size, restrict ourselves to the same requirement for now to ensure
+ * that a block size set by a block descriptor and then read back by
+ * a subsequent SCSI command will be the same
+ */
+ if (bs && !(bs & ~0xff00) && bs != s->qdev.blocksize) {
+ s->qdev.blocksize = bs;
+ trace_scsi_disk_mode_select_set_blocksize(s->qdev.blocksize);
+ }
+ }
+
+ len -= bd_len;
+ p += bd_len;
+
+ /* Ensure no change is made if there is an error! */
+ for (pass = 0; pass < 2; pass++) {
+ if (mode_select_pages(r, p, len, pass == 1) < 0) {
+ assert(pass == 0);
+ return;
+ }
+ }
+ if (!blk_enable_write_cache(s->qdev.conf.blk)) {
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_aio_complete, r);
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+ return;
+
+invalid_param:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM));
+ return;
+
+invalid_param_len:
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return;
+
+invalid_field:
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+}
+
+/* sector_num and nb_sectors expected to be in qdev blocksize */
+static inline bool check_lba_range(SCSIDiskState *s,
+ uint64_t sector_num, uint32_t nb_sectors)
+{
+ /*
+ * The first line tests that no overflow happens when computing the last
+ * sector. The second line tests that the last accessed sector is in
+ * range.
+ *
+ * Careful, the computations should not underflow for nb_sectors == 0,
+ * and a 0-block read to the first LBA beyond the end of device is
+ * valid.
+ */
+ return (sector_num <= sector_num + nb_sectors &&
+ sector_num + nb_sectors <= s->qdev.max_lba + 1);
+}
+
+typedef struct UnmapCBData {
+ SCSIDiskReq *r;
+ uint8_t *inbuf;
+ int count;
+} UnmapCBData;
+
+static void scsi_unmap_complete(void *opaque, int ret);
+
+static void scsi_unmap_complete_noio(UnmapCBData *data, int ret)
+{
+ SCSIDiskReq *r = data->r;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb == NULL);
+
+ if (data->count > 0) {
+ uint64_t sector_num = ldq_be_p(&data->inbuf[0]);
+ uint32_t nb_sectors = ldl_be_p(&data->inbuf[8]) & 0xffffffffULL;
+ r->sector = sector_num * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ r->sector_count = nb_sectors * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+
+ if (!check_lba_range(s, sector_num, nb_sectors)) {
+ block_acct_invalid(blk_get_stats(s->qdev.conf.blk),
+ BLOCK_ACCT_UNMAP);
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ goto done;
+ }
+
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ r->sector_count * BDRV_SECTOR_SIZE,
+ BLOCK_ACCT_UNMAP);
+
+ r->req.aiocb = blk_aio_pdiscard(s->qdev.conf.blk,
+ r->sector * BDRV_SECTOR_SIZE,
+ r->sector_count * BDRV_SECTOR_SIZE,
+ scsi_unmap_complete, data);
+ data->count--;
+ data->inbuf += 16;
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ scsi_req_unref(&r->req);
+ g_free(data);
+}
+
+static void scsi_unmap_complete(void *opaque, int ret)
+{
+ UnmapCBData *data = opaque;
+ SCSIDiskReq *r = data->r;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
+ if (scsi_disk_req_check_error(r, ret, true)) {
+ scsi_req_unref(&r->req);
+ g_free(data);
+ } else {
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+ scsi_unmap_complete_noio(data, ret);
+ }
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+}
+
+static void scsi_disk_emulate_unmap(SCSIDiskReq *r, uint8_t *inbuf)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ uint8_t *p = inbuf;
+ int len = r->req.cmd.xfer;
+ UnmapCBData *data;
+
+ /* Reject ANCHOR=1. */
+ if (r->req.cmd.buf[1] & 0x1) {
+ goto invalid_field;
+ }
+
+ if (len < 8) {
+ goto invalid_param_len;
+ }
+ if (len < lduw_be_p(&p[0]) + 2) {
+ goto invalid_param_len;
+ }
+ if (len < lduw_be_p(&p[2]) + 8) {
+ goto invalid_param_len;
+ }
+ if (lduw_be_p(&p[2]) & 15) {
+ goto invalid_param_len;
+ }
+
+ if (!blk_is_writable(s->qdev.conf.blk)) {
+ block_acct_invalid(blk_get_stats(s->qdev.conf.blk), BLOCK_ACCT_UNMAP);
+ scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED));
+ return;
+ }
+
+ data = g_new0(UnmapCBData, 1);
+ data->r = r;
+ data->inbuf = &p[8];
+ data->count = lduw_be_p(&p[2]) >> 4;
+
+ /* The matching unref is in scsi_unmap_complete, before data is freed. */
+ scsi_req_ref(&r->req);
+ scsi_unmap_complete_noio(data, 0);
+ return;
+
+invalid_param_len:
+ block_acct_invalid(blk_get_stats(s->qdev.conf.blk), BLOCK_ACCT_UNMAP);
+ scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
+ return;
+
+invalid_field:
+ block_acct_invalid(blk_get_stats(s->qdev.conf.blk), BLOCK_ACCT_UNMAP);
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+}
+
+typedef struct WriteSameCBData {
+ SCSIDiskReq *r;
+ int64_t sector;
+ int nb_sectors;
+ QEMUIOVector qiov;
+ struct iovec iov;
+} WriteSameCBData;
+
+static void scsi_write_same_complete(void *opaque, int ret)
+{
+ WriteSameCBData *data = opaque;
+ SCSIDiskReq *r = data->r;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+ aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
+ if (scsi_disk_req_check_error(r, ret, true)) {
+ goto done;
+ }
+
+ block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
+
+ data->nb_sectors -= data->iov.iov_len / BDRV_SECTOR_SIZE;
+ data->sector += data->iov.iov_len / BDRV_SECTOR_SIZE;
+ data->iov.iov_len = MIN(data->nb_sectors * BDRV_SECTOR_SIZE,
+ data->iov.iov_len);
+ if (data->iov.iov_len) {
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ data->iov.iov_len, BLOCK_ACCT_WRITE);
+ /* Reinitialize qiov, to handle unaligned WRITE SAME request
+ * where final qiov may need smaller size */
+ qemu_iovec_init_external(&data->qiov, &data->iov, 1);
+ r->req.aiocb = blk_aio_pwritev(s->qdev.conf.blk,
+ data->sector << BDRV_SECTOR_BITS,
+ &data->qiov, 0,
+ scsi_write_same_complete, data);
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+ return;
+ }
+
+ scsi_req_complete(&r->req, GOOD);
+
+done:
+ scsi_req_unref(&r->req);
+ qemu_vfree(data->iov.iov_base);
+ g_free(data);
+ aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
+}
+
+static void scsi_disk_emulate_write_same(SCSIDiskReq *r, uint8_t *inbuf)
+{
+ SCSIRequest *req = &r->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ uint32_t nb_sectors = scsi_data_cdb_xfer(r->req.cmd.buf);
+ WriteSameCBData *data;
+ uint8_t *buf;
+ int i, l;
+
+ /* Fail if PBDATA=1 or LBDATA=1 or ANCHOR=1. */
+ if (nb_sectors == 0 || (req->cmd.buf[1] & 0x16)) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return;
+ }
+
+ if (!blk_is_writable(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED));
+ return;
+ }
+ if (!check_lba_range(s, r->req.cmd.lba, nb_sectors)) {
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ return;
+ }
+
+ if ((req->cmd.buf[1] & 0x1) || buffer_is_zero(inbuf, s->qdev.blocksize)) {
+ int flags = (req->cmd.buf[1] & 0x8) ? BDRV_REQ_MAY_UNMAP : 0;
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ nb_sectors * s->qdev.blocksize,
+ BLOCK_ACCT_WRITE);
+ r->req.aiocb = blk_aio_pwrite_zeroes(s->qdev.conf.blk,
+ r->req.cmd.lba * s->qdev.blocksize,
+ nb_sectors * s->qdev.blocksize,
+ flags, scsi_aio_complete, r);
+ return;
+ }
+
+ data = g_new0(WriteSameCBData, 1);
+ data->r = r;
+ data->sector = r->req.cmd.lba * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ data->nb_sectors = nb_sectors * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ data->iov.iov_len = MIN(data->nb_sectors * BDRV_SECTOR_SIZE,
+ SCSI_WRITE_SAME_MAX);
+ data->iov.iov_base = buf = blk_blockalign(s->qdev.conf.blk,
+ data->iov.iov_len);
+ qemu_iovec_init_external(&data->qiov, &data->iov, 1);
+
+ for (i = 0; i < data->iov.iov_len; i += l) {
+ l = MIN(s->qdev.blocksize, data->iov.iov_len - i);
+ memcpy(&buf[i], inbuf, l);
+ }
+
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct,
+ data->iov.iov_len, BLOCK_ACCT_WRITE);
+ r->req.aiocb = blk_aio_pwritev(s->qdev.conf.blk,
+ data->sector << BDRV_SECTOR_BITS,
+ &data->qiov, 0,
+ scsi_write_same_complete, data);
+}
+
+static void scsi_disk_emulate_write_data(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+
+ if (r->iov.iov_len) {
+ int buflen = r->iov.iov_len;
+ trace_scsi_disk_emulate_write_data(buflen);
+ r->iov.iov_len = 0;
+ scsi_req_data(&r->req, buflen);
+ return;
+ }
+
+ switch (req->cmd.buf[0]) {
+ case MODE_SELECT:
+ case MODE_SELECT_10:
+ /* This also clears the sense buffer for REQUEST SENSE. */
+ scsi_disk_emulate_mode_select(r, r->iov.iov_base);
+ break;
+
+ case UNMAP:
+ scsi_disk_emulate_unmap(r, r->iov.iov_base);
+ break;
+
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ if (r->req.status == -1) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ }
+ break;
+
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ scsi_disk_emulate_write_same(r, r->iov.iov_base);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ uint64_t nb_sectors;
+ uint8_t *outbuf;
+ int buflen;
+
+ switch (req->cmd.buf[0]) {
+ case INQUIRY:
+ case MODE_SENSE:
+ case MODE_SENSE_10:
+ case RESERVE:
+ case RESERVE_10:
+ case RELEASE:
+ case RELEASE_10:
+ case START_STOP:
+ case ALLOW_MEDIUM_REMOVAL:
+ case GET_CONFIGURATION:
+ case GET_EVENT_STATUS_NOTIFICATION:
+ case MECHANISM_STATUS:
+ case REQUEST_SENSE:
+ break;
+
+ default:
+ if (!blk_is_available(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ return 0;
+ }
+ break;
+ }
+
+ /*
+ * FIXME: we shouldn't return anything bigger than 4k, but the code
+ * requires the buffer to be as big as req->cmd.xfer in several
+ * places. So, do not allow CDBs with a very large ALLOCATION
+ * LENGTH. The real fix would be to modify scsi_read_data and
+ * dma_buf_read, so that they return data beyond the buflen
+ * as all zeros.
+ */
+ if (req->cmd.xfer > 65536) {
+ goto illegal_request;
+ }
+ r->buflen = MAX(4096, req->cmd.xfer);
+
+ if (!r->iov.iov_base) {
+ r->iov.iov_base = blk_blockalign(s->qdev.conf.blk, r->buflen);
+ }
+
+ outbuf = r->iov.iov_base;
+ memset(outbuf, 0, r->buflen);
+ switch (req->cmd.buf[0]) {
+ case TEST_UNIT_READY:
+ assert(blk_is_available(s->qdev.conf.blk));
+ break;
+ case INQUIRY:
+ buflen = scsi_disk_emulate_inquiry(req, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case MODE_SENSE:
+ case MODE_SENSE_10:
+ buflen = scsi_disk_emulate_mode_sense(r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case READ_TOC:
+ buflen = scsi_disk_emulate_read_toc(req, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case RESERVE:
+ if (req->cmd.buf[1] & 1) {
+ goto illegal_request;
+ }
+ break;
+ case RESERVE_10:
+ if (req->cmd.buf[1] & 3) {
+ goto illegal_request;
+ }
+ break;
+ case RELEASE:
+ if (req->cmd.buf[1] & 1) {
+ goto illegal_request;
+ }
+ break;
+ case RELEASE_10:
+ if (req->cmd.buf[1] & 3) {
+ goto illegal_request;
+ }
+ break;
+ case START_STOP:
+ if (scsi_disk_emulate_start_stop(r) < 0) {
+ return 0;
+ }
+ break;
+ case ALLOW_MEDIUM_REMOVAL:
+ s->tray_locked = req->cmd.buf[4] & 1;
+ blk_lock_medium(s->qdev.conf.blk, req->cmd.buf[4] & 1);
+ break;
+ case READ_CAPACITY_10:
+ /* The normal LEN field for this command is zero. */
+ memset(outbuf, 0, 8);
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ if (!nb_sectors) {
+ scsi_check_condition(r, SENSE_CODE(LUN_NOT_READY));
+ return 0;
+ }
+ if ((req->cmd.buf[8] & 1) == 0 && req->cmd.lba) {
+ goto illegal_request;
+ }
+ nb_sectors /= s->qdev.blocksize / BDRV_SECTOR_SIZE;
+ /* Returned value is the address of the last sector. */
+ nb_sectors--;
+ /* Remember the new size for read/write sanity checking. */
+ s->qdev.max_lba = nb_sectors;
+ /* Clip to 2TB, instead of returning capacity modulo 2TB. */
+ if (nb_sectors > UINT32_MAX) {
+ nb_sectors = UINT32_MAX;
+ }
+ outbuf[0] = (nb_sectors >> 24) & 0xff;
+ outbuf[1] = (nb_sectors >> 16) & 0xff;
+ outbuf[2] = (nb_sectors >> 8) & 0xff;
+ outbuf[3] = nb_sectors & 0xff;
+ outbuf[4] = 0;
+ outbuf[5] = 0;
+ outbuf[6] = s->qdev.blocksize >> 8;
+ outbuf[7] = 0;
+ break;
+ case REQUEST_SENSE:
+ /* Just return "NO SENSE". */
+ buflen = scsi_convert_sense(NULL, 0, outbuf, r->buflen,
+ (req->cmd.buf[1] & 1) == 0);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case MECHANISM_STATUS:
+ buflen = scsi_emulate_mechanism_status(s, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case GET_CONFIGURATION:
+ buflen = scsi_get_configuration(s, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case GET_EVENT_STATUS_NOTIFICATION:
+ buflen = scsi_get_event_status_notification(s, r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case READ_DISC_INFORMATION:
+ buflen = scsi_read_disc_information(s, r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case READ_DVD_STRUCTURE:
+ buflen = scsi_read_dvd_structure(s, r, outbuf);
+ if (buflen < 0) {
+ goto illegal_request;
+ }
+ break;
+ case SERVICE_ACTION_IN_16:
+ /* Service Action In subcommands. */
+ if ((req->cmd.buf[1] & 31) == SAI_READ_CAPACITY_16) {
+ trace_scsi_disk_emulate_command_SAI_16();
+ memset(outbuf, 0, req->cmd.xfer);
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ if (!nb_sectors) {
+ scsi_check_condition(r, SENSE_CODE(LUN_NOT_READY));
+ return 0;
+ }
+ if ((req->cmd.buf[14] & 1) == 0 && req->cmd.lba) {
+ goto illegal_request;
+ }
+ nb_sectors /= s->qdev.blocksize / BDRV_SECTOR_SIZE;
+ /* Returned value is the address of the last sector. */
+ nb_sectors--;
+ /* Remember the new size for read/write sanity checking. */
+ s->qdev.max_lba = nb_sectors;
+ outbuf[0] = (nb_sectors >> 56) & 0xff;
+ outbuf[1] = (nb_sectors >> 48) & 0xff;
+ outbuf[2] = (nb_sectors >> 40) & 0xff;
+ outbuf[3] = (nb_sectors >> 32) & 0xff;
+ outbuf[4] = (nb_sectors >> 24) & 0xff;
+ outbuf[5] = (nb_sectors >> 16) & 0xff;
+ outbuf[6] = (nb_sectors >> 8) & 0xff;
+ outbuf[7] = nb_sectors & 0xff;
+ outbuf[8] = 0;
+ outbuf[9] = 0;
+ outbuf[10] = s->qdev.blocksize >> 8;
+ outbuf[11] = 0;
+ outbuf[12] = 0;
+ outbuf[13] = get_physical_block_exp(&s->qdev.conf);
+
+ /* set TPE bit if the format supports discard */
+ if (s->qdev.conf.discard_granularity) {
+ outbuf[14] = 0x80;
+ }
+
+ /* Protection, exponent and lowest lba field left blank. */
+ break;
+ }
+ trace_scsi_disk_emulate_command_SAI_unsupported();
+ goto illegal_request;
+ case SYNCHRONIZE_CACHE:
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ block_acct_start(blk_get_stats(s->qdev.conf.blk), &r->acct, 0,
+ BLOCK_ACCT_FLUSH);
+ r->req.aiocb = blk_aio_flush(s->qdev.conf.blk, scsi_aio_complete, r);
+ return 0;
+ case SEEK_10:
+ trace_scsi_disk_emulate_command_SEEK_10(r->req.cmd.lba);
+ if (r->req.cmd.lba > s->qdev.max_lba) {
+ goto illegal_lba;
+ }
+ break;
+ case MODE_SELECT:
+ trace_scsi_disk_emulate_command_MODE_SELECT(r->req.cmd.xfer);
+ break;
+ case MODE_SELECT_10:
+ trace_scsi_disk_emulate_command_MODE_SELECT_10(r->req.cmd.xfer);
+ break;
+ case UNMAP:
+ trace_scsi_disk_emulate_command_UNMAP(r->req.cmd.xfer);
+ break;
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ trace_scsi_disk_emulate_command_VERIFY((req->cmd.buf[1] >> 1) & 3);
+ if (req->cmd.buf[1] & 6) {
+ goto illegal_request;
+ }
+ break;
+ case WRITE_SAME_10:
+ case WRITE_SAME_16:
+ trace_scsi_disk_emulate_command_WRITE_SAME(
+ req->cmd.buf[0] == WRITE_SAME_10 ? 10 : 16, r->req.cmd.xfer);
+ break;
+ case FORMAT_UNIT:
+ trace_scsi_disk_emulate_command_FORMAT_UNIT(r->req.cmd.xfer);
+ break;
+ default:
+ trace_scsi_disk_emulate_command_UNKNOWN(buf[0],
+ scsi_command_name(buf[0]));
+ scsi_check_condition(r, SENSE_CODE(INVALID_OPCODE));
+ return 0;
+ }
+ assert(!r->req.aiocb);
+ r->iov.iov_len = MIN(r->buflen, req->cmd.xfer);
+ if (r->iov.iov_len == 0) {
+ scsi_req_complete(&r->req, GOOD);
+ }
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ assert(r->iov.iov_len == req->cmd.xfer);
+ return -r->iov.iov_len;
+ } else {
+ return r->iov.iov_len;
+ }
+
+illegal_request:
+ if (r->req.status == -1) {
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ }
+ return 0;
+
+illegal_lba:
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ return 0;
+}
+
+/* Execute a scsi command. Returns the length of the data expected by the
+ command. This will be Positive for data transfers from the device
+ (eg. disk reads), negative for transfers to the device (eg. disk writes),
+ and zero if the command does not transfer any data. */
+
+static int32_t scsi_disk_dma_command(SCSIRequest *req, uint8_t *buf)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+ SCSIDiskClass *sdc = (SCSIDiskClass *) object_get_class(OBJECT(s));
+ uint32_t len;
+ uint8_t command;
+
+ command = buf[0];
+
+ if (!blk_is_available(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(NO_MEDIUM));
+ return 0;
+ }
+
+ len = scsi_data_cdb_xfer(r->req.cmd.buf);
+ switch (command) {
+ case READ_6:
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ trace_scsi_disk_dma_command_READ(r->req.cmd.lba, len);
+ /* Protection information is not supported. For SCSI versions 2 and
+ * older (as determined by snooping the guest's INQUIRY commands),
+ * there is no RD/WR/VRPROTECT, so skip this check in these versions.
+ */
+ if (s->qdev.scsi_version > 2 && (r->req.cmd.buf[1] & 0xe0)) {
+ goto illegal_request;
+ }
+ if (!check_lba_range(s, r->req.cmd.lba, len)) {
+ goto illegal_lba;
+ }
+ r->sector = r->req.cmd.lba * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ r->sector_count = len * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ break;
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ case WRITE_VERIFY_10:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ if (!blk_is_writable(s->qdev.conf.blk)) {
+ scsi_check_condition(r, SENSE_CODE(WRITE_PROTECTED));
+ return 0;
+ }
+ trace_scsi_disk_dma_command_WRITE(
+ (command & 0xe) == 0xe ? "And Verify " : "",
+ r->req.cmd.lba, len);
+ /* fall through */
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ /* We get here only for BYTCHK == 0x01 and only for scsi-block.
+ * As far as DMA is concerned, we can treat it the same as a write;
+ * scsi_block_do_sgio will send VERIFY commands.
+ */
+ if (s->qdev.scsi_version > 2 && (r->req.cmd.buf[1] & 0xe0)) {
+ goto illegal_request;
+ }
+ if (!check_lba_range(s, r->req.cmd.lba, len)) {
+ goto illegal_lba;
+ }
+ r->sector = r->req.cmd.lba * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ r->sector_count = len * (s->qdev.blocksize / BDRV_SECTOR_SIZE);
+ break;
+ default:
+ abort();
+ illegal_request:
+ scsi_check_condition(r, SENSE_CODE(INVALID_FIELD));
+ return 0;
+ illegal_lba:
+ scsi_check_condition(r, SENSE_CODE(LBA_OUT_OF_RANGE));
+ return 0;
+ }
+ r->need_fua_emulation = sdc->need_fua_emulation(&r->req.cmd);
+ if (r->sector_count == 0) {
+ scsi_req_complete(&r->req, GOOD);
+ }
+ assert(r->iov.iov_len == 0);
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ return -r->sector_count * BDRV_SECTOR_SIZE;
+ } else {
+ return r->sector_count * BDRV_SECTOR_SIZE;
+ }
+}
+
+static void scsi_disk_reset(DeviceState *dev)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev.qdev, dev);
+ uint64_t nb_sectors;
+
+ scsi_device_purge_requests(&s->qdev, SENSE_CODE(RESET));
+
+ blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
+ nb_sectors /= s->qdev.blocksize / BDRV_SECTOR_SIZE;
+ if (nb_sectors) {
+ nb_sectors--;
+ }
+ s->qdev.max_lba = nb_sectors;
+ /* reset tray statuses */
+ s->tray_locked = 0;
+ s->tray_open = 0;
+
+ s->qdev.scsi_version = s->qdev.default_scsi_version;
+}
+
+static void scsi_disk_resize_cb(void *opaque)
+{
+ SCSIDiskState *s = opaque;
+
+ /* SPC lists this sense code as available only for
+ * direct-access devices.
+ */
+ if (s->qdev.type == TYPE_DISK) {
+ scsi_device_report_change(&s->qdev, SENSE_CODE(CAPACITY_CHANGED));
+ }
+}
+
+static void scsi_cd_change_media_cb(void *opaque, bool load, Error **errp)
+{
+ SCSIDiskState *s = opaque;
+
+ /*
+ * When a CD gets changed, we have to report an ejected state and
+ * then a loaded state to guests so that they detect tray
+ * open/close and media change events. Guests that do not use
+ * GET_EVENT_STATUS_NOTIFICATION to detect such tray open/close
+ * states rely on this behavior.
+ *
+ * media_changed governs the state machine used for unit attention
+ * report. media_event is used by GET EVENT STATUS NOTIFICATION.
+ */
+ s->media_changed = load;
+ s->tray_open = !load;
+ scsi_device_set_ua(&s->qdev, SENSE_CODE(UNIT_ATTENTION_NO_MEDIUM));
+ s->media_event = true;
+ s->eject_request = false;
+}
+
+static void scsi_cd_eject_request_cb(void *opaque, bool force)
+{
+ SCSIDiskState *s = opaque;
+
+ s->eject_request = true;
+ if (force) {
+ s->tray_locked = false;
+ }
+}
+
+static bool scsi_cd_is_tray_open(void *opaque)
+{
+ return ((SCSIDiskState *)opaque)->tray_open;
+}
+
+static bool scsi_cd_is_medium_locked(void *opaque)
+{
+ return ((SCSIDiskState *)opaque)->tray_locked;
+}
+
+static const BlockDevOps scsi_disk_removable_block_ops = {
+ .change_media_cb = scsi_cd_change_media_cb,
+ .eject_request_cb = scsi_cd_eject_request_cb,
+ .is_tray_open = scsi_cd_is_tray_open,
+ .is_medium_locked = scsi_cd_is_medium_locked,
+
+ .resize_cb = scsi_disk_resize_cb,
+};
+
+static const BlockDevOps scsi_disk_block_ops = {
+ .resize_cb = scsi_disk_resize_cb,
+};
+
+static void scsi_disk_unit_attention_reported(SCSIDevice *dev)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ if (s->media_changed) {
+ s->media_changed = false;
+ scsi_device_set_ua(&s->qdev, SENSE_CODE(MEDIUM_CHANGED));
+ }
+}
+
+static void scsi_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ bool read_only;
+
+ if (!s->qdev.conf.blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+
+ if (!(s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
+ !blk_is_inserted(s->qdev.conf.blk)) {
+ error_setg(errp, "Device needs media, but drive is empty");
+ return;
+ }
+
+ if (!blkconf_blocksizes(&s->qdev.conf, errp)) {
+ return;
+ }
+
+ if (blk_get_aio_context(s->qdev.conf.blk) != qemu_get_aio_context() &&
+ !s->qdev.hba_supports_iothread)
+ {
+ error_setg(errp, "HBA does not support iothreads");
+ return;
+ }
+
+ if (dev->type == TYPE_DISK) {
+ if (!blkconf_geometry(&dev->conf, NULL, 65535, 255, 255, errp)) {
+ return;
+ }
+ }
+
+ read_only = !blk_supports_write_perm(s->qdev.conf.blk);
+ if (dev->type == TYPE_ROM) {
+ read_only = true;
+ }
+
+ if (!blkconf_apply_backend_options(&dev->conf, read_only,
+ dev->type == TYPE_DISK, errp)) {
+ return;
+ }
+
+ if (s->qdev.conf.discard_granularity == -1) {
+ s->qdev.conf.discard_granularity =
+ MAX(s->qdev.conf.logical_block_size, DEFAULT_DISCARD_GRANULARITY);
+ }
+
+ if (!s->version) {
+ s->version = g_strdup(qemu_hw_version());
+ }
+ if (!s->vendor) {
+ s->vendor = g_strdup("QEMU");
+ }
+ if (!s->device_id) {
+ if (s->serial) {
+ s->device_id = g_strdup_printf("%.20s", s->serial);
+ } else {
+ const char *str = blk_name(s->qdev.conf.blk);
+ if (str && *str) {
+ s->device_id = g_strdup(str);
+ }
+ }
+ }
+
+ if (blk_is_sg(s->qdev.conf.blk)) {
+ error_setg(errp, "unwanted /dev/sg*");
+ return;
+ }
+
+ if ((s->features & (1 << SCSI_DISK_F_REMOVABLE)) &&
+ !(s->features & (1 << SCSI_DISK_F_NO_REMOVABLE_DEVOPS))) {
+ blk_set_dev_ops(s->qdev.conf.blk, &scsi_disk_removable_block_ops, s);
+ } else {
+ blk_set_dev_ops(s->qdev.conf.blk, &scsi_disk_block_ops, s);
+ }
+
+ blk_iostatus_enable(s->qdev.conf.blk);
+
+ add_boot_device_lchs(&dev->qdev, NULL,
+ dev->conf.lcyls,
+ dev->conf.lheads,
+ dev->conf.lsecs);
+}
+
+static void scsi_unrealize(SCSIDevice *dev)
+{
+ del_boot_device_lchs(&dev->qdev, NULL);
+}
+
+static void scsi_hd_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ AioContext *ctx = NULL;
+ /* can happen for devices without drive. The error message for missing
+ * backend will be issued in scsi_realize
+ */
+ if (s->qdev.conf.blk) {
+ ctx = blk_get_aio_context(s->qdev.conf.blk);
+ aio_context_acquire(ctx);
+ if (!blkconf_blocksizes(&s->qdev.conf, errp)) {
+ goto out;
+ }
+ }
+ s->qdev.blocksize = s->qdev.conf.logical_block_size;
+ s->qdev.type = TYPE_DISK;
+ if (!s->product) {
+ s->product = g_strdup("QEMU HARDDISK");
+ }
+ scsi_realize(&s->qdev, errp);
+out:
+ if (ctx) {
+ aio_context_release(ctx);
+ }
+}
+
+static void scsi_cd_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ AioContext *ctx;
+ int ret;
+ uint32_t blocksize = 2048;
+
+ if (!dev->conf.blk) {
+ /* Anonymous BlockBackend for an empty drive. As we put it into
+ * dev->conf, qdev takes care of detaching on unplug. */
+ dev->conf.blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
+ ret = blk_attach_dev(dev->conf.blk, &dev->qdev);
+ assert(ret == 0);
+ }
+
+ if (dev->conf.physical_block_size != 0) {
+ blocksize = dev->conf.physical_block_size;
+ }
+
+ ctx = blk_get_aio_context(dev->conf.blk);
+ aio_context_acquire(ctx);
+ s->qdev.blocksize = blocksize;
+ s->qdev.type = TYPE_ROM;
+ s->features |= 1 << SCSI_DISK_F_REMOVABLE;
+ if (!s->product) {
+ s->product = g_strdup("QEMU CD-ROM");
+ }
+ scsi_realize(&s->qdev, errp);
+ aio_context_release(ctx);
+}
+
+
+static const SCSIReqOps scsi_disk_emulate_reqops = {
+ .size = sizeof(SCSIDiskReq),
+ .free_req = scsi_free_request,
+ .send_command = scsi_disk_emulate_command,
+ .read_data = scsi_disk_emulate_read_data,
+ .write_data = scsi_disk_emulate_write_data,
+ .get_buf = scsi_get_buf,
+};
+
+static const SCSIReqOps scsi_disk_dma_reqops = {
+ .size = sizeof(SCSIDiskReq),
+ .free_req = scsi_free_request,
+ .send_command = scsi_disk_dma_command,
+ .read_data = scsi_read_data,
+ .write_data = scsi_write_data,
+ .get_buf = scsi_get_buf,
+ .load_request = scsi_disk_load_request,
+ .save_request = scsi_disk_save_request,
+};
+
+static const SCSIReqOps *const scsi_disk_reqops_dispatch[256] = {
+ [TEST_UNIT_READY] = &scsi_disk_emulate_reqops,
+ [INQUIRY] = &scsi_disk_emulate_reqops,
+ [MODE_SENSE] = &scsi_disk_emulate_reqops,
+ [MODE_SENSE_10] = &scsi_disk_emulate_reqops,
+ [START_STOP] = &scsi_disk_emulate_reqops,
+ [ALLOW_MEDIUM_REMOVAL] = &scsi_disk_emulate_reqops,
+ [READ_CAPACITY_10] = &scsi_disk_emulate_reqops,
+ [READ_TOC] = &scsi_disk_emulate_reqops,
+ [READ_DVD_STRUCTURE] = &scsi_disk_emulate_reqops,
+ [READ_DISC_INFORMATION] = &scsi_disk_emulate_reqops,
+ [GET_CONFIGURATION] = &scsi_disk_emulate_reqops,
+ [GET_EVENT_STATUS_NOTIFICATION] = &scsi_disk_emulate_reqops,
+ [MECHANISM_STATUS] = &scsi_disk_emulate_reqops,
+ [SERVICE_ACTION_IN_16] = &scsi_disk_emulate_reqops,
+ [REQUEST_SENSE] = &scsi_disk_emulate_reqops,
+ [SYNCHRONIZE_CACHE] = &scsi_disk_emulate_reqops,
+ [SEEK_10] = &scsi_disk_emulate_reqops,
+ [MODE_SELECT] = &scsi_disk_emulate_reqops,
+ [MODE_SELECT_10] = &scsi_disk_emulate_reqops,
+ [UNMAP] = &scsi_disk_emulate_reqops,
+ [WRITE_SAME_10] = &scsi_disk_emulate_reqops,
+ [WRITE_SAME_16] = &scsi_disk_emulate_reqops,
+ [VERIFY_10] = &scsi_disk_emulate_reqops,
+ [VERIFY_12] = &scsi_disk_emulate_reqops,
+ [VERIFY_16] = &scsi_disk_emulate_reqops,
+ [FORMAT_UNIT] = &scsi_disk_emulate_reqops,
+
+ [READ_6] = &scsi_disk_dma_reqops,
+ [READ_10] = &scsi_disk_dma_reqops,
+ [READ_12] = &scsi_disk_dma_reqops,
+ [READ_16] = &scsi_disk_dma_reqops,
+ [WRITE_6] = &scsi_disk_dma_reqops,
+ [WRITE_10] = &scsi_disk_dma_reqops,
+ [WRITE_12] = &scsi_disk_dma_reqops,
+ [WRITE_16] = &scsi_disk_dma_reqops,
+ [WRITE_VERIFY_10] = &scsi_disk_dma_reqops,
+ [WRITE_VERIFY_12] = &scsi_disk_dma_reqops,
+ [WRITE_VERIFY_16] = &scsi_disk_dma_reqops,
+};
+
+static void scsi_disk_new_request_dump(uint32_t lun, uint32_t tag, uint8_t *buf)
+{
+ int i;
+ int len = scsi_cdb_length(buf);
+ char *line_buffer, *p;
+
+ assert(len > 0 && len <= 16);
+ line_buffer = g_malloc(len * 5 + 1);
+
+ for (i = 0, p = line_buffer; i < len; i++) {
+ p += sprintf(p, " 0x%02x", buf[i]);
+ }
+ trace_scsi_disk_new_request(lun, tag, line_buffer);
+
+ g_free(line_buffer);
+}
+
+static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun,
+ uint8_t *buf, void *hba_private)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+ SCSIRequest *req;
+ const SCSIReqOps *ops;
+ uint8_t command;
+
+ command = buf[0];
+ ops = scsi_disk_reqops_dispatch[command];
+ if (!ops) {
+ ops = &scsi_disk_emulate_reqops;
+ }
+ req = scsi_req_alloc(ops, &s->qdev, tag, lun, hba_private);
+
+ if (trace_event_get_state_backends(TRACE_SCSI_DISK_NEW_REQUEST)) {
+ scsi_disk_new_request_dump(lun, tag, buf);
+ }
+
+ return req;
+}
+
+#ifdef __linux__
+static int get_device_type(SCSIDiskState *s)
+{
+ uint8_t cmd[16];
+ uint8_t buf[36];
+ int ret;
+
+ memset(cmd, 0, sizeof(cmd));
+ memset(buf, 0, sizeof(buf));
+ cmd[0] = INQUIRY;
+ cmd[4] = sizeof(buf);
+
+ ret = scsi_SG_IO_FROM_DEV(s->qdev.conf.blk, cmd, sizeof(cmd),
+ buf, sizeof(buf), s->qdev.io_timeout);
+ if (ret < 0) {
+ return -1;
+ }
+ s->qdev.type = buf[0];
+ if (buf[1] & 0x80) {
+ s->features |= 1 << SCSI_DISK_F_REMOVABLE;
+ }
+ return 0;
+}
+
+static void scsi_block_realize(SCSIDevice *dev, Error **errp)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev);
+ AioContext *ctx;
+ int sg_version;
+ int rc;
+
+ if (!s->qdev.conf.blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+
+ if (s->rotation_rate) {
+ error_report_once("rotation_rate is specified for scsi-block but is "
+ "not implemented. This option is deprecated and will "
+ "be removed in a future version");
+ }
+
+ ctx = blk_get_aio_context(s->qdev.conf.blk);
+ aio_context_acquire(ctx);
+
+ /* check we are using a driver managing SG_IO (version 3 and after) */
+ rc = blk_ioctl(s->qdev.conf.blk, SG_GET_VERSION_NUM, &sg_version);
+ if (rc < 0) {
+ error_setg_errno(errp, -rc, "cannot get SG_IO version number");
+ if (rc != -EPERM) {
+ error_append_hint(errp, "Is this a SCSI device?\n");
+ }
+ goto out;
+ }
+ if (sg_version < 30000) {
+ error_setg(errp, "scsi generic interface too old");
+ goto out;
+ }
+
+ /* get device type from INQUIRY data */
+ rc = get_device_type(s);
+ if (rc < 0) {
+ error_setg(errp, "INQUIRY failed");
+ goto out;
+ }
+
+ /* Make a guess for the block size, we'll fix it when the guest sends.
+ * READ CAPACITY. If they don't, they likely would assume these sizes
+ * anyway. (TODO: check in /sys).
+ */
+ if (s->qdev.type == TYPE_ROM || s->qdev.type == TYPE_WORM) {
+ s->qdev.blocksize = 2048;
+ } else {
+ s->qdev.blocksize = 512;
+ }
+
+ /* Makes the scsi-block device not removable by using HMP and QMP eject
+ * command.
+ */
+ s->features |= (1 << SCSI_DISK_F_NO_REMOVABLE_DEVOPS);
+
+ scsi_realize(&s->qdev, errp);
+ scsi_generic_read_device_inquiry(&s->qdev);
+
+out:
+ aio_context_release(ctx);
+}
+
+typedef struct SCSIBlockReq {
+ SCSIDiskReq req;
+ sg_io_hdr_t io_header;
+
+ /* Selected bytes of the original CDB, copied into our own CDB. */
+ uint8_t cmd, cdb1, group_number;
+
+ /* CDB passed to SG_IO. */
+ uint8_t cdb[16];
+ BlockCompletionFunc *cb;
+ void *cb_opaque;
+} SCSIBlockReq;
+
+static void scsi_block_sgio_complete(void *opaque, int ret)
+{
+ SCSIBlockReq *req = (SCSIBlockReq *)opaque;
+ SCSIDiskReq *r = &req->req;
+ SCSIDevice *s = r->req.dev;
+ sg_io_hdr_t *io_hdr = &req->io_header;
+
+ if (ret == 0) {
+ if (io_hdr->host_status != SCSI_HOST_OK) {
+ scsi_req_complete_failed(&r->req, io_hdr->host_status);
+ scsi_req_unref(&r->req);
+ return;
+ }
+
+ if (io_hdr->driver_status & SG_ERR_DRIVER_TIMEOUT) {
+ ret = BUSY;
+ } else {
+ ret = io_hdr->status;
+ }
+
+ if (ret > 0) {
+ aio_context_acquire(blk_get_aio_context(s->conf.blk));
+ if (scsi_handle_rw_error(r, ret, true)) {
+ aio_context_release(blk_get_aio_context(s->conf.blk));
+ scsi_req_unref(&r->req);
+ return;
+ }
+ aio_context_release(blk_get_aio_context(s->conf.blk));
+
+ /* Ignore error. */
+ ret = 0;
+ }
+ }
+
+ req->cb(req->cb_opaque, ret);
+}
+
+static BlockAIOCB *scsi_block_do_sgio(SCSIBlockReq *req,
+ int64_t offset, QEMUIOVector *iov,
+ int direction,
+ BlockCompletionFunc *cb, void *opaque)
+{
+ sg_io_hdr_t *io_header = &req->io_header;
+ SCSIDiskReq *r = &req->req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ int nb_logical_blocks;
+ uint64_t lba;
+ BlockAIOCB *aiocb;
+
+ /* This is not supported yet. It can only happen if the guest does
+ * reads and writes that are not aligned to one logical sectors
+ * _and_ cover multiple MemoryRegions.
+ */
+ assert(offset % s->qdev.blocksize == 0);
+ assert(iov->size % s->qdev.blocksize == 0);
+
+ io_header->interface_id = 'S';
+
+ /* The data transfer comes from the QEMUIOVector. */
+ io_header->dxfer_direction = direction;
+ io_header->dxfer_len = iov->size;
+ io_header->dxferp = (void *)iov->iov;
+ io_header->iovec_count = iov->niov;
+ assert(io_header->iovec_count == iov->niov); /* no overflow! */
+
+ /* Build a new CDB with the LBA and length patched in, in case
+ * DMA helpers split the transfer in multiple segments. Do not
+ * build a CDB smaller than what the guest wanted, and only build
+ * a larger one if strictly necessary.
+ */
+ io_header->cmdp = req->cdb;
+ lba = offset / s->qdev.blocksize;
+ nb_logical_blocks = io_header->dxfer_len / s->qdev.blocksize;
+
+ if ((req->cmd >> 5) == 0 && lba <= 0x1ffff) {
+ /* 6-byte CDB */
+ stl_be_p(&req->cdb[0], lba | (req->cmd << 24));
+ req->cdb[4] = nb_logical_blocks;
+ req->cdb[5] = 0;
+ io_header->cmd_len = 6;
+ } else if ((req->cmd >> 5) <= 1 && lba <= 0xffffffffULL) {
+ /* 10-byte CDB */
+ req->cdb[0] = (req->cmd & 0x1f) | 0x20;
+ req->cdb[1] = req->cdb1;
+ stl_be_p(&req->cdb[2], lba);
+ req->cdb[6] = req->group_number;
+ stw_be_p(&req->cdb[7], nb_logical_blocks);
+ req->cdb[9] = 0;
+ io_header->cmd_len = 10;
+ } else if ((req->cmd >> 5) != 4 && lba <= 0xffffffffULL) {
+ /* 12-byte CDB */
+ req->cdb[0] = (req->cmd & 0x1f) | 0xA0;
+ req->cdb[1] = req->cdb1;
+ stl_be_p(&req->cdb[2], lba);
+ stl_be_p(&req->cdb[6], nb_logical_blocks);
+ req->cdb[10] = req->group_number;
+ req->cdb[11] = 0;
+ io_header->cmd_len = 12;
+ } else {
+ /* 16-byte CDB */
+ req->cdb[0] = (req->cmd & 0x1f) | 0x80;
+ req->cdb[1] = req->cdb1;
+ stq_be_p(&req->cdb[2], lba);
+ stl_be_p(&req->cdb[10], nb_logical_blocks);
+ req->cdb[14] = req->group_number;
+ req->cdb[15] = 0;
+ io_header->cmd_len = 16;
+ }
+
+ /* The rest is as in scsi-generic.c. */
+ io_header->mx_sb_len = sizeof(r->req.sense);
+ io_header->sbp = r->req.sense;
+ io_header->timeout = s->qdev.io_timeout * 1000;
+ io_header->usr_ptr = r;
+ io_header->flags |= SG_FLAG_DIRECT_IO;
+ req->cb = cb;
+ req->cb_opaque = opaque;
+ trace_scsi_disk_aio_sgio_command(r->req.tag, req->cdb[0], lba,
+ nb_logical_blocks, io_header->timeout);
+ aiocb = blk_aio_ioctl(s->qdev.conf.blk, SG_IO, io_header, scsi_block_sgio_complete, req);
+ assert(aiocb != NULL);
+ return aiocb;
+}
+
+static bool scsi_block_no_fua(SCSICommand *cmd)
+{
+ return false;
+}
+
+static BlockAIOCB *scsi_block_dma_readv(int64_t offset,
+ QEMUIOVector *iov,
+ BlockCompletionFunc *cb, void *cb_opaque,
+ void *opaque)
+{
+ SCSIBlockReq *r = opaque;
+ return scsi_block_do_sgio(r, offset, iov,
+ SG_DXFER_FROM_DEV, cb, cb_opaque);
+}
+
+static BlockAIOCB *scsi_block_dma_writev(int64_t offset,
+ QEMUIOVector *iov,
+ BlockCompletionFunc *cb, void *cb_opaque,
+ void *opaque)
+{
+ SCSIBlockReq *r = opaque;
+ return scsi_block_do_sgio(r, offset, iov,
+ SG_DXFER_TO_DEV, cb, cb_opaque);
+}
+
+static bool scsi_block_is_passthrough(SCSIDiskState *s, uint8_t *buf)
+{
+ switch (buf[0]) {
+ case VERIFY_10:
+ case VERIFY_12:
+ case VERIFY_16:
+ /* Check if BYTCHK == 0x01 (data-out buffer contains data
+ * for the number of logical blocks specified in the length
+ * field). For other modes, do not use scatter/gather operation.
+ */
+ if ((buf[1] & 6) == 2) {
+ return false;
+ }
+ break;
+
+ case READ_6:
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ case WRITE_6:
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ case WRITE_VERIFY_10:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ /* MMC writing cannot be done via DMA helpers, because it sometimes
+ * involves writing beyond the maximum LBA or to negative LBA (lead-in).
+ * We might use scsi_block_dma_reqops as long as no writing commands are
+ * seen, but performance usually isn't paramount on optical media. So,
+ * just make scsi-block operate the same as scsi-generic for them.
+ */
+ if (s->qdev.type != TYPE_ROM) {
+ return false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+static int32_t scsi_block_dma_command(SCSIRequest *req, uint8_t *buf)
+{
+ SCSIBlockReq *r = (SCSIBlockReq *)req;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev);
+
+ r->cmd = req->cmd.buf[0];
+ switch (r->cmd >> 5) {
+ case 0:
+ /* 6-byte CDB. */
+ r->cdb1 = r->group_number = 0;
+ break;
+ case 1:
+ /* 10-byte CDB. */
+ r->cdb1 = req->cmd.buf[1];
+ r->group_number = req->cmd.buf[6];
+ break;
+ case 4:
+ /* 12-byte CDB. */
+ r->cdb1 = req->cmd.buf[1];
+ r->group_number = req->cmd.buf[10];
+ break;
+ case 5:
+ /* 16-byte CDB. */
+ r->cdb1 = req->cmd.buf[1];
+ r->group_number = req->cmd.buf[14];
+ break;
+ default:
+ abort();
+ }
+
+ /* Protection information is not supported. For SCSI versions 2 and
+ * older (as determined by snooping the guest's INQUIRY commands),
+ * there is no RD/WR/VRPROTECT, so skip this check in these versions.
+ */
+ if (s->qdev.scsi_version > 2 && (req->cmd.buf[1] & 0xe0)) {
+ scsi_check_condition(&r->req, SENSE_CODE(INVALID_FIELD));
+ return 0;
+ }
+
+ return scsi_disk_dma_command(req, buf);
+}
+
+static const SCSIReqOps scsi_block_dma_reqops = {
+ .size = sizeof(SCSIBlockReq),
+ .free_req = scsi_free_request,
+ .send_command = scsi_block_dma_command,
+ .read_data = scsi_read_data,
+ .write_data = scsi_write_data,
+ .get_buf = scsi_get_buf,
+ .load_request = scsi_disk_load_request,
+ .save_request = scsi_disk_save_request,
+};
+
+static SCSIRequest *scsi_block_new_request(SCSIDevice *d, uint32_t tag,
+ uint32_t lun, uint8_t *buf,
+ void *hba_private)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+
+ if (scsi_block_is_passthrough(s, buf)) {
+ return scsi_req_alloc(&scsi_generic_req_ops, &s->qdev, tag, lun,
+ hba_private);
+ } else {
+ return scsi_req_alloc(&scsi_block_dma_reqops, &s->qdev, tag, lun,
+ hba_private);
+ }
+}
+
+static int scsi_block_parse_cdb(SCSIDevice *d, SCSICommand *cmd,
+ uint8_t *buf, size_t buf_len,
+ void *hba_private)
+{
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d);
+
+ if (scsi_block_is_passthrough(s, buf)) {
+ return scsi_bus_parse_cdb(&s->qdev, cmd, buf, buf_len, hba_private);
+ } else {
+ return scsi_req_parse_cdb(&s->qdev, cmd, buf, buf_len);
+ }
+}
+
+static void scsi_block_update_sense(SCSIRequest *req)
+{
+ SCSIDiskReq *r = DO_UPCAST(SCSIDiskReq, req, req);
+ SCSIBlockReq *br = DO_UPCAST(SCSIBlockReq, req, r);
+ r->req.sense_len = MIN(br->io_header.sb_len_wr, sizeof(r->req.sense));
+}
+#endif
+
+static
+BlockAIOCB *scsi_dma_readv(int64_t offset, QEMUIOVector *iov,
+ BlockCompletionFunc *cb, void *cb_opaque,
+ void *opaque)
+{
+ SCSIDiskReq *r = opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ return blk_aio_preadv(s->qdev.conf.blk, offset, iov, 0, cb, cb_opaque);
+}
+
+static
+BlockAIOCB *scsi_dma_writev(int64_t offset, QEMUIOVector *iov,
+ BlockCompletionFunc *cb, void *cb_opaque,
+ void *opaque)
+{
+ SCSIDiskReq *r = opaque;
+ SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);
+ return blk_aio_pwritev(s->qdev.conf.blk, offset, iov, 0, cb, cb_opaque);
+}
+
+static void scsi_disk_base_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDiskClass *sdc = SCSI_DISK_BASE_CLASS(klass);
+
+ dc->fw_name = "disk";
+ dc->reset = scsi_disk_reset;
+ sdc->dma_readv = scsi_dma_readv;
+ sdc->dma_writev = scsi_dma_writev;
+ sdc->need_fua_emulation = scsi_is_cmd_fua;
+}
+
+static const TypeInfo scsi_disk_base_info = {
+ .name = TYPE_SCSI_DISK_BASE,
+ .parent = TYPE_SCSI_DEVICE,
+ .class_init = scsi_disk_base_class_initfn,
+ .instance_size = sizeof(SCSIDiskState),
+ .class_size = sizeof(SCSIDiskClass),
+ .abstract = true,
+};
+
+#define DEFINE_SCSI_DISK_PROPERTIES() \
+ DEFINE_PROP_DRIVE_IOTHREAD("drive", SCSIDiskState, qdev.conf.blk), \
+ DEFINE_BLOCK_PROPERTIES_BASE(SCSIDiskState, qdev.conf), \
+ DEFINE_BLOCK_ERROR_PROPERTIES(SCSIDiskState, qdev.conf), \
+ DEFINE_PROP_STRING("ver", SCSIDiskState, version), \
+ DEFINE_PROP_STRING("serial", SCSIDiskState, serial), \
+ DEFINE_PROP_STRING("vendor", SCSIDiskState, vendor), \
+ DEFINE_PROP_STRING("product", SCSIDiskState, product), \
+ DEFINE_PROP_STRING("device_id", SCSIDiskState, device_id)
+
+
+static Property scsi_hd_properties[] = {
+ DEFINE_SCSI_DISK_PROPERTIES(),
+ DEFINE_PROP_BIT("removable", SCSIDiskState, features,
+ SCSI_DISK_F_REMOVABLE, false),
+ DEFINE_PROP_BIT("dpofua", SCSIDiskState, features,
+ SCSI_DISK_F_DPOFUA, false),
+ DEFINE_PROP_UINT64("wwn", SCSIDiskState, qdev.wwn, 0),
+ DEFINE_PROP_UINT64("port_wwn", SCSIDiskState, qdev.port_wwn, 0),
+ DEFINE_PROP_UINT16("port_index", SCSIDiskState, port_index, 0),
+ DEFINE_PROP_UINT64("max_unmap_size", SCSIDiskState, max_unmap_size,
+ DEFAULT_MAX_UNMAP_SIZE),
+ DEFINE_PROP_UINT64("max_io_size", SCSIDiskState, max_io_size,
+ DEFAULT_MAX_IO_SIZE),
+ DEFINE_PROP_UINT16("rotation_rate", SCSIDiskState, rotation_rate, 0),
+ DEFINE_PROP_INT32("scsi_version", SCSIDiskState, qdev.default_scsi_version,
+ 5),
+ DEFINE_PROP_BIT("quirk_mode_page_vendor_specific_apple", SCSIDiskState,
+ quirks, SCSI_DISK_QUIRK_MODE_PAGE_VENDOR_SPECIFIC_APPLE,
+ 0),
+ DEFINE_BLOCK_CHS_PROPERTIES(SCSIDiskState, qdev.conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_scsi_disk_state = {
+ .name = "scsi-disk",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SCSI_DEVICE(qdev, SCSIDiskState),
+ VMSTATE_BOOL(media_changed, SCSIDiskState),
+ VMSTATE_BOOL(media_event, SCSIDiskState),
+ VMSTATE_BOOL(eject_request, SCSIDiskState),
+ VMSTATE_BOOL(tray_open, SCSIDiskState),
+ VMSTATE_BOOL(tray_locked, SCSIDiskState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void scsi_hd_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_hd_realize;
+ sc->unrealize = scsi_unrealize;
+ sc->alloc_req = scsi_new_request;
+ sc->unit_attention_reported = scsi_disk_unit_attention_reported;
+ dc->desc = "virtual SCSI disk";
+ device_class_set_props(dc, scsi_hd_properties);
+ dc->vmsd = &vmstate_scsi_disk_state;
+}
+
+static const TypeInfo scsi_hd_info = {
+ .name = "scsi-hd",
+ .parent = TYPE_SCSI_DISK_BASE,
+ .class_init = scsi_hd_class_initfn,
+};
+
+static Property scsi_cd_properties[] = {
+ DEFINE_SCSI_DISK_PROPERTIES(),
+ DEFINE_PROP_UINT64("wwn", SCSIDiskState, qdev.wwn, 0),
+ DEFINE_PROP_UINT64("port_wwn", SCSIDiskState, qdev.port_wwn, 0),
+ DEFINE_PROP_UINT16("port_index", SCSIDiskState, port_index, 0),
+ DEFINE_PROP_UINT64("max_io_size", SCSIDiskState, max_io_size,
+ DEFAULT_MAX_IO_SIZE),
+ DEFINE_PROP_INT32("scsi_version", SCSIDiskState, qdev.default_scsi_version,
+ 5),
+ DEFINE_PROP_BIT("quirk_mode_page_apple_vendor", SCSIDiskState, quirks,
+ SCSI_DISK_QUIRK_MODE_PAGE_APPLE_VENDOR, 0),
+ DEFINE_PROP_BIT("quirk_mode_sense_rom_use_dbd", SCSIDiskState, quirks,
+ SCSI_DISK_QUIRK_MODE_SENSE_ROM_USE_DBD, 0),
+ DEFINE_PROP_BIT("quirk_mode_page_vendor_specific_apple", SCSIDiskState,
+ quirks, SCSI_DISK_QUIRK_MODE_PAGE_VENDOR_SPECIFIC_APPLE,
+ 0),
+ DEFINE_PROP_BIT("quirk_mode_page_truncated", SCSIDiskState, quirks,
+ SCSI_DISK_QUIRK_MODE_PAGE_TRUNCATED, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scsi_cd_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_cd_realize;
+ sc->alloc_req = scsi_new_request;
+ sc->unit_attention_reported = scsi_disk_unit_attention_reported;
+ dc->desc = "virtual SCSI CD-ROM";
+ device_class_set_props(dc, scsi_cd_properties);
+ dc->vmsd = &vmstate_scsi_disk_state;
+}
+
+static const TypeInfo scsi_cd_info = {
+ .name = "scsi-cd",
+ .parent = TYPE_SCSI_DISK_BASE,
+ .class_init = scsi_cd_class_initfn,
+};
+
+#ifdef __linux__
+static Property scsi_block_properties[] = {
+ DEFINE_BLOCK_ERROR_PROPERTIES(SCSIDiskState, qdev.conf),
+ DEFINE_PROP_DRIVE("drive", SCSIDiskState, qdev.conf.blk),
+ DEFINE_PROP_BOOL("share-rw", SCSIDiskState, qdev.conf.share_rw, false),
+ DEFINE_PROP_UINT16("rotation_rate", SCSIDiskState, rotation_rate, 0),
+ DEFINE_PROP_UINT64("max_unmap_size", SCSIDiskState, max_unmap_size,
+ DEFAULT_MAX_UNMAP_SIZE),
+ DEFINE_PROP_UINT64("max_io_size", SCSIDiskState, max_io_size,
+ DEFAULT_MAX_IO_SIZE),
+ DEFINE_PROP_INT32("scsi_version", SCSIDiskState, qdev.default_scsi_version,
+ -1),
+ DEFINE_PROP_UINT32("io_timeout", SCSIDiskState, qdev.io_timeout,
+ DEFAULT_IO_TIMEOUT),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void scsi_block_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+ SCSIDiskClass *sdc = SCSI_DISK_BASE_CLASS(klass);
+
+ sc->realize = scsi_block_realize;
+ sc->alloc_req = scsi_block_new_request;
+ sc->parse_cdb = scsi_block_parse_cdb;
+ sdc->dma_readv = scsi_block_dma_readv;
+ sdc->dma_writev = scsi_block_dma_writev;
+ sdc->update_sense = scsi_block_update_sense;
+ sdc->need_fua_emulation = scsi_block_no_fua;
+ dc->desc = "SCSI block device passthrough";
+ device_class_set_props(dc, scsi_block_properties);
+ dc->vmsd = &vmstate_scsi_disk_state;
+}
+
+static const TypeInfo scsi_block_info = {
+ .name = "scsi-block",
+ .parent = TYPE_SCSI_DISK_BASE,
+ .class_init = scsi_block_class_initfn,
+};
+#endif
+
+static void scsi_disk_register_types(void)
+{
+ type_register_static(&scsi_disk_base_info);
+ type_register_static(&scsi_hd_info);
+ type_register_static(&scsi_cd_info);
+#ifdef __linux__
+ type_register_static(&scsi_block_info);
+#endif
+}
+
+type_init(scsi_disk_register_types)
diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
new file mode 100644
index 00000000..92cce20a
--- /dev/null
+++ b/hw/scsi/scsi-generic.c
@@ -0,0 +1,829 @@
+/*
+ * Generic SCSI Device support
+ *
+ * Copyright (c) 2007 Bull S.A.S.
+ * Based on code by Paul Brook
+ * Based on code by Fabrice Bellard
+ *
+ * Written by Laurent Vivier <Laurent.Vivier@bull.net>
+ *
+ * This code is licensed under the LGPL.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/ctype.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "hw/scsi/scsi.h"
+#include "migration/qemu-file-types.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "hw/scsi/emulation.h"
+#include "sysemu/block-backend.h"
+#include "trace.h"
+
+#ifdef __linux__
+
+#include <scsi/sg.h>
+#include "scsi/constants.h"
+
+#ifndef MAX_UINT
+#define MAX_UINT ((unsigned int)-1)
+#endif
+
+typedef struct SCSIGenericReq {
+ SCSIRequest req;
+ uint8_t *buf;
+ int buflen;
+ int len;
+ sg_io_hdr_t io_header;
+} SCSIGenericReq;
+
+static void scsi_generic_save_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ qemu_put_sbe32s(f, &r->buflen);
+ if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ assert(!r->req.sg);
+ qemu_put_buffer(f, r->buf, r->req.cmd.xfer);
+ }
+}
+
+static void scsi_generic_load_request(QEMUFile *f, SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ qemu_get_sbe32s(f, &r->buflen);
+ if (r->buflen && r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ assert(!r->req.sg);
+ qemu_get_buffer(f, r->buf, r->req.cmd.xfer);
+ }
+}
+
+static void scsi_free_request(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ g_free(r->buf);
+}
+
+/* Helper function for command completion. */
+static void scsi_command_complete_noio(SCSIGenericReq *r, int ret)
+{
+ int status;
+ SCSISense sense;
+ sg_io_hdr_t *io_hdr = &r->io_header;
+
+ assert(r->req.aiocb == NULL);
+
+ if (r->req.io_canceled) {
+ scsi_req_cancel_complete(&r->req);
+ goto done;
+ }
+ if (ret < 0) {
+ status = scsi_sense_from_errno(-ret, &sense);
+ if (status == CHECK_CONDITION) {
+ scsi_req_build_sense(&r->req, sense);
+ }
+ } else if (io_hdr->host_status != SCSI_HOST_OK) {
+ scsi_req_complete_failed(&r->req, io_hdr->host_status);
+ goto done;
+ } else if (io_hdr->driver_status & SG_ERR_DRIVER_TIMEOUT) {
+ status = BUSY;
+ } else {
+ status = io_hdr->status;
+ if (io_hdr->driver_status & SG_ERR_DRIVER_SENSE) {
+ r->req.sense_len = io_hdr->sb_len_wr;
+ }
+ }
+ trace_scsi_generic_command_complete_noio(r, r->req.tag, status);
+
+ scsi_req_complete(&r->req, status);
+done:
+ scsi_req_unref(&r->req);
+}
+
+static void scsi_command_complete(void *opaque, int ret)
+{
+ SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+ SCSIDevice *s = r->req.dev;
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->conf.blk));
+ scsi_command_complete_noio(r, ret);
+ aio_context_release(blk_get_aio_context(s->conf.blk));
+}
+
+static int execute_command(BlockBackend *blk,
+ SCSIGenericReq *r, int direction,
+ BlockCompletionFunc *complete)
+{
+ SCSIDevice *s = r->req.dev;
+
+ r->io_header.interface_id = 'S';
+ r->io_header.dxfer_direction = direction;
+ r->io_header.dxferp = r->buf;
+ r->io_header.dxfer_len = r->buflen;
+ r->io_header.cmdp = r->req.cmd.buf;
+ r->io_header.cmd_len = r->req.cmd.len;
+ r->io_header.mx_sb_len = sizeof(r->req.sense);
+ r->io_header.sbp = r->req.sense;
+ r->io_header.timeout = s->io_timeout * 1000;
+ r->io_header.usr_ptr = r;
+ r->io_header.flags |= SG_FLAG_DIRECT_IO;
+
+ trace_scsi_generic_aio_sgio_command(r->req.tag, r->req.cmd.buf[0],
+ r->io_header.timeout);
+ r->req.aiocb = blk_aio_ioctl(blk, SG_IO, &r->io_header, complete, r);
+ if (r->req.aiocb == NULL) {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static uint64_t calculate_max_transfer(SCSIDevice *s)
+{
+ uint64_t max_transfer = blk_get_max_hw_transfer(s->conf.blk);
+ uint32_t max_iov = blk_get_max_hw_iov(s->conf.blk);
+
+ assert(max_transfer);
+ max_transfer = MIN_NON_ZERO(max_transfer,
+ max_iov * qemu_real_host_page_size());
+
+ return max_transfer / s->blocksize;
+}
+
+static int scsi_handle_inquiry_reply(SCSIGenericReq *r, SCSIDevice *s, int len)
+{
+ uint8_t page, page_idx;
+
+ /*
+ * EVPD set to zero returns the standard INQUIRY data.
+ *
+ * Check if scsi_version is unset (-1) to avoid re-defining it
+ * each time an INQUIRY with standard data is received.
+ * scsi_version is initialized with -1 in scsi_generic_reset
+ * and scsi_disk_reset, making sure that we'll set the
+ * scsi_version after a reset. If the version field of the
+ * INQUIRY response somehow changes after a guest reboot,
+ * we'll be able to keep track of it.
+ *
+ * On SCSI-2 and older, first 3 bits of byte 2 is the
+ * ANSI-approved version, while on later versions the
+ * whole byte 2 contains the version. Check if we're dealing
+ * with a newer version and, in that case, assign the
+ * whole byte.
+ */
+ if (s->scsi_version == -1 && !(r->req.cmd.buf[1] & 0x01)) {
+ s->scsi_version = r->buf[2] & 0x07;
+ if (s->scsi_version > 2) {
+ s->scsi_version = r->buf[2];
+ }
+ }
+
+ if ((s->type == TYPE_DISK || s->type == TYPE_ZBC) &&
+ (r->req.cmd.buf[1] & 0x01)) {
+ page = r->req.cmd.buf[2];
+ if (page == 0xb0) {
+ uint64_t max_transfer = calculate_max_transfer(s);
+ stl_be_p(&r->buf[8], max_transfer);
+ /* Also take care of the opt xfer len. */
+ stl_be_p(&r->buf[12],
+ MIN_NON_ZERO(max_transfer, ldl_be_p(&r->buf[12])));
+ } else if (s->needs_vpd_bl_emulation && page == 0x00 && r->buflen >= 4) {
+ /*
+ * Now we're capable of supplying the VPD Block Limits
+ * response if the hardware can't. Add it in the INQUIRY
+ * Supported VPD pages response in case we are using the
+ * emulation for this device.
+ *
+ * This way, the guest kernel will be aware of the support
+ * and will use it to proper setup the SCSI device.
+ *
+ * VPD page numbers must be sorted, so insert 0xb0 at the
+ * right place with an in-place insert. When the while loop
+ * begins the device response is at r[0] to r[page_idx - 1].
+ */
+ page_idx = lduw_be_p(r->buf + 2) + 4;
+ page_idx = MIN(page_idx, r->buflen);
+ while (page_idx > 4 && r->buf[page_idx - 1] >= 0xb0) {
+ if (page_idx < r->buflen) {
+ r->buf[page_idx] = r->buf[page_idx - 1];
+ }
+ page_idx--;
+ }
+ if (page_idx < r->buflen) {
+ r->buf[page_idx] = 0xb0;
+ }
+ stw_be_p(r->buf + 2, lduw_be_p(r->buf + 2) + 1);
+
+ if (len < r->buflen) {
+ len++;
+ }
+ }
+ }
+ return len;
+}
+
+static int scsi_generic_emulate_block_limits(SCSIGenericReq *r, SCSIDevice *s)
+{
+ int len;
+ uint8_t buf[64];
+
+ SCSIBlockLimits bl = {
+ .max_io_sectors = calculate_max_transfer(s),
+ };
+
+ memset(r->buf, 0, r->buflen);
+ stb_p(buf, s->type);
+ stb_p(buf + 1, 0xb0);
+ len = scsi_emulate_block_limits(buf + 4, &bl);
+ assert(len <= sizeof(buf) - 4);
+ stw_be_p(buf + 2, len);
+
+ memcpy(r->buf, buf, MIN(r->buflen, len + 4));
+
+ r->io_header.sb_len_wr = 0;
+
+ /*
+ * We have valid contents in the reply buffer but the
+ * io_header can report a sense error coming from
+ * the hardware in scsi_command_complete_noio. Clean
+ * up the io_header to avoid reporting it.
+ */
+ r->io_header.driver_status = 0;
+ r->io_header.status = 0;
+
+ return r->buflen;
+}
+
+static void scsi_read_complete(void * opaque, int ret)
+{
+ SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+ SCSIDevice *s = r->req.dev;
+ int len;
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->conf.blk));
+
+ if (ret || r->req.io_canceled) {
+ scsi_command_complete_noio(r, ret);
+ goto done;
+ }
+
+ len = r->io_header.dxfer_len - r->io_header.resid;
+ trace_scsi_generic_read_complete(r->req.tag, len);
+
+ r->len = -1;
+
+ if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) {
+ SCSISense sense =
+ scsi_parse_sense_buf(r->req.sense, r->io_header.sb_len_wr);
+
+ /*
+ * Check if this is a VPD Block Limits request that
+ * resulted in sense error but would need emulation.
+ * In this case, emulate a valid VPD response.
+ */
+ if (sense.key == ILLEGAL_REQUEST &&
+ s->needs_vpd_bl_emulation &&
+ r->req.cmd.buf[0] == INQUIRY &&
+ (r->req.cmd.buf[1] & 0x01) &&
+ r->req.cmd.buf[2] == 0xb0) {
+ len = scsi_generic_emulate_block_limits(r, s);
+ /*
+ * It's okay to jup to req_complete: no need to
+ * let scsi_handle_inquiry_reply handle an
+ * INQUIRY VPD BL request we created manually.
+ */
+ }
+ if (sense.key) {
+ goto req_complete;
+ }
+ }
+
+ if (r->io_header.host_status != SCSI_HOST_OK ||
+ (r->io_header.driver_status & SG_ERR_DRIVER_TIMEOUT) ||
+ r->io_header.status != GOOD ||
+ len == 0) {
+ scsi_command_complete_noio(r, 0);
+ goto done;
+ }
+
+ /* Snoop READ CAPACITY output to set the blocksize. */
+ if (r->req.cmd.buf[0] == READ_CAPACITY_10 &&
+ (ldl_be_p(&r->buf[0]) != 0xffffffffU || s->max_lba == 0)) {
+ s->blocksize = ldl_be_p(&r->buf[4]);
+ s->max_lba = ldl_be_p(&r->buf[0]) & 0xffffffffULL;
+ } else if (r->req.cmd.buf[0] == SERVICE_ACTION_IN_16 &&
+ (r->req.cmd.buf[1] & 31) == SAI_READ_CAPACITY_16) {
+ s->blocksize = ldl_be_p(&r->buf[8]);
+ s->max_lba = ldq_be_p(&r->buf[0]);
+ }
+
+ /*
+ * Patch MODE SENSE device specific parameters if the BDS is opened
+ * readonly.
+ */
+ if ((s->type == TYPE_DISK || s->type == TYPE_TAPE || s->type == TYPE_ZBC) &&
+ !blk_is_writable(s->conf.blk) &&
+ (r->req.cmd.buf[0] == MODE_SENSE ||
+ r->req.cmd.buf[0] == MODE_SENSE_10) &&
+ (r->req.cmd.buf[1] & 0x8) == 0) {
+ if (r->req.cmd.buf[0] == MODE_SENSE) {
+ r->buf[2] |= 0x80;
+ } else {
+ r->buf[3] |= 0x80;
+ }
+ }
+ if (r->req.cmd.buf[0] == INQUIRY) {
+ len = scsi_handle_inquiry_reply(r, s, len);
+ }
+
+req_complete:
+ scsi_req_data(&r->req, len);
+ scsi_req_unref(&r->req);
+
+done:
+ aio_context_release(blk_get_aio_context(s->conf.blk));
+}
+
+/* Read more data from scsi device into buffer. */
+static void scsi_read_data(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+ SCSIDevice *s = r->req.dev;
+ int ret;
+
+ trace_scsi_generic_read_data(req->tag);
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ if (r->len == -1) {
+ scsi_command_complete_noio(r, 0);
+ return;
+ }
+
+ ret = execute_command(s->conf.blk, r, SG_DXFER_FROM_DEV,
+ scsi_read_complete);
+ if (ret < 0) {
+ scsi_command_complete_noio(r, ret);
+ }
+}
+
+static void scsi_write_complete(void * opaque, int ret)
+{
+ SCSIGenericReq *r = (SCSIGenericReq *)opaque;
+ SCSIDevice *s = r->req.dev;
+
+ trace_scsi_generic_write_complete(ret);
+
+ assert(r->req.aiocb != NULL);
+ r->req.aiocb = NULL;
+
+ aio_context_acquire(blk_get_aio_context(s->conf.blk));
+
+ if (ret || r->req.io_canceled) {
+ scsi_command_complete_noio(r, ret);
+ goto done;
+ }
+
+ if (r->req.cmd.buf[0] == MODE_SELECT && r->req.cmd.buf[4] == 12 &&
+ s->type == TYPE_TAPE) {
+ s->blocksize = (r->buf[9] << 16) | (r->buf[10] << 8) | r->buf[11];
+ trace_scsi_generic_write_complete_blocksize(s->blocksize);
+ }
+
+ scsi_command_complete_noio(r, ret);
+
+done:
+ aio_context_release(blk_get_aio_context(s->conf.blk));
+}
+
+/* Write data to a scsi device. Returns nonzero on failure.
+ The transfer may complete asynchronously. */
+static void scsi_write_data(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+ SCSIDevice *s = r->req.dev;
+ int ret;
+
+ trace_scsi_generic_write_data(req->tag);
+ if (r->len == 0) {
+ r->len = r->buflen;
+ scsi_req_data(&r->req, r->len);
+ return;
+ }
+
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ ret = execute_command(s->conf.blk, r, SG_DXFER_TO_DEV, scsi_write_complete);
+ if (ret < 0) {
+ scsi_command_complete_noio(r, ret);
+ }
+}
+
+/* Return a pointer to the data buffer. */
+static uint8_t *scsi_get_buf(SCSIRequest *req)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+
+ return r->buf;
+}
+
+static void scsi_generic_command_dump(uint8_t *cmd, int len)
+{
+ int i;
+ char *line_buffer, *p;
+
+ line_buffer = g_malloc(len * 5 + 1);
+
+ for (i = 0, p = line_buffer; i < len; i++) {
+ p += sprintf(p, " 0x%02x", cmd[i]);
+ }
+ trace_scsi_generic_send_command(line_buffer);
+
+ g_free(line_buffer);
+}
+
+/* Execute a scsi command. Returns the length of the data expected by the
+ command. This will be Positive for data transfers from the device
+ (eg. disk reads), negative for transfers to the device (eg. disk writes),
+ and zero if the command does not transfer any data. */
+
+static int32_t scsi_send_command(SCSIRequest *req, uint8_t *cmd)
+{
+ SCSIGenericReq *r = DO_UPCAST(SCSIGenericReq, req, req);
+ SCSIDevice *s = r->req.dev;
+ int ret;
+
+ if (trace_event_get_state_backends(TRACE_SCSI_GENERIC_SEND_COMMAND)) {
+ scsi_generic_command_dump(cmd, r->req.cmd.len);
+ }
+
+ if (r->req.cmd.xfer == 0) {
+ g_free(r->buf);
+ r->buflen = 0;
+ r->buf = NULL;
+ /* The request is used as the AIO opaque value, so add a ref. */
+ scsi_req_ref(&r->req);
+ ret = execute_command(s->conf.blk, r, SG_DXFER_NONE,
+ scsi_command_complete);
+ if (ret < 0) {
+ scsi_command_complete_noio(r, ret);
+ return 0;
+ }
+ return 0;
+ }
+
+ if (r->buflen != r->req.cmd.xfer) {
+ g_free(r->buf);
+ r->buf = g_malloc(r->req.cmd.xfer);
+ r->buflen = r->req.cmd.xfer;
+ }
+
+ memset(r->buf, 0, r->buflen);
+ r->len = r->req.cmd.xfer;
+ if (r->req.cmd.mode == SCSI_XFER_TO_DEV) {
+ r->len = 0;
+ return -r->req.cmd.xfer;
+ } else {
+ return r->req.cmd.xfer;
+ }
+}
+
+static int read_naa_id(const uint8_t *p, uint64_t *p_wwn)
+{
+ int i;
+
+ if ((p[1] & 0xF) == 3) {
+ /* NAA designator type */
+ if (p[3] != 8) {
+ return -EINVAL;
+ }
+ *p_wwn = ldq_be_p(p + 4);
+ return 0;
+ }
+
+ if ((p[1] & 0xF) == 8) {
+ /* SCSI name string designator type */
+ if (p[3] < 20 || memcmp(&p[4], "naa.", 4)) {
+ return -EINVAL;
+ }
+ if (p[3] > 20 && p[24] != ',') {
+ return -EINVAL;
+ }
+ *p_wwn = 0;
+ for (i = 8; i < 24; i++) {
+ char c = qemu_toupper(p[i]);
+ c -= (c >= '0' && c <= '9' ? '0' : 'A' - 10);
+ *p_wwn = (*p_wwn << 4) | c;
+ }
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+int scsi_SG_IO_FROM_DEV(BlockBackend *blk, uint8_t *cmd, uint8_t cmd_size,
+ uint8_t *buf, uint8_t buf_size, uint32_t timeout)
+{
+ sg_io_hdr_t io_header;
+ uint8_t sensebuf[8];
+ int ret;
+
+ memset(&io_header, 0, sizeof(io_header));
+ io_header.interface_id = 'S';
+ io_header.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_header.dxfer_len = buf_size;
+ io_header.dxferp = buf;
+ io_header.cmdp = cmd;
+ io_header.cmd_len = cmd_size;
+ io_header.mx_sb_len = sizeof(sensebuf);
+ io_header.sbp = sensebuf;
+ io_header.timeout = timeout * 1000;
+
+ trace_scsi_generic_ioctl_sgio_command(cmd[0], io_header.timeout);
+ ret = blk_ioctl(blk, SG_IO, &io_header);
+ if (ret < 0 || io_header.status ||
+ io_header.driver_status || io_header.host_status) {
+ trace_scsi_generic_ioctl_sgio_done(cmd[0], ret, io_header.status,
+ io_header.host_status);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Executes an INQUIRY request with EVPD set to retrieve the
+ * available VPD pages of the device. If the device does
+ * not support the Block Limits page (page 0xb0), set
+ * the needs_vpd_bl_emulation flag for future use.
+ */
+static void scsi_generic_set_vpd_bl_emulation(SCSIDevice *s)
+{
+ uint8_t cmd[6];
+ uint8_t buf[250];
+ uint8_t page_len;
+ int ret, i;
+
+ memset(cmd, 0, sizeof(cmd));
+ memset(buf, 0, sizeof(buf));
+ cmd[0] = INQUIRY;
+ cmd[1] = 1;
+ cmd[2] = 0x00;
+ cmd[4] = sizeof(buf);
+
+ ret = scsi_SG_IO_FROM_DEV(s->conf.blk, cmd, sizeof(cmd),
+ buf, sizeof(buf), s->io_timeout);
+ if (ret < 0) {
+ /*
+ * Do not assume anything if we can't retrieve the
+ * INQUIRY response to assert the VPD Block Limits
+ * support.
+ */
+ s->needs_vpd_bl_emulation = false;
+ return;
+ }
+
+ page_len = buf[3];
+ for (i = 4; i < MIN(sizeof(buf), page_len + 4); i++) {
+ if (buf[i] == 0xb0) {
+ s->needs_vpd_bl_emulation = false;
+ return;
+ }
+ }
+ s->needs_vpd_bl_emulation = true;
+}
+
+static void scsi_generic_read_device_identification(SCSIDevice *s)
+{
+ uint8_t cmd[6];
+ uint8_t buf[250];
+ int ret;
+ int i, len;
+
+ memset(cmd, 0, sizeof(cmd));
+ memset(buf, 0, sizeof(buf));
+ cmd[0] = INQUIRY;
+ cmd[1] = 1;
+ cmd[2] = 0x83;
+ cmd[4] = sizeof(buf);
+
+ ret = scsi_SG_IO_FROM_DEV(s->conf.blk, cmd, sizeof(cmd),
+ buf, sizeof(buf), s->io_timeout);
+ if (ret < 0) {
+ return;
+ }
+
+ len = MIN((buf[2] << 8) | buf[3], sizeof(buf) - 4);
+ for (i = 0; i + 3 <= len; ) {
+ const uint8_t *p = &buf[i + 4];
+ uint64_t wwn;
+
+ if (i + (p[3] + 4) > len) {
+ break;
+ }
+
+ if ((p[1] & 0x10) == 0) {
+ /* Associated with the logical unit */
+ if (read_naa_id(p, &wwn) == 0) {
+ s->wwn = wwn;
+ }
+ } else if ((p[1] & 0x10) == 0x10) {
+ /* Associated with the target port */
+ if (read_naa_id(p, &wwn) == 0) {
+ s->port_wwn = wwn;
+ }
+ }
+
+ i += p[3] + 4;
+ }
+}
+
+void scsi_generic_read_device_inquiry(SCSIDevice *s)
+{
+ scsi_generic_read_device_identification(s);
+ if (s->type == TYPE_DISK || s->type == TYPE_ZBC) {
+ scsi_generic_set_vpd_bl_emulation(s);
+ } else {
+ s->needs_vpd_bl_emulation = false;
+ }
+}
+
+static int get_stream_blocksize(BlockBackend *blk)
+{
+ uint8_t cmd[6];
+ uint8_t buf[12];
+ int ret;
+
+ memset(cmd, 0, sizeof(cmd));
+ memset(buf, 0, sizeof(buf));
+ cmd[0] = MODE_SENSE;
+ cmd[4] = sizeof(buf);
+
+ ret = scsi_SG_IO_FROM_DEV(blk, cmd, sizeof(cmd), buf, sizeof(buf), 6);
+ if (ret < 0) {
+ return -1;
+ }
+
+ return (buf[9] << 16) | (buf[10] << 8) | buf[11];
+}
+
+static void scsi_generic_reset(DeviceState *dev)
+{
+ SCSIDevice *s = SCSI_DEVICE(dev);
+
+ s->scsi_version = s->default_scsi_version;
+ scsi_device_purge_requests(s, SENSE_CODE(RESET));
+}
+
+static void scsi_generic_realize(SCSIDevice *s, Error **errp)
+{
+ int rc;
+ int sg_version;
+ struct sg_scsi_id scsiid;
+
+ if (!s->conf.blk) {
+ error_setg(errp, "drive property not set");
+ return;
+ }
+
+ if (blk_get_on_error(s->conf.blk, 0) != BLOCKDEV_ON_ERROR_ENOSPC &&
+ blk_get_on_error(s->conf.blk, 0) != BLOCKDEV_ON_ERROR_REPORT) {
+ error_setg(errp, "Device doesn't support drive option werror");
+ return;
+ }
+ if (blk_get_on_error(s->conf.blk, 1) != BLOCKDEV_ON_ERROR_REPORT) {
+ error_setg(errp, "Device doesn't support drive option rerror");
+ return;
+ }
+
+ /* check we are using a driver managing SG_IO (version 3 and after */
+ rc = blk_ioctl(s->conf.blk, SG_GET_VERSION_NUM, &sg_version);
+ if (rc < 0) {
+ error_setg_errno(errp, -rc, "cannot get SG_IO version number");
+ if (rc != -EPERM) {
+ error_append_hint(errp, "Is this a SCSI device?\n");
+ }
+ return;
+ }
+ if (sg_version < 30000) {
+ error_setg(errp, "scsi generic interface too old");
+ return;
+ }
+
+ /* get LUN of the /dev/sg? */
+ if (blk_ioctl(s->conf.blk, SG_GET_SCSI_ID, &scsiid)) {
+ error_setg(errp, "SG_GET_SCSI_ID ioctl failed");
+ return;
+ }
+ if (!blkconf_apply_backend_options(&s->conf,
+ !blk_supports_write_perm(s->conf.blk),
+ true, errp)) {
+ return;
+ }
+
+ /* define device state */
+ s->type = scsiid.scsi_type;
+ trace_scsi_generic_realize_type(s->type);
+
+ switch (s->type) {
+ case TYPE_TAPE:
+ s->blocksize = get_stream_blocksize(s->conf.blk);
+ if (s->blocksize == -1) {
+ s->blocksize = 0;
+ }
+ break;
+
+ /* Make a guess for block devices, we'll fix it when the guest sends.
+ * READ CAPACITY. If they don't, they likely would assume these sizes
+ * anyway. (TODO: they could also send MODE SENSE).
+ */
+ case TYPE_ROM:
+ case TYPE_WORM:
+ s->blocksize = 2048;
+ break;
+ default:
+ s->blocksize = 512;
+ break;
+ }
+
+ trace_scsi_generic_realize_blocksize(s->blocksize);
+
+ /* Only used by scsi-block, but initialize it nevertheless to be clean. */
+ s->default_scsi_version = -1;
+ s->io_timeout = DEFAULT_IO_TIMEOUT;
+ scsi_generic_read_device_inquiry(s);
+}
+
+const SCSIReqOps scsi_generic_req_ops = {
+ .size = sizeof(SCSIGenericReq),
+ .free_req = scsi_free_request,
+ .send_command = scsi_send_command,
+ .read_data = scsi_read_data,
+ .write_data = scsi_write_data,
+ .get_buf = scsi_get_buf,
+ .load_request = scsi_generic_load_request,
+ .save_request = scsi_generic_save_request,
+};
+
+static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun,
+ uint8_t *buf, void *hba_private)
+{
+ return scsi_req_alloc(&scsi_generic_req_ops, d, tag, lun, hba_private);
+}
+
+static Property scsi_generic_properties[] = {
+ DEFINE_PROP_DRIVE("drive", SCSIDevice, conf.blk),
+ DEFINE_PROP_BOOL("share-rw", SCSIDevice, conf.share_rw, false),
+ DEFINE_PROP_UINT32("io_timeout", SCSIDevice, io_timeout,
+ DEFAULT_IO_TIMEOUT),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static int scsi_generic_parse_cdb(SCSIDevice *dev, SCSICommand *cmd,
+ uint8_t *buf, size_t buf_len,
+ void *hba_private)
+{
+ return scsi_bus_parse_cdb(dev, cmd, buf, buf_len, hba_private);
+}
+
+static void scsi_generic_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
+
+ sc->realize = scsi_generic_realize;
+ sc->alloc_req = scsi_new_request;
+ sc->parse_cdb = scsi_generic_parse_cdb;
+ dc->fw_name = "disk";
+ dc->desc = "pass through generic scsi device (/dev/sg*)";
+ dc->reset = scsi_generic_reset;
+ device_class_set_props(dc, scsi_generic_properties);
+ dc->vmsd = &vmstate_scsi_device;
+}
+
+static const TypeInfo scsi_generic_info = {
+ .name = "scsi-generic",
+ .parent = TYPE_SCSI_DEVICE,
+ .instance_size = sizeof(SCSIDevice),
+ .class_init = scsi_generic_class_initfn,
+};
+
+static void scsi_generic_register_types(void)
+{
+ type_register_static(&scsi_generic_info);
+}
+
+type_init(scsi_generic_register_types)
+
+#endif /* __linux__ */
diff --git a/hw/scsi/spapr_vscsi.c b/hw/scsi/spapr_vscsi.c
new file mode 100644
index 00000000..5bbbef64
--- /dev/null
+++ b/hw/scsi/spapr_vscsi.c
@@ -0,0 +1,1301 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * PAPR Virtual SCSI, aka ibmvscsi
+ *
+ * Copyright (c) 2010,2011 Benjamin Herrenschmidt, IBM Corporation.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * TODO:
+ *
+ * - Cleanups :-)
+ * - Sort out better how to assign devices to VSCSI instances
+ * - Fix residual counts
+ * - Add indirect descriptors support
+ * - Maybe do autosense (PAPR seems to mandate it, linux doesn't care)
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "hw/scsi/scsi.h"
+#include "migration/vmstate.h"
+#include "scsi/constants.h"
+#include "srp.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+#include "hw/qdev-properties.h"
+#include "viosrp.h"
+#include "trace.h"
+
+#include <libfdt.h>
+#include "qom/object.h"
+
+/*
+ * Virtual SCSI device
+ */
+
+/* Random numbers */
+#define VSCSI_MAX_SECTORS 4096
+#define VSCSI_REQ_LIMIT 24
+
+/* Maximum size of a IU payload */
+#define SRP_MAX_IU_DATA_LEN (SRP_MAX_IU_LEN - sizeof(union srp_iu))
+#define SRP_RSP_SENSE_DATA_LEN 18
+
+#define SRP_REPORT_LUNS_WLUN 0xc10100000000000ULL
+
+typedef union vscsi_crq {
+ struct viosrp_crq s;
+ uint8_t raw[16];
+} vscsi_crq;
+
+typedef struct vscsi_req {
+ vscsi_crq crq;
+ uint8_t viosrp_iu_buf[SRP_MAX_IU_LEN];
+
+ /* SCSI request tracking */
+ SCSIRequest *sreq;
+ uint32_t qtag; /* qemu tag != srp tag */
+ bool active;
+ bool writing;
+ bool dma_error;
+ uint32_t data_len;
+ uint32_t senselen;
+ uint8_t sense[SCSI_SENSE_BUF_SIZE];
+
+ /* RDMA related bits */
+ uint8_t dma_fmt;
+ uint16_t local_desc;
+ uint16_t total_desc;
+ uint16_t cdb_offset;
+ uint16_t cur_desc_num;
+ uint16_t cur_desc_offset;
+} vscsi_req;
+
+#define TYPE_VIO_SPAPR_VSCSI_DEVICE "spapr-vscsi"
+OBJECT_DECLARE_SIMPLE_TYPE(VSCSIState, VIO_SPAPR_VSCSI_DEVICE)
+
+struct VSCSIState {
+ SpaprVioDevice vdev;
+ SCSIBus bus;
+ vscsi_req reqs[VSCSI_REQ_LIMIT];
+};
+
+static union viosrp_iu *req_iu(vscsi_req *req)
+{
+ return (union viosrp_iu *)req->viosrp_iu_buf;
+}
+
+static struct vscsi_req *vscsi_get_req(VSCSIState *s)
+{
+ vscsi_req *req;
+ int i;
+
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ req = &s->reqs[i];
+ if (!req->active) {
+ memset(req, 0, sizeof(*req));
+ req->qtag = i;
+ req->active = 1;
+ return req;
+ }
+ }
+ return NULL;
+}
+
+static struct vscsi_req *vscsi_find_req(VSCSIState *s, uint64_t srp_tag)
+{
+ vscsi_req *req;
+ int i;
+
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ req = &s->reqs[i];
+ if (req_iu(req)->srp.cmd.tag == srp_tag) {
+ return req;
+ }
+ }
+ return NULL;
+}
+
+static void vscsi_put_req(vscsi_req *req)
+{
+ if (req->sreq != NULL) {
+ scsi_req_unref(req->sreq);
+ }
+ req->sreq = NULL;
+ req->active = 0;
+}
+
+static SCSIDevice *vscsi_device_find(SCSIBus *bus, uint64_t srp_lun, int *lun)
+{
+ int channel = 0, id = 0;
+
+retry:
+ switch (srp_lun >> 62) {
+ case 0:
+ if ((srp_lun >> 56) != 0) {
+ channel = (srp_lun >> 56) & 0x3f;
+ id = (srp_lun >> 48) & 0xff;
+ srp_lun <<= 16;
+ goto retry;
+ }
+ *lun = (srp_lun >> 48) & 0xff;
+ break;
+
+ case 1:
+ *lun = (srp_lun >> 48) & 0x3fff;
+ break;
+ case 2:
+ channel = (srp_lun >> 53) & 0x7;
+ id = (srp_lun >> 56) & 0x3f;
+ *lun = (srp_lun >> 48) & 0x1f;
+ break;
+ case 3:
+ *lun = -1;
+ return NULL;
+ default:
+ abort();
+ }
+
+ return scsi_device_find(bus, channel, id, *lun);
+}
+
+static int vscsi_send_iu(VSCSIState *s, vscsi_req *req,
+ uint64_t length, uint8_t format)
+{
+ long rc, rc1;
+
+ assert(length <= SRP_MAX_IU_LEN);
+
+ /* First copy the SRP */
+ rc = spapr_vio_dma_write(&s->vdev, req->crq.s.IU_data_ptr,
+ &req->viosrp_iu_buf, length);
+ if (rc) {
+ fprintf(stderr, "vscsi_send_iu: DMA write failure !\n");
+ }
+
+ req->crq.s.valid = 0x80;
+ req->crq.s.format = format;
+ req->crq.s.reserved = 0x00;
+ req->crq.s.timeout = cpu_to_be16(0x0000);
+ req->crq.s.IU_length = cpu_to_be16(length);
+ req->crq.s.IU_data_ptr = req_iu(req)->srp.rsp.tag; /* right byte order */
+
+ if (rc == 0) {
+ req->crq.s.status = VIOSRP_OK;
+ } else {
+ req->crq.s.status = VIOSRP_ADAPTER_FAIL;
+ }
+
+ rc1 = spapr_vio_send_crq(&s->vdev, req->crq.raw);
+ if (rc1) {
+ fprintf(stderr, "vscsi_send_iu: Error sending response\n");
+ return rc1;
+ }
+
+ return rc;
+}
+
+static void vscsi_makeup_sense(VSCSIState *s, vscsi_req *req,
+ uint8_t key, uint8_t asc, uint8_t ascq)
+{
+ req->senselen = SRP_RSP_SENSE_DATA_LEN;
+
+ /* Valid bit and 'current errors' */
+ req->sense[0] = (0x1 << 7 | 0x70);
+ /* Sense key */
+ req->sense[2] = key;
+ /* Additional sense length */
+ req->sense[7] = 0xa; /* 10 bytes */
+ /* Additional sense code */
+ req->sense[12] = asc;
+ req->sense[13] = ascq;
+}
+
+static int vscsi_send_rsp(VSCSIState *s, vscsi_req *req,
+ uint8_t status, int32_t res_in, int32_t res_out)
+{
+ union viosrp_iu *iu = req_iu(req);
+ uint64_t tag = iu->srp.rsp.tag;
+ int total_len = sizeof(iu->srp.rsp);
+ uint8_t sol_not = iu->srp.cmd.sol_not;
+
+ trace_spapr_vscsi_send_rsp(status, res_in, res_out);
+
+ memset(iu, 0, sizeof(struct srp_rsp));
+ iu->srp.rsp.opcode = SRP_RSP;
+ iu->srp.rsp.req_lim_delta = cpu_to_be32(1);
+ iu->srp.rsp.tag = tag;
+
+ /* Handle residuals */
+ if (res_in < 0) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DIUNDER;
+ res_in = -res_in;
+ } else if (res_in) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DIOVER;
+ }
+ if (res_out < 0) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DOUNDER;
+ res_out = -res_out;
+ } else if (res_out) {
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_DOOVER;
+ }
+ iu->srp.rsp.data_in_res_cnt = cpu_to_be32(res_in);
+ iu->srp.rsp.data_out_res_cnt = cpu_to_be32(res_out);
+
+ /* We don't do response data */
+ /* iu->srp.rsp.flags &= ~SRP_RSP_FLAG_RSPVALID; */
+ iu->srp.rsp.resp_data_len = cpu_to_be32(0);
+
+ /* Handle success vs. failure */
+ iu->srp.rsp.status = status;
+ if (status) {
+ iu->srp.rsp.sol_not = (sol_not & 0x04) >> 2;
+ if (req->senselen) {
+ int sense_data_len = MIN(req->senselen, SRP_MAX_IU_DATA_LEN);
+
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_SNSVALID;
+ iu->srp.rsp.sense_data_len = cpu_to_be32(sense_data_len);
+ memcpy(iu->srp.rsp.data, req->sense, sense_data_len);
+ total_len += sense_data_len;
+ }
+ } else {
+ iu->srp.rsp.sol_not = (sol_not & 0x02) >> 1;
+ }
+
+ vscsi_send_iu(s, req, total_len, VIOSRP_SRP_FORMAT);
+ return 0;
+}
+
+static inline struct srp_direct_buf vscsi_swap_desc(struct srp_direct_buf desc)
+{
+ desc.va = be64_to_cpu(desc.va);
+ desc.len = be32_to_cpu(desc.len);
+ return desc;
+}
+
+static int vscsi_fetch_desc(VSCSIState *s, struct vscsi_req *req,
+ unsigned n, unsigned buf_offset,
+ struct srp_direct_buf *ret)
+{
+ struct srp_cmd *cmd = &req_iu(req)->srp.cmd;
+
+ switch (req->dma_fmt) {
+ case SRP_NO_DATA_DESC: {
+ trace_spapr_vscsi_fetch_desc_no_data();
+ return 0;
+ }
+ case SRP_DATA_DESC_DIRECT: {
+ memcpy(ret, cmd->add_data + req->cdb_offset, sizeof(*ret));
+ assert(req->cur_desc_num == 0);
+ trace_spapr_vscsi_fetch_desc_direct();
+ break;
+ }
+ case SRP_DATA_DESC_INDIRECT: {
+ struct srp_indirect_buf *tmp = (struct srp_indirect_buf *)
+ (cmd->add_data + req->cdb_offset);
+ if (n < req->local_desc) {
+ *ret = tmp->desc_list[n];
+ trace_spapr_vscsi_fetch_desc_indirect(req->qtag, n,
+ req->local_desc);
+ } else if (n < req->total_desc) {
+ int rc;
+ struct srp_direct_buf tbl_desc = vscsi_swap_desc(tmp->table_desc);
+ unsigned desc_offset = n * sizeof(struct srp_direct_buf);
+
+ if (desc_offset >= tbl_desc.len) {
+ trace_spapr_vscsi_fetch_desc_out_of_range(n, desc_offset);
+ return -1;
+ }
+ rc = spapr_vio_dma_read(&s->vdev, tbl_desc.va + desc_offset,
+ ret, sizeof(struct srp_direct_buf));
+ if (rc) {
+ trace_spapr_vscsi_fetch_desc_dma_read_error(rc);
+ return -1;
+ }
+ trace_spapr_vscsi_fetch_desc_indirect_seg_ext(req->qtag, n,
+ req->total_desc,
+ tbl_desc.va,
+ tbl_desc.len);
+ } else {
+ trace_spapr_vscsi_fetch_desc_out_of_desc();
+ return 0;
+ }
+ break;
+ }
+ default:
+ fprintf(stderr, "VSCSI: Unknown format %x\n", req->dma_fmt);
+ return -1;
+ }
+
+ *ret = vscsi_swap_desc(*ret);
+ if (buf_offset > ret->len) {
+ trace_spapr_vscsi_fetch_desc_out_of_desc_boundary(buf_offset,
+ req->cur_desc_num,
+ ret->len);
+ return -1;
+ }
+ ret->va += buf_offset;
+ ret->len -= buf_offset;
+
+ trace_spapr_vscsi_fetch_desc_done(req->cur_desc_num, req->cur_desc_offset,
+ ret->va, ret->len);
+
+ return ret->len ? 1 : 0;
+}
+
+static int vscsi_srp_direct_data(VSCSIState *s, vscsi_req *req,
+ uint8_t *buf, uint32_t len)
+{
+ struct srp_direct_buf md;
+ uint32_t llen;
+ int rc = 0;
+
+ rc = vscsi_fetch_desc(s, req, req->cur_desc_num, req->cur_desc_offset, &md);
+ if (rc < 0) {
+ return -1;
+ } else if (rc == 0) {
+ return 0;
+ }
+
+ llen = MIN(len, md.len);
+ if (llen) {
+ if (req->writing) { /* writing = to device = reading from memory */
+ rc = spapr_vio_dma_read(&s->vdev, md.va, buf, llen);
+ } else {
+ rc = spapr_vio_dma_write(&s->vdev, md.va, buf, llen);
+ }
+ }
+
+ if (rc) {
+ return -1;
+ }
+ req->cur_desc_offset += llen;
+
+ return llen;
+}
+
+static int vscsi_srp_indirect_data(VSCSIState *s, vscsi_req *req,
+ uint8_t *buf, uint32_t len)
+{
+ struct srp_direct_buf md;
+ int rc = 0;
+ uint32_t llen, total = 0;
+
+ trace_spapr_vscsi_srp_indirect_data(len);
+
+ /* While we have data ... */
+ while (len) {
+ rc = vscsi_fetch_desc(s, req, req->cur_desc_num, req->cur_desc_offset, &md);
+ if (rc < 0) {
+ return -1;
+ } else if (rc == 0) {
+ break;
+ }
+
+ /* Perform transfer */
+ llen = MIN(len, md.len);
+ if (req->writing) { /* writing = to device = reading from memory */
+ rc = spapr_vio_dma_read(&s->vdev, md.va, buf, llen);
+ } else {
+ rc = spapr_vio_dma_write(&s->vdev, md.va, buf, llen);
+ }
+ if (rc) {
+ trace_spapr_vscsi_srp_indirect_data_rw(req->writing, rc);
+ break;
+ }
+ trace_spapr_vscsi_srp_indirect_data_buf(buf[0], buf[1], buf[2], buf[3]);
+
+ len -= llen;
+ buf += llen;
+
+ total += llen;
+
+ /* Update current position in the current descriptor */
+ req->cur_desc_offset += llen;
+ if (md.len == llen) {
+ /* Go to the next descriptor if the current one finished */
+ ++req->cur_desc_num;
+ req->cur_desc_offset = 0;
+ }
+ }
+
+ return rc ? -1 : total;
+}
+
+static int vscsi_srp_transfer_data(VSCSIState *s, vscsi_req *req,
+ int writing, uint8_t *buf, uint32_t len)
+{
+ int err = 0;
+
+ switch (req->dma_fmt) {
+ case SRP_NO_DATA_DESC:
+ trace_spapr_vscsi_srp_transfer_data(len);
+ break;
+ case SRP_DATA_DESC_DIRECT:
+ err = vscsi_srp_direct_data(s, req, buf, len);
+ break;
+ case SRP_DATA_DESC_INDIRECT:
+ err = vscsi_srp_indirect_data(s, req, buf, len);
+ break;
+ }
+ return err;
+}
+
+/* Bits from linux srp */
+static int data_out_desc_size(struct srp_cmd *cmd)
+{
+ int size = 0;
+ uint8_t fmt = cmd->buf_fmt >> 4;
+
+ switch (fmt) {
+ case SRP_NO_DATA_DESC:
+ break;
+ case SRP_DATA_DESC_DIRECT:
+ size = sizeof(struct srp_direct_buf);
+ break;
+ case SRP_DATA_DESC_INDIRECT:
+ size = sizeof(struct srp_indirect_buf) +
+ sizeof(struct srp_direct_buf)*cmd->data_out_desc_cnt;
+ break;
+ default:
+ break;
+ }
+ return size;
+}
+
+static int vscsi_preprocess_desc(vscsi_req *req)
+{
+ struct srp_cmd *cmd = &req_iu(req)->srp.cmd;
+
+ req->cdb_offset = cmd->add_cdb_len & ~3;
+
+ if (req->writing) {
+ req->dma_fmt = cmd->buf_fmt >> 4;
+ } else {
+ req->cdb_offset += data_out_desc_size(cmd);
+ req->dma_fmt = cmd->buf_fmt & ((1U << 4) - 1);
+ }
+
+ switch (req->dma_fmt) {
+ case SRP_NO_DATA_DESC:
+ break;
+ case SRP_DATA_DESC_DIRECT:
+ req->total_desc = req->local_desc = 1;
+ break;
+ case SRP_DATA_DESC_INDIRECT: {
+ struct srp_indirect_buf *ind_tmp = (struct srp_indirect_buf *)
+ (cmd->add_data + req->cdb_offset);
+
+ req->total_desc = be32_to_cpu(ind_tmp->table_desc.len) /
+ sizeof(struct srp_direct_buf);
+ req->local_desc = req->writing ? cmd->data_out_desc_cnt :
+ cmd->data_in_desc_cnt;
+ break;
+ }
+ default:
+ fprintf(stderr,
+ "vscsi_preprocess_desc: Unknown format %x\n", req->dma_fmt);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Callback to indicate that the SCSI layer has completed a transfer. */
+static void vscsi_transfer_data(SCSIRequest *sreq, uint32_t len)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(sreq->bus->qbus.parent);
+ vscsi_req *req = sreq->hba_private;
+ uint8_t *buf;
+ int rc = 0;
+
+ trace_spapr_vscsi_transfer_data(sreq->tag, len, req);
+ if (req == NULL) {
+ fprintf(stderr, "VSCSI: Can't find request for tag 0x%x\n", sreq->tag);
+ return;
+ }
+
+ if (len) {
+ buf = scsi_req_get_buf(sreq);
+ rc = vscsi_srp_transfer_data(s, req, req->writing, buf, len);
+ }
+ if (rc < 0) {
+ fprintf(stderr, "VSCSI: RDMA error rc=%d!\n", rc);
+ req->dma_error = true;
+ scsi_req_cancel(req->sreq);
+ return;
+ }
+
+ /* Start next chunk */
+ req->data_len -= rc;
+ scsi_req_continue(sreq);
+}
+
+/* Callback to indicate that the SCSI layer has completed a transfer. */
+static void vscsi_command_complete(SCSIRequest *sreq, size_t resid)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(sreq->bus->qbus.parent);
+ vscsi_req *req = sreq->hba_private;
+ int32_t res_in = 0, res_out = 0;
+
+ trace_spapr_vscsi_command_complete(sreq->tag, sreq->status, req);
+ if (req == NULL) {
+ fprintf(stderr, "VSCSI: Can't find request for tag 0x%x\n", sreq->tag);
+ return;
+ }
+
+ if (sreq->status == CHECK_CONDITION) {
+ req->senselen = scsi_req_get_sense(req->sreq, req->sense,
+ sizeof(req->sense));
+ trace_spapr_vscsi_command_complete_sense_data1(req->senselen,
+ req->sense[0], req->sense[1], req->sense[2], req->sense[3],
+ req->sense[4], req->sense[5], req->sense[6], req->sense[7]);
+ trace_spapr_vscsi_command_complete_sense_data2(
+ req->sense[8], req->sense[9], req->sense[10], req->sense[11],
+ req->sense[12], req->sense[13], req->sense[14], req->sense[15]);
+ }
+
+ trace_spapr_vscsi_command_complete_status(sreq->status);
+ if (sreq->status == 0) {
+ /* We handle overflows, not underflows for normal commands,
+ * but hopefully nobody cares
+ */
+ if (req->writing) {
+ res_out = req->data_len;
+ } else {
+ res_in = req->data_len;
+ }
+ }
+ vscsi_send_rsp(s, req, sreq->status, res_in, res_out);
+ vscsi_put_req(req);
+}
+
+static void vscsi_request_cancelled(SCSIRequest *sreq)
+{
+ vscsi_req *req = sreq->hba_private;
+
+ if (req->dma_error) {
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(sreq->bus->qbus.parent);
+
+ vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ }
+ vscsi_put_req(req);
+}
+
+static const VMStateDescription vmstate_spapr_vscsi_req = {
+ .name = "spapr_vscsi_req",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER(crq.raw, vscsi_req),
+ VMSTATE_BUFFER(viosrp_iu_buf, vscsi_req),
+ VMSTATE_UINT32(qtag, vscsi_req),
+ VMSTATE_BOOL(active, vscsi_req),
+ VMSTATE_UINT32(data_len, vscsi_req),
+ VMSTATE_BOOL(writing, vscsi_req),
+ VMSTATE_UINT32(senselen, vscsi_req),
+ VMSTATE_BUFFER(sense, vscsi_req),
+ VMSTATE_UINT8(dma_fmt, vscsi_req),
+ VMSTATE_UINT16(local_desc, vscsi_req),
+ VMSTATE_UINT16(total_desc, vscsi_req),
+ VMSTATE_UINT16(cdb_offset, vscsi_req),
+ /*Restart SCSI request from the beginning for now */
+ /*VMSTATE_UINT16(cur_desc_num, vscsi_req),
+ VMSTATE_UINT16(cur_desc_offset, vscsi_req),*/
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void vscsi_save_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ vscsi_req *req = sreq->hba_private;
+ assert(req->active);
+
+ vmstate_save_state(f, &vmstate_spapr_vscsi_req, req, NULL);
+
+ trace_spapr_vscsi_save_request(req->qtag, req->cur_desc_num,
+ req->cur_desc_offset);
+}
+
+static void *vscsi_load_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ SCSIBus *bus = sreq->bus;
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(bus->qbus.parent);
+ vscsi_req *req;
+ int rc;
+
+ assert(sreq->tag < VSCSI_REQ_LIMIT);
+ req = &s->reqs[sreq->tag];
+ assert(!req->active);
+
+ memset(req, 0, sizeof(*req));
+ rc = vmstate_load_state(f, &vmstate_spapr_vscsi_req, req, 1);
+ if (rc) {
+ fprintf(stderr, "VSCSI: failed loading request tag#%u\n", sreq->tag);
+ return NULL;
+ }
+ assert(req->active);
+
+ req->sreq = scsi_req_ref(sreq);
+
+ trace_spapr_vscsi_load_request(req->qtag, req->cur_desc_num,
+ req->cur_desc_offset);
+
+ return req;
+}
+
+static void vscsi_process_login(VSCSIState *s, vscsi_req *req)
+{
+ union viosrp_iu *iu = req_iu(req);
+ struct srp_login_rsp *rsp = &iu->srp.login_rsp;
+ uint64_t tag = iu->srp.rsp.tag;
+
+ trace_spapr_vscsi_process_login();
+
+ /* TODO handle case that requested size is wrong and
+ * buffer format is wrong
+ */
+ memset(iu, 0, sizeof(struct srp_login_rsp));
+ rsp->opcode = SRP_LOGIN_RSP;
+ /* Don't advertise quite as many request as we support to
+ * keep room for management stuff etc...
+ */
+ rsp->req_lim_delta = cpu_to_be32(VSCSI_REQ_LIMIT-2);
+ rsp->tag = tag;
+ rsp->max_it_iu_len = cpu_to_be32(SRP_MAX_IU_LEN);
+ rsp->max_ti_iu_len = cpu_to_be32(SRP_MAX_IU_LEN);
+ /* direct and indirect */
+ rsp->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT | SRP_BUF_FORMAT_INDIRECT);
+
+ vscsi_send_iu(s, req, sizeof(*rsp), VIOSRP_SRP_FORMAT);
+}
+
+static void vscsi_inquiry_no_target(VSCSIState *s, vscsi_req *req)
+{
+ uint8_t *cdb = req_iu(req)->srp.cmd.cdb;
+ uint8_t resp_data[36];
+ int rc, len, alen;
+
+ /* We don't do EVPD. Also check that page_code is 0 */
+ if ((cdb[1] & 0x01) || cdb[2] != 0) {
+ /* Send INVALID FIELD IN CDB */
+ vscsi_makeup_sense(s, req, ILLEGAL_REQUEST, 0x24, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ return;
+ }
+ alen = cdb[3];
+ alen = (alen << 8) | cdb[4];
+ len = MIN(alen, 36);
+
+ /* Fake up inquiry using PQ=3 */
+ memset(resp_data, 0, 36);
+ resp_data[0] = 0x7f; /* Not capable of supporting a device here */
+ resp_data[2] = 0x06; /* SPS-4 */
+ resp_data[3] = 0x02; /* Resp data format */
+ resp_data[4] = 36 - 5; /* Additional length */
+ resp_data[7] = 0x10; /* Sync transfers */
+ memcpy(&resp_data[16], "QEMU EMPTY ", 16);
+ memcpy(&resp_data[8], "QEMU ", 8);
+
+ req->writing = 0;
+ vscsi_preprocess_desc(req);
+ rc = vscsi_srp_transfer_data(s, req, 0, resp_data, len);
+ if (rc < 0) {
+ vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ } else {
+ vscsi_send_rsp(s, req, 0, 36 - rc, 0);
+ }
+}
+
+static void vscsi_report_luns(VSCSIState *s, vscsi_req *req)
+{
+ BusChild *kid;
+ int i, len, n, rc;
+ uint8_t *resp_data;
+ bool found_lun0;
+
+ n = 0;
+ found_lun0 = false;
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *dev = SCSI_DEVICE(kid->child);
+
+ n += 8;
+ if (dev->channel == 0 && dev->id == 0 && dev->lun == 0) {
+ found_lun0 = true;
+ }
+ }
+ if (!found_lun0) {
+ n += 8;
+ }
+ len = n+8;
+
+ resp_data = g_malloc0(len);
+ stl_be_p(resp_data, n);
+ i = found_lun0 ? 8 : 16;
+ QTAILQ_FOREACH(kid, &s->bus.qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ SCSIDevice *dev = SCSI_DEVICE(qdev);
+
+ if (dev->id == 0 && dev->channel == 0) {
+ resp_data[i] = 0; /* Use simple LUN for 0 (SAM5 4.7.7.1) */
+ } else {
+ resp_data[i] = (2 << 6); /* Otherwise LUN addressing (4.7.7.4) */
+ }
+ resp_data[i] |= dev->id;
+ resp_data[i+1] = (dev->channel << 5);
+ resp_data[i+1] |= dev->lun;
+ i += 8;
+ }
+
+ vscsi_preprocess_desc(req);
+ rc = vscsi_srp_transfer_data(s, req, 0, resp_data, len);
+ g_free(resp_data);
+ if (rc < 0) {
+ vscsi_makeup_sense(s, req, HARDWARE_ERROR, 0, 0);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ } else {
+ vscsi_send_rsp(s, req, 0, len - rc, 0);
+ }
+}
+
+static int vscsi_queue_cmd(VSCSIState *s, vscsi_req *req)
+{
+ union srp_iu *srp = &req_iu(req)->srp;
+ SCSIDevice *sdev;
+ int n, lun;
+ size_t cdb_len = sizeof (srp->cmd.cdb) + (srp->cmd.add_cdb_len & ~3);
+
+ if ((srp->cmd.lun == 0 || be64_to_cpu(srp->cmd.lun) == SRP_REPORT_LUNS_WLUN)
+ && srp->cmd.cdb[0] == REPORT_LUNS) {
+ vscsi_report_luns(s, req);
+ return 0;
+ }
+
+ sdev = vscsi_device_find(&s->bus, be64_to_cpu(srp->cmd.lun), &lun);
+ if (!sdev) {
+ trace_spapr_vscsi_queue_cmd_no_drive(be64_to_cpu(srp->cmd.lun));
+ if (srp->cmd.cdb[0] == INQUIRY) {
+ vscsi_inquiry_no_target(s, req);
+ } else {
+ vscsi_makeup_sense(s, req, ILLEGAL_REQUEST, 0x24, 0x00);
+ vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0);
+ } return 1;
+ }
+
+ req->sreq = scsi_req_new(sdev, req->qtag, lun, srp->cmd.cdb, cdb_len, req);
+ n = scsi_req_enqueue(req->sreq);
+
+ trace_spapr_vscsi_queue_cmd(req->qtag, srp->cmd.cdb[0],
+ scsi_command_name(srp->cmd.cdb[0]), lun, n);
+
+ if (n) {
+ /* Transfer direction must be set before preprocessing the
+ * descriptors
+ */
+ req->writing = (n < 1);
+
+ /* Preprocess RDMA descriptors */
+ vscsi_preprocess_desc(req);
+
+ /* Get transfer direction and initiate transfer */
+ if (n > 0) {
+ req->data_len = n;
+ } else if (n < 0) {
+ req->data_len = -n;
+ }
+ scsi_req_continue(req->sreq);
+ }
+ /* Don't touch req here, it may have been recycled already */
+
+ return 0;
+}
+
+static int vscsi_process_tsk_mgmt(VSCSIState *s, vscsi_req *req)
+{
+ union viosrp_iu *iu = req_iu(req);
+ vscsi_req *tmpreq;
+ int i, lun = 0, resp = SRP_TSK_MGMT_COMPLETE;
+ SCSIDevice *d;
+ uint64_t tag = iu->srp.rsp.tag;
+ uint8_t sol_not = iu->srp.cmd.sol_not;
+
+ trace_spapr_vscsi_process_tsk_mgmt(iu->srp.tsk_mgmt.tsk_mgmt_func);
+ d = vscsi_device_find(&s->bus,
+ be64_to_cpu(req_iu(req)->srp.tsk_mgmt.lun), &lun);
+ if (!d) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ } else {
+ switch (iu->srp.tsk_mgmt.tsk_mgmt_func) {
+ case SRP_TSK_ABORT_TASK:
+ if (d->lun != lun) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+
+ tmpreq = vscsi_find_req(s, req_iu(req)->srp.tsk_mgmt.task_tag);
+ if (tmpreq && tmpreq->sreq) {
+ assert(tmpreq->sreq->hba_private);
+ scsi_req_cancel(tmpreq->sreq);
+ }
+ break;
+
+ case SRP_TSK_LUN_RESET:
+ if (d->lun != lun) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+
+ device_cold_reset(&d->qdev);
+ break;
+
+ case SRP_TSK_ABORT_TASK_SET:
+ case SRP_TSK_CLEAR_TASK_SET:
+ if (d->lun != lun) {
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ tmpreq = &s->reqs[i];
+ if (req_iu(tmpreq)->srp.cmd.lun
+ != req_iu(req)->srp.tsk_mgmt.lun) {
+ continue;
+ }
+ if (!tmpreq->active || !tmpreq->sreq) {
+ continue;
+ }
+ assert(tmpreq->sreq->hba_private);
+ scsi_req_cancel(tmpreq->sreq);
+ }
+ break;
+
+ case SRP_TSK_CLEAR_ACA:
+ resp = SRP_TSK_MGMT_NOT_SUPPORTED;
+ break;
+
+ default:
+ resp = SRP_TSK_MGMT_FIELDS_INVALID;
+ break;
+ }
+ }
+
+ /* Compose the response here as */
+ QEMU_BUILD_BUG_ON(SRP_MAX_IU_DATA_LEN < 4);
+ memset(iu, 0, sizeof(struct srp_rsp) + 4);
+ iu->srp.rsp.opcode = SRP_RSP;
+ iu->srp.rsp.req_lim_delta = cpu_to_be32(1);
+ iu->srp.rsp.tag = tag;
+ iu->srp.rsp.flags |= SRP_RSP_FLAG_RSPVALID;
+ iu->srp.rsp.resp_data_len = cpu_to_be32(4);
+ if (resp) {
+ iu->srp.rsp.sol_not = (sol_not & 0x04) >> 2;
+ } else {
+ iu->srp.rsp.sol_not = (sol_not & 0x02) >> 1;
+ }
+
+ iu->srp.rsp.status = GOOD;
+ iu->srp.rsp.data[3] = resp;
+
+ vscsi_send_iu(s, req, sizeof(iu->srp.rsp) + 4, VIOSRP_SRP_FORMAT);
+
+ return 1;
+}
+
+static int vscsi_handle_srp_req(VSCSIState *s, vscsi_req *req)
+{
+ union srp_iu *srp = &req_iu(req)->srp;
+ int done = 1;
+ uint8_t opcode = srp->rsp.opcode;
+
+ switch (opcode) {
+ case SRP_LOGIN_REQ:
+ vscsi_process_login(s, req);
+ break;
+ case SRP_TSK_MGMT:
+ done = vscsi_process_tsk_mgmt(s, req);
+ break;
+ case SRP_CMD:
+ done = vscsi_queue_cmd(s, req);
+ break;
+ case SRP_LOGIN_RSP:
+ case SRP_I_LOGOUT:
+ case SRP_T_LOGOUT:
+ case SRP_RSP:
+ case SRP_CRED_REQ:
+ case SRP_CRED_RSP:
+ case SRP_AER_REQ:
+ case SRP_AER_RSP:
+ fprintf(stderr, "VSCSI: Unsupported opcode %02x\n", opcode);
+ break;
+ default:
+ fprintf(stderr, "VSCSI: Unknown type %02x\n", opcode);
+ }
+
+ return done;
+}
+
+static int vscsi_send_adapter_info(VSCSIState *s, vscsi_req *req)
+{
+ struct viosrp_adapter_info *sinfo;
+ struct mad_adapter_info_data info;
+ int rc;
+
+ sinfo = &req_iu(req)->mad.adapter_info;
+
+#if 0 /* What for ? */
+ rc = spapr_vio_dma_read(&s->vdev, be64_to_cpu(sinfo->buffer),
+ &info, be16_to_cpu(sinfo->common.length));
+ if (rc) {
+ fprintf(stderr, "vscsi_send_adapter_info: DMA read failure !\n");
+ }
+#endif
+ memset(&info, 0, sizeof(info));
+ strcpy(info.srp_version, SRP_VERSION);
+ memcpy(info.partition_name, "qemu", sizeof("qemu"));
+ info.partition_number = cpu_to_be32(0);
+ info.mad_version = cpu_to_be32(1);
+ info.os_type = cpu_to_be32(2);
+ info.port_max_txu[0] = cpu_to_be32(VSCSI_MAX_SECTORS << 9);
+
+ rc = spapr_vio_dma_write(&s->vdev, be64_to_cpu(sinfo->buffer),
+ &info, be16_to_cpu(sinfo->common.length));
+ if (rc) {
+ fprintf(stderr, "vscsi_send_adapter_info: DMA write failure !\n");
+ }
+
+ sinfo->common.status = rc ? cpu_to_be32(1) : 0;
+
+ return vscsi_send_iu(s, req, sizeof(*sinfo), VIOSRP_MAD_FORMAT);
+}
+
+static int vscsi_send_capabilities(VSCSIState *s, vscsi_req *req)
+{
+ struct viosrp_capabilities *vcap;
+ struct capabilities cap = { };
+ uint16_t len, req_len;
+ uint64_t buffer;
+ int rc;
+
+ vcap = &req_iu(req)->mad.capabilities;
+ req_len = len = be16_to_cpu(vcap->common.length);
+ buffer = be64_to_cpu(vcap->buffer);
+ if (len > sizeof(cap)) {
+ fprintf(stderr, "vscsi_send_capabilities: capabilities size mismatch !\n");
+
+ /*
+ * Just read and populate the structure that is known.
+ * Zero rest of the structure.
+ */
+ len = sizeof(cap);
+ }
+ rc = spapr_vio_dma_read(&s->vdev, buffer, &cap, len);
+ if (rc) {
+ fprintf(stderr, "vscsi_send_capabilities: DMA read failure !\n");
+ }
+
+ /*
+ * Current implementation does not support any migration or
+ * reservation capabilities. Construct the response telling the
+ * guest not to use them.
+ */
+ cap.flags = 0;
+ cap.migration.ecl = 0;
+ cap.reserve.type = 0;
+ cap.migration.common.server_support = 0;
+ cap.reserve.common.server_support = 0;
+
+ rc = spapr_vio_dma_write(&s->vdev, buffer, &cap, len);
+ if (rc) {
+ fprintf(stderr, "vscsi_send_capabilities: DMA write failure !\n");
+ }
+ if (req_len > len) {
+ /*
+ * Being paranoid and lets not worry about the error code
+ * here. Actual write of the cap is done above.
+ */
+ spapr_vio_dma_set(&s->vdev, (buffer + len), 0, (req_len - len));
+ }
+ vcap->common.status = rc ? cpu_to_be32(1) : 0;
+ return vscsi_send_iu(s, req, sizeof(*vcap), VIOSRP_MAD_FORMAT);
+}
+
+static int vscsi_handle_mad_req(VSCSIState *s, vscsi_req *req)
+{
+ union mad_iu *mad = &req_iu(req)->mad;
+ bool request_handled = false;
+ uint64_t retlen = 0;
+
+ switch (be32_to_cpu(mad->empty_iu.common.type)) {
+ case VIOSRP_EMPTY_IU_TYPE:
+ fprintf(stderr, "Unsupported EMPTY MAD IU\n");
+ retlen = sizeof(mad->empty_iu);
+ break;
+ case VIOSRP_ERROR_LOG_TYPE:
+ fprintf(stderr, "Unsupported ERROR LOG MAD IU\n");
+ retlen = sizeof(mad->error_log);
+ break;
+ case VIOSRP_ADAPTER_INFO_TYPE:
+ vscsi_send_adapter_info(s, req);
+ request_handled = true;
+ break;
+ case VIOSRP_HOST_CONFIG_TYPE:
+ retlen = sizeof(mad->host_config);
+ break;
+ case VIOSRP_CAPABILITIES_TYPE:
+ vscsi_send_capabilities(s, req);
+ request_handled = true;
+ break;
+ default:
+ fprintf(stderr, "VSCSI: Unknown MAD type %02x\n",
+ be32_to_cpu(mad->empty_iu.common.type));
+ /*
+ * PAPR+ says that "The length field is set to the length
+ * of the data structure(s) used in the command".
+ * As we did not recognize the request type, put zero there.
+ */
+ retlen = 0;
+ }
+
+ if (!request_handled) {
+ mad->empty_iu.common.status = cpu_to_be16(VIOSRP_MAD_NOT_SUPPORTED);
+ vscsi_send_iu(s, req, retlen, VIOSRP_MAD_FORMAT);
+ }
+
+ return 1;
+}
+
+static void vscsi_got_payload(VSCSIState *s, vscsi_crq *crq)
+{
+ vscsi_req *req;
+ int done;
+
+ req = vscsi_get_req(s);
+ if (req == NULL) {
+ fprintf(stderr, "VSCSI: Failed to get a request !\n");
+ return;
+ }
+
+ /* We only support a limited number of descriptors, we know
+ * the ibmvscsi driver uses up to 10 max, so it should fit
+ * in our 256 bytes IUs. If not we'll have to increase the size
+ * of the structure.
+ */
+ if (crq->s.IU_length > SRP_MAX_IU_LEN) {
+ fprintf(stderr, "VSCSI: SRP IU too long (%d bytes) !\n",
+ crq->s.IU_length);
+ vscsi_put_req(req);
+ return;
+ }
+
+ /* XXX Handle failure differently ? */
+ if (spapr_vio_dma_read(&s->vdev, crq->s.IU_data_ptr, &req->viosrp_iu_buf,
+ crq->s.IU_length)) {
+ fprintf(stderr, "vscsi_got_payload: DMA read failure !\n");
+ vscsi_put_req(req);
+ return;
+ }
+ memcpy(&req->crq, crq, sizeof(vscsi_crq));
+
+ if (crq->s.format == VIOSRP_MAD_FORMAT) {
+ done = vscsi_handle_mad_req(s, req);
+ } else {
+ done = vscsi_handle_srp_req(s, req);
+ }
+
+ if (done) {
+ vscsi_put_req(req);
+ }
+}
+
+
+static int vscsi_do_crq(struct SpaprVioDevice *dev, uint8_t *crq_data)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(dev);
+ vscsi_crq crq;
+
+ memcpy(crq.raw, crq_data, 16);
+ crq.s.timeout = be16_to_cpu(crq.s.timeout);
+ crq.s.IU_length = be16_to_cpu(crq.s.IU_length);
+ crq.s.IU_data_ptr = be64_to_cpu(crq.s.IU_data_ptr);
+
+ trace_spapr_vscsi_do_crq(crq.raw[0], crq.raw[1]);
+
+ switch (crq.s.valid) {
+ case 0xc0: /* Init command/response */
+
+ /* Respond to initialization request */
+ if (crq.s.format == 0x01) {
+ memset(crq.raw, 0, 16);
+ crq.s.valid = 0xc0;
+ crq.s.format = 0x02;
+ spapr_vio_send_crq(dev, crq.raw);
+ }
+
+ /* Note that in hotplug cases, we might get a 0x02
+ * as a result of us emitting the init request
+ */
+
+ break;
+ case 0xff: /* Link event */
+
+ /* Not handled for now */
+
+ break;
+ case 0x80: /* Payloads */
+ switch (crq.s.format) {
+ case VIOSRP_SRP_FORMAT: /* AKA VSCSI request */
+ case VIOSRP_MAD_FORMAT: /* AKA VSCSI response */
+ vscsi_got_payload(s, &crq);
+ break;
+ case VIOSRP_OS400_FORMAT:
+ case VIOSRP_AIX_FORMAT:
+ case VIOSRP_LINUX_FORMAT:
+ case VIOSRP_INLINE_FORMAT:
+ fprintf(stderr, "vscsi_do_srq: Unsupported payload format %02x\n",
+ crq.s.format);
+ break;
+ default:
+ fprintf(stderr, "vscsi_do_srq: Unknown payload format %02x\n",
+ crq.s.format);
+ }
+ break;
+ default:
+ fprintf(stderr, "vscsi_do_crq: unknown CRQ %02x %02x ...\n",
+ crq.raw[0], crq.raw[1]);
+ };
+
+ return 0;
+}
+
+static const struct SCSIBusInfo vscsi_scsi_info = {
+ .tcq = true,
+ .max_channel = 7, /* logical unit addressing format */
+ .max_target = 63,
+ .max_lun = 31,
+
+ .transfer_data = vscsi_transfer_data,
+ .complete = vscsi_command_complete,
+ .cancel = vscsi_request_cancelled,
+ .save_request = vscsi_save_request,
+ .load_request = vscsi_load_request,
+};
+
+static void spapr_vscsi_reset(SpaprVioDevice *dev)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(dev);
+ int i;
+
+ memset(s->reqs, 0, sizeof(s->reqs));
+ for (i = 0; i < VSCSI_REQ_LIMIT; i++) {
+ s->reqs[i].qtag = i;
+ }
+}
+
+static void spapr_vscsi_realize(SpaprVioDevice *dev, Error **errp)
+{
+ VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(dev);
+
+ dev->crq.SendFunc = vscsi_do_crq;
+
+ scsi_bus_init(&s->bus, sizeof(s->bus), DEVICE(dev), &vscsi_scsi_info);
+
+ /* ibmvscsi SCSI bus does not allow hotplug. */
+ qbus_set_hotplug_handler(BUS(&s->bus), NULL);
+}
+
+void spapr_vscsi_create(SpaprVioBus *bus)
+{
+ DeviceState *dev;
+
+ dev = qdev_new("spapr-vscsi");
+
+ qdev_realize_and_unref(dev, &bus->bus, &error_fatal);
+ scsi_bus_legacy_handle_cmdline(&VIO_SPAPR_VSCSI_DEVICE(dev)->bus);
+}
+
+static int spapr_vscsi_devnode(SpaprVioDevice *dev, void *fdt, int node_off)
+{
+ int ret;
+
+ ret = fdt_setprop_cell(fdt, node_off, "#address-cells", 2);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop_cell(fdt, node_off, "#size-cells", 0);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static Property spapr_vscsi_properties[] = {
+ DEFINE_SPAPR_PROPERTIES(VSCSIState, vdev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_spapr_vscsi = {
+ .name = "spapr_vscsi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_SPAPR_VIO(vdev, VSCSIState),
+ /* VSCSI state */
+ /* ???? */
+
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void spapr_vscsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SpaprVioDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
+
+ k->realize = spapr_vscsi_realize;
+ k->reset = spapr_vscsi_reset;
+ k->devnode = spapr_vscsi_devnode;
+ k->dt_name = "v-scsi";
+ k->dt_type = "vscsi";
+ k->dt_compatible = "IBM,v-scsi";
+ k->signal_mask = 0x00000001;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ device_class_set_props(dc, spapr_vscsi_properties);
+ k->rtce_window_size = 0x10000000;
+ dc->vmsd = &vmstate_spapr_vscsi;
+}
+
+static const TypeInfo spapr_vscsi_info = {
+ .name = TYPE_VIO_SPAPR_VSCSI_DEVICE,
+ .parent = TYPE_VIO_SPAPR_DEVICE,
+ .instance_size = sizeof(VSCSIState),
+ .class_init = spapr_vscsi_class_init,
+};
+
+static void spapr_vscsi_register_types(void)
+{
+ type_register_static(&spapr_vscsi_info);
+}
+
+type_init(spapr_vscsi_register_types)
diff --git a/hw/scsi/srp.h b/hw/scsi/srp.h
new file mode 100644
index 00000000..d27f31d2
--- /dev/null
+++ b/hw/scsi/srp.h
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2005 Cisco Systems. All rights reserved.
+ *
+ * This software is available to you under a choice of one of two
+ * licenses. You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * OpenIB.org BSD license below:
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#ifndef SCSI_SRP_H
+#define SCSI_SRP_H
+
+/*
+ * Structures and constants for the SCSI RDMA Protocol (SRP) as
+ * defined by the INCITS T10 committee. This file was written using
+ * draft Revision 16a of the SRP standard.
+ */
+
+enum {
+
+ SRP_LOGIN_REQ = 0x00,
+ SRP_TSK_MGMT = 0x01,
+ SRP_CMD = 0x02,
+ SRP_I_LOGOUT = 0x03,
+ SRP_LOGIN_RSP = 0xc0,
+ SRP_RSP = 0xc1,
+ SRP_LOGIN_REJ = 0xc2,
+ SRP_T_LOGOUT = 0x80,
+ SRP_CRED_REQ = 0x81,
+ SRP_AER_REQ = 0x82,
+ SRP_CRED_RSP = 0x41,
+ SRP_AER_RSP = 0x42
+};
+
+enum {
+ SRP_BUF_FORMAT_DIRECT = 1 << 1,
+ SRP_BUF_FORMAT_INDIRECT = 1 << 2
+};
+
+enum {
+ SRP_NO_DATA_DESC = 0,
+ SRP_DATA_DESC_DIRECT = 1,
+ SRP_DATA_DESC_INDIRECT = 2
+};
+
+enum {
+ SRP_TSK_ABORT_TASK = 0x01,
+ SRP_TSK_ABORT_TASK_SET = 0x02,
+ SRP_TSK_CLEAR_TASK_SET = 0x04,
+ SRP_TSK_LUN_RESET = 0x08,
+ SRP_TSK_CLEAR_ACA = 0x40
+};
+
+enum srp_login_rej_reason {
+ SRP_LOGIN_REJ_UNABLE_ESTABLISH_CHANNEL = 0x00010000,
+ SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES = 0x00010001,
+ SRP_LOGIN_REJ_REQ_IT_IU_LENGTH_TOO_LARGE = 0x00010002,
+ SRP_LOGIN_REJ_UNABLE_ASSOCIATE_CHANNEL = 0x00010003,
+ SRP_LOGIN_REJ_UNSUPPORTED_DESCRIPTOR_FMT = 0x00010004,
+ SRP_LOGIN_REJ_MULTI_CHANNEL_UNSUPPORTED = 0x00010005,
+ SRP_LOGIN_REJ_CHANNEL_LIMIT_REACHED = 0x00010006
+};
+
+enum {
+ SRP_REV10_IB_IO_CLASS = 0xff00,
+ SRP_REV16A_IB_IO_CLASS = 0x0100
+};
+
+enum {
+ SRP_TSK_MGMT_COMPLETE = 0x00,
+ SRP_TSK_MGMT_FIELDS_INVALID = 0x02,
+ SRP_TSK_MGMT_NOT_SUPPORTED = 0x04,
+ SRP_TSK_MGMT_FAILED = 0x05
+};
+
+struct srp_direct_buf {
+ uint64_t va;
+ uint32_t key;
+ uint32_t len;
+};
+
+/*
+ * We need the packed attribute because the SRP spec puts the list of
+ * descriptors at an offset of 20, which is not aligned to the size of
+ * struct srp_direct_buf. The whole structure must be packed to avoid
+ * having the 20-byte structure padded to 24 bytes on 64-bit architectures.
+ */
+struct srp_indirect_buf {
+ struct srp_direct_buf table_desc;
+ uint32_t len;
+ struct srp_direct_buf desc_list[0];
+} QEMU_PACKED;
+
+enum {
+ SRP_MULTICHAN_SINGLE = 0,
+ SRP_MULTICHAN_MULTI = 1
+};
+
+struct srp_login_req {
+ uint8_t opcode;
+ uint8_t reserved1[7];
+ uint64_t tag;
+ uint32_t req_it_iu_len;
+ uint8_t reserved2[4];
+ uint16_t req_buf_fmt;
+ uint8_t req_flags;
+ uint8_t reserved3[5];
+ uint8_t initiator_port_id[16];
+ uint8_t target_port_id[16];
+};
+
+/*
+ * The SRP spec defines the size of the LOGIN_RSP structure to be 52
+ * bytes, so it needs to be packed to avoid having it padded to 56
+ * bytes on 64-bit architectures.
+ */
+struct srp_login_rsp {
+ uint8_t opcode;
+ uint8_t reserved1[3];
+ uint32_t req_lim_delta;
+ uint64_t tag;
+ uint32_t max_it_iu_len;
+ uint32_t max_ti_iu_len;
+ uint16_t buf_fmt;
+ uint8_t rsp_flags;
+ uint8_t reserved2[25];
+} QEMU_PACKED;
+
+struct srp_login_rej {
+ uint8_t opcode;
+ uint8_t reserved1[3];
+ uint32_t reason;
+ uint64_t tag;
+ uint8_t reserved2[8];
+ uint16_t buf_fmt;
+ uint8_t reserved3[6];
+};
+
+struct srp_i_logout {
+ uint8_t opcode;
+ uint8_t reserved[7];
+ uint64_t tag;
+};
+
+struct srp_t_logout {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved[2];
+ uint32_t reason;
+ uint64_t tag;
+};
+
+/*
+ * We need the packed attribute because the SRP spec only aligns the
+ * 8-byte LUN field to 4 bytes.
+ */
+struct srp_tsk_mgmt {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved1[6];
+ uint64_t tag;
+ uint8_t reserved2[4];
+ uint64_t lun;
+ uint8_t reserved3[2];
+ uint8_t tsk_mgmt_func;
+ uint8_t reserved4;
+ uint64_t task_tag;
+ uint8_t reserved5[8];
+} QEMU_PACKED;
+
+/*
+ * We need the packed attribute because the SRP spec only aligns the
+ * 8-byte LUN field to 4 bytes.
+ */
+struct srp_cmd {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved1[3];
+ uint8_t buf_fmt;
+ uint8_t data_out_desc_cnt;
+ uint8_t data_in_desc_cnt;
+ uint64_t tag;
+ uint8_t reserved2[4];
+ uint64_t lun;
+ uint8_t reserved3;
+ uint8_t task_attr;
+ uint8_t reserved4;
+ uint8_t add_cdb_len;
+ uint8_t cdb[16];
+ uint8_t add_data[0];
+} QEMU_PACKED;
+
+enum {
+ SRP_RSP_FLAG_RSPVALID = 1 << 0,
+ SRP_RSP_FLAG_SNSVALID = 1 << 1,
+ SRP_RSP_FLAG_DOOVER = 1 << 2,
+ SRP_RSP_FLAG_DOUNDER = 1 << 3,
+ SRP_RSP_FLAG_DIOVER = 1 << 4,
+ SRP_RSP_FLAG_DIUNDER = 1 << 5
+};
+
+/*
+ * The SRP spec defines the size of the RSP structure to be 36 bytes,
+ * so it needs to be packed to avoid having it padded to 40 bytes on
+ * 64-bit architectures.
+ */
+struct srp_rsp {
+ uint8_t opcode;
+ uint8_t sol_not;
+ uint8_t reserved1[2];
+ uint32_t req_lim_delta;
+ uint64_t tag;
+ uint8_t reserved2[2];
+ uint8_t flags;
+ uint8_t status;
+ uint32_t data_out_res_cnt;
+ uint32_t data_in_res_cnt;
+ uint32_t sense_data_len;
+ uint32_t resp_data_len;
+ uint8_t data[0];
+} QEMU_PACKED;
+
+#endif /* SCSI_SRP_H */
diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events
new file mode 100644
index 00000000..ab238293
--- /dev/null
+++ b/hw/scsi/trace-events
@@ -0,0 +1,357 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# scsi-bus.c
+scsi_req_alloc(int target, int lun, int tag) "target %d lun %d tag %d"
+scsi_req_cancel(int target, int lun, int tag) "target %d lun %d tag %d"
+scsi_req_data(int target, int lun, int tag, int len) "target %d lun %d tag %d len %d"
+scsi_req_data_canceled(int target, int lun, int tag, int len) "target %d lun %d tag %d len %d"
+scsi_req_dequeue(int target, int lun, int tag) "target %d lun %d tag %d"
+scsi_req_continue(int target, int lun, int tag) "target %d lun %d tag %d"
+scsi_req_continue_canceled(int target, int lun, int tag) "target %d lun %d tag %d"
+scsi_req_parsed(int target, int lun, int tag, int cmd, int mode, int xfer) "target %d lun %d tag %d command %d dir %d length %d"
+scsi_req_parsed_lba(int target, int lun, int tag, int cmd, uint64_t lba) "target %d lun %d tag %d command %d lba %"PRIu64
+scsi_req_parse_bad(int target, int lun, int tag, int cmd) "target %d lun %d tag %d command %d"
+scsi_req_build_sense(int target, int lun, int tag, int key, int asc, int ascq) "target %d lun %d tag %d key 0x%02x asc 0x%02x ascq 0x%02x"
+scsi_device_set_ua(int target, int lun, int key, int asc, int ascq) "target %d lun %d key 0x%02x asc 0x%02x ascq 0x%02x"
+scsi_report_luns(int target, int lun, int tag) "target %d lun %d tag %d"
+scsi_inquiry(int target, int lun, int tag, int cdb1, int cdb2) "target %d lun %d tag %d page 0x%02x/0x%02x"
+scsi_test_unit_ready(int target, int lun, int tag) "target %d lun %d tag %d"
+scsi_request_sense(int target, int lun, int tag) "target %d lun %d tag %d"
+
+# mptsas.c
+mptsas_command_complete(void *dev, uint32_t ctx, uint32_t status, uint32_t resid) "dev %p context 0x%08x status 0x%x resid %d"
+mptsas_diag_read(void *dev, uint32_t addr, uint32_t val) "dev %p addr 0x%08x value 0x%08x"
+mptsas_diag_write(void *dev, uint32_t addr, uint32_t val) "dev %p addr 0x%08x value 0x%08x"
+mptsas_irq_intx(void *dev, int level) "dev %p level %d"
+mptsas_irq_msi(void *dev) "dev %p "
+mptsas_mmio_read(void *dev, uint32_t addr, uint32_t val) "dev %p addr 0x%08x value 0x%x"
+mptsas_mmio_unhandled_read(void *dev, uint32_t addr) "dev %p addr 0x%08x"
+mptsas_mmio_unhandled_write(void *dev, uint32_t addr, uint32_t val) "dev %p addr 0x%08x value 0x%x"
+mptsas_mmio_write(void *dev, uint32_t addr, uint32_t val) "dev %p addr 0x%08x value 0x%x"
+mptsas_process_message(void *dev, int msg, uint32_t ctx) "dev %p cmd %d context 0x%08x"
+mptsas_process_scsi_io_request(void *dev, int bus, int target, int lun, uint64_t len) "dev %p dev %d:%d:%d length %"PRIu64""
+mptsas_reset(void *dev) "dev %p "
+mptsas_scsi_overflow(void *dev, uint32_t ctx, uint64_t req, uint64_t found) "dev %p context 0x%08x: %"PRIu64"/%"PRIu64""
+mptsas_sgl_overflow(void *dev, uint32_t ctx, uint64_t req, uint64_t found) "dev %p context 0x%08x: %"PRIu64"/%"PRIu64""
+mptsas_unhandled_cmd(void *dev, uint32_t ctx, uint8_t msg_cmd) "dev %p context 0x%08x: Unhandled cmd 0x%x"
+mptsas_unhandled_doorbell_cmd(void *dev, int cmd) "dev %p value 0x%08x"
+
+# mptconfig.c
+mptsas_config_sas_device(void *dev, int address, int port, int phy_handle, int dev_handle, int page) "dev %p address %d (port %d, handles: phy %d dev %d) page %d"
+mptsas_config_sas_phy(void *dev, int address, int port, int phy_handle, int dev_handle, int page) "dev %p address %d (port %d, handles: phy %d dev %d) page %d"
+
+# megasas.c
+megasas_init_firmware(uint64_t pa) "pa 0x%" PRIx64 " "
+megasas_init_queue(uint64_t queue_pa, int queue_len, uint32_t head, uint32_t tail, uint32_t flags) "queue at 0x%" PRIx64 " len %d head 0x%" PRIx32 " tail 0x%" PRIx32 " flags 0x%x"
+megasas_initq_map_failed(int frame) "scmd %d: failed to map queue"
+megasas_initq_mapped(uint64_t pa) "queue already mapped at 0x%" PRIx64
+megasas_initq_mismatch(int queue_len, int fw_cmds) "queue size %d max fw cmds %d"
+megasas_qf_mapped(unsigned int index) "skip mapped frame 0x%x"
+megasas_qf_new(unsigned int index, uint64_t frame) "frame 0x%x addr 0x%" PRIx64
+megasas_qf_busy(unsigned long pa) "all frames busy for frame 0x%lx"
+megasas_qf_enqueue(unsigned int index, unsigned int count, uint64_t context, uint32_t head, uint32_t tail, unsigned int busy) "frame 0x%x count %d context 0x%" PRIx64 " head 0x%" PRIx32 " tail 0x%" PRIx32 " busy %u"
+megasas_qf_update(uint32_t head, uint32_t tail, unsigned int busy) "head 0x%" PRIx32 " tail 0x%" PRIx32 " busy %u"
+megasas_qf_map_failed(int cmd, unsigned long frame) "scmd %d: frame %lu"
+megasas_qf_complete_noirq(uint64_t context) "context 0x%" PRIx64 " "
+megasas_qf_complete(uint64_t context, uint32_t head, uint32_t tail, int busy) "context 0x%" PRIx64 " head 0x%" PRIx32 " tail 0x%" PRIx32 " busy %u"
+megasas_frame_busy(uint64_t addr) "frame 0x%" PRIx64 " busy"
+megasas_unhandled_frame_cmd(int cmd, uint8_t frame_cmd) "scmd %d: MFI cmd 0x%x"
+megasas_handle_scsi(const char *frame, int bus, int dev, int lun, void *sdev, unsigned long size) "%s dev %x/%x/%x sdev %p xfer %lu"
+megasas_scsi_target_not_present(const char *frame, int bus, int dev, int lun) "%s dev %x/%x/%x"
+megasas_scsi_invalid_cdb_len(const char *frame, int bus, int dev, int lun, int len) "%s dev %x/%x/%x invalid cdb len %d"
+megasas_iov_read_overflow(int cmd, int bytes, int len) "scmd %d: %d/%d bytes"
+megasas_iov_write_overflow(int cmd, int bytes, int len) "scmd %d: %d/%d bytes"
+megasas_iov_read_underflow(int cmd, int bytes, int len) "scmd %d: %d/%d bytes"
+megasas_iov_write_underflow(int cmd, int bytes, int len) "scmd %d: %d/%d bytes"
+megasas_scsi_req_alloc_failed(const char *frame, int dev, int lun) "%s dev %x/%x"
+megasas_scsi_read_start(int cmd, int len) "scmd %d: transfer %d bytes of data"
+megasas_scsi_write_start(int cmd, int len) "scmd %d: transfer %d bytes of data"
+megasas_scsi_nodata(int cmd) "scmd %d: no data to be transferred"
+megasas_scsi_complete(int cmd, uint32_t status, int len, int xfer) "scmd %d: status 0x%x, len %u/%u"
+megasas_command_complete(int cmd, uint32_t status, uint32_t resid) "scmd %d: status 0x%x, residual %d"
+megasas_handle_io(int cmd, const char *frame, int dev, int lun, unsigned long lba, unsigned long count) "scmd %d: %s dev %x/%x lba 0x%lx count %lu"
+megasas_io_target_not_present(int cmd, const char *frame, int dev, int lun) "scmd %d: %s dev 1/%x/%x LUN not present"
+megasas_io_read_start(int cmd, unsigned long lba, unsigned long count, unsigned long len) "scmd %d: start LBA 0x%lx %lu blocks (%lu bytes)"
+megasas_io_write_start(int cmd, unsigned long lba, unsigned long count, unsigned long len) "scmd %d: start LBA 0x%lx %lu blocks (%lu bytes)"
+megasas_io_complete(int cmd, uint32_t len) "scmd %d: %d bytes"
+megasas_iovec_sgl_overflow(int cmd, int index, int limit) "scmd %d: iovec count %d limit %d"
+megasas_iovec_sgl_underflow(int cmd, int index) "scmd %d: iovec count %d"
+megasas_iovec_sgl_invalid(int cmd, int index, uint64_t pa, uint32_t len) "scmd %d: element %d pa 0x%" PRIx64 " len %u"
+megasas_iovec_overflow(int cmd, int len, int limit) "scmd %d: len %d limit %d"
+megasas_iovec_underflow(int cmd, int len, int limit) "scmd %d: len %d limit %d"
+megasas_handle_dcmd(int cmd, int opcode) "scmd %d: MFI DCMD opcode 0x%x"
+megasas_finish_dcmd(int cmd, int size) "scmd %d: MFI DCMD wrote %d bytes"
+megasas_dcmd_req_alloc_failed(int cmd, const char *desc) "scmd %d: %s"
+megasas_dcmd_internal_submit(int cmd, const char *desc, int dev) "scmd %d: %s to dev %d"
+megasas_dcmd_internal_finish(int cmd, int opcode, int lun) "scmd %d: cmd 0x%x lun %d"
+megasas_dcmd_internal_invalid(int cmd, int opcode) "scmd %d: DCMD 0x%x"
+megasas_dcmd_unhandled(int cmd, int opcode, int len) "scmd %d: opcode 0x%x, len %d"
+megasas_dcmd_zero_sge(int cmd) "scmd %d: zero DCMD sge count"
+megasas_dcmd_invalid_sge(int cmd, int count) "scmd %d: DCMD sge count %d"
+megasas_dcmd_invalid_xfer_len(int cmd, unsigned long size, unsigned long max) "scmd %d: xfer len %ld, max %ld"
+megasas_dcmd_enter(int cmd, const char *dcmd, int len) "scmd %d: DCMD %s len %d"
+megasas_dcmd_dummy(int cmd, unsigned long size) "scmd %d: xfer len %ld"
+megasas_dcmd_set_fw_time(int cmd, unsigned long time) "scmd %d: Set FW time 0x%lx"
+megasas_dcmd_pd_get_list(int cmd, int num, int max, int offset) "scmd %d: DCMD PD get list: %d / %d PDs, size %d"
+megasas_dcmd_ld_get_list(int cmd, int num, int max) "scmd %d: DCMD LD get list: found %d / %d LDs"
+megasas_dcmd_ld_get_info(int cmd, int ld_id) "scmd %d: dev %d"
+megasas_dcmd_ld_list_query(int cmd, int flags) "scmd %d: query flags 0x%x"
+megasas_dcmd_pd_get_info(int cmd, int pd_id) "scmd %d: dev %d"
+megasas_dcmd_pd_list_query(int cmd, int flags) "scmd %d: query flags 0x%x"
+megasas_dcmd_reset_ld(int cmd, int target_id) "scmd %d: dev %d"
+megasas_dcmd_unsupported(int cmd, unsigned long size) "scmd %d: set properties len %ld"
+megasas_abort_frame(int cmd, int abort_cmd) "scmd %d: frame 0x%x"
+megasas_abort_no_cmd(int cmd, uint64_t context) "scmd %d: no active command for frame context 0x%" PRIx64
+megasas_abort_invalid_context(int cmd, uint64_t context, int abort_cmd) "scmd %d: invalid frame context 0x%" PRIx64 " for abort frame 0x%x"
+megasas_reset(int fw_state) "firmware state 0x%x"
+megasas_init(int sges, int cmds, const char *mode) "Using %d sges, %d cmds, %s mode"
+megasas_msix_raise(int vector) "vector %d"
+megasas_msi_raise(int vector) "vector %d"
+megasas_irq_lower(void) "INTx"
+megasas_irq_raise(void) "INTx"
+megasas_intr_enabled(void) "Interrupts enabled"
+megasas_intr_disabled(void) "Interrupts disabled"
+megasas_msix_enabled(int vector) "vector %d"
+megasas_msi_enabled(int vector) "vector %d"
+megasas_mmio_readl(const char *reg, uint32_t val) "reg %s: 0x%x"
+megasas_mmio_invalid_readl(unsigned long addr) "addr 0x%lx"
+megasas_mmio_writel(const char *reg, uint32_t val) "reg %s: 0x%x"
+megasas_mmio_invalid_writel(uint32_t addr, uint32_t val) "addr 0x%x: 0x%x"
+
+# vmw_pvscsi.c
+pvscsi_ring_init_data(uint32_t txr_len_log2, uint32_t rxr_len_log2) "TX/RX rings logarithms set to %d/%d"
+pvscsi_ring_init_msg(uint32_t len_log2) "MSG ring logarithm set to %d"
+pvscsi_ring_flush_cmp(uint64_t filled_cmp_ptr) "new production counter of completion ring is 0x%"PRIx64
+pvscsi_ring_flush_msg(uint64_t filled_cmp_ptr) "new production counter of message ring is 0x%"PRIx64
+pvscsi_update_irq_level(bool raise, uint64_t mask, uint64_t status) "interrupt level set to %d (MASK: 0x%"PRIx64", STATUS: 0x%"PRIx64")"
+pvscsi_update_irq_msi(void) "sending MSI notification"
+pvscsi_cmp_ring_put(unsigned long addr) "got completion descriptor 0x%lx"
+pvscsi_msg_ring_put(unsigned long addr) "got message descriptor 0x%lx"
+pvscsi_complete_request(uint64_t context, uint64_t len, uint8_t sense_key) "completion: ctx: 0x%"PRIx64", len: 0x%"PRIx64", sense key: %u"
+pvscsi_get_sg_list(int nsg, size_t size) "get SG list: depth: %u, size: %zu"
+pvscsi_get_next_sg_elem(uint32_t flags) "unknown flags in SG element (val: 0x%x)"
+pvscsi_command_complete_not_found(uint32_t tag) "can't find request for tag 0x%x"
+pvscsi_command_complete_data_run(void) "not all data required for command transferred"
+pvscsi_command_complete_sense_len(int len) "sense information length is %d bytes"
+pvscsi_convert_sglist(uint64_t context, unsigned long addr, uint32_t resid) "element: ctx: 0x%"PRIx64" addr: 0x%lx, len: %ul"
+pvscsi_process_req_descr(uint8_t cmd, uint64_t ctx) "SCSI cmd 0x%x, ctx: 0x%"PRIx64
+pvscsi_process_req_descr_unknown_device(void) "command directed to unknown device rejected"
+pvscsi_process_req_descr_invalid_dir(void) "command with invalid transfer direction rejected"
+pvscsi_process_io(unsigned long addr) "got descriptor 0x%lx"
+pvscsi_on_cmd_noimpl(const char* cmd) "unimplemented command %s ignored"
+pvscsi_on_cmd_reset_dev(uint32_t tgt, int lun, void* dev) "PVSCSI_CMD_RESET_DEVICE[target %u lun %d (dev 0x%p)]"
+pvscsi_on_cmd_arrived(const char* cmd) "command %s arrived"
+pvscsi_on_cmd_abort(uint64_t ctx, uint32_t tgt) "command PVSCSI_CMD_ABORT_CMD for ctx 0x%"PRIx64", target %u"
+pvscsi_on_cmd_unknown(uint64_t cmd_id) "unknown command 0x%"PRIx64
+pvscsi_on_cmd_unknown_data(uint32_t data) "data for unknown command 0x:0x%x"
+pvscsi_io_write(const char* cmd, uint64_t val) "%s write: 0x%"PRIx64
+pvscsi_io_write_unknown(unsigned long addr, unsigned sz, uint64_t val) "unknown write address: 0x%lx size: %u bytes value: 0x%"PRIx64
+pvscsi_io_read(const char* cmd, uint64_t status) "%s read: 0x%"PRIx64
+pvscsi_io_read_unknown(unsigned long addr, unsigned sz) "unknown read address: 0x%lx size: %u bytes"
+pvscsi_init_msi_fail(int res) "failed to initialize MSI, error %d"
+pvscsi_state(const char* state) "starting %s ..."
+pvscsi_tx_rings_ppn(const char* label, uint64_t ppn) "%s page: 0x%"PRIx64
+pvscsi_tx_rings_num_pages(const char* label, uint32_t num) "Number of %s pages: %u"
+
+# esp.c
+esp_error_fifo_overrun(void) "FIFO overrun"
+esp_error_unhandled_command(uint32_t val) "unhandled command (0x%2.2x)"
+esp_error_invalid_write(uint32_t val, uint32_t addr) "invalid write of 0x%02x at [0x%x]"
+esp_raise_irq(void) "Raise IRQ"
+esp_lower_irq(void) "Lower IRQ"
+esp_raise_drq(void) "Raise DREQ"
+esp_lower_drq(void) "Lower DREQ"
+esp_dma_enable(void) "Raise enable"
+esp_dma_disable(void) "Lower enable"
+esp_pdma_read(int size) "pDMA read %u bytes"
+esp_pdma_write(int size) "pDMA write %u bytes"
+esp_get_cmd(uint32_t dmalen, int target) "len %d target %d"
+esp_do_command_phase(uint8_t busid) "busid 0x%x"
+esp_do_identify(uint8_t byte) "0x%x"
+esp_handle_satn_stop(uint32_t cmdlen) "cmdlen %d"
+esp_write_response(uint32_t status) "Transfer status (status=%d)"
+esp_do_dma(uint32_t cmdlen, uint32_t len) "command len %d + %d"
+esp_command_complete(void) "SCSI Command complete"
+esp_command_complete_deferred(void) "SCSI Command complete deferred"
+esp_command_complete_unexpected(void) "SCSI command completed unexpectedly"
+esp_command_complete_fail(void) "Command failed"
+esp_transfer_data(uint32_t dma_left, int32_t ti_size) "transfer %d/%d"
+esp_handle_ti(uint32_t minlen) "Transfer Information len %d"
+esp_handle_ti_cmd(uint32_t cmdlen) "command len %d"
+esp_mem_readb(uint32_t saddr, uint8_t reg) "reg[%d]: 0x%2.2x"
+esp_mem_writeb(uint32_t saddr, uint8_t reg, uint32_t val) "reg[%d]: 0x%2.2x -> 0x%2.2x"
+esp_mem_writeb_cmd_nop(uint32_t val) "NOP (0x%2.2x)"
+esp_mem_writeb_cmd_flush(uint32_t val) "Flush FIFO (0x%2.2x)"
+esp_mem_writeb_cmd_reset(uint32_t val) "Chip reset (0x%2.2x)"
+esp_mem_writeb_cmd_bus_reset(uint32_t val) "Bus reset (0x%2.2x)"
+esp_mem_writeb_cmd_iccs(uint32_t val) "Initiator Command Complete Sequence (0x%2.2x)"
+esp_mem_writeb_cmd_msgacc(uint32_t val) "Message Accepted (0x%2.2x)"
+esp_mem_writeb_cmd_pad(uint32_t val) "Transfer padding (0x%2.2x)"
+esp_mem_writeb_cmd_satn(uint32_t val) "Set ATN (0x%2.2x)"
+esp_mem_writeb_cmd_rstatn(uint32_t val) "Reset ATN (0x%2.2x)"
+esp_mem_writeb_cmd_sel(uint32_t val) "Select without ATN (0x%2.2x)"
+esp_mem_writeb_cmd_selatn(uint32_t val) "Select with ATN (0x%2.2x)"
+esp_mem_writeb_cmd_selatns(uint32_t val) "Select with ATN & stop (0x%2.2x)"
+esp_mem_writeb_cmd_ensel(uint32_t val) "Enable selection (0x%2.2x)"
+esp_mem_writeb_cmd_dissel(uint32_t val) "Disable selection (0x%2.2x)"
+esp_mem_writeb_cmd_ti(uint32_t val) "Transfer Information (0x%2.2x)"
+
+# esp-pci.c
+esp_pci_error_invalid_dma_direction(void) "invalid DMA transfer direction"
+esp_pci_error_invalid_read(uint32_t reg) "read access outside bounds (reg 0x%x)"
+esp_pci_error_invalid_write(uint32_t reg) "write access outside bounds (reg 0x%x)"
+esp_pci_error_invalid_write_dma(uint32_t val, uint32_t addr) "invalid write of 0x%02x at [0x%x]"
+esp_pci_dma_read(uint32_t saddr, uint32_t reg) "reg[%d]: 0x%8.8x"
+esp_pci_dma_write(uint32_t saddr, uint32_t reg, uint32_t val) "reg[%d]: 0x%8.8x -> 0x%8.8x"
+esp_pci_dma_idle(uint32_t val) "IDLE (0x%.8x)"
+esp_pci_dma_blast(uint32_t val) "BLAST (0x%.8x)"
+esp_pci_dma_abort(uint32_t val) "ABORT (0x%.8x)"
+esp_pci_dma_start(uint32_t val) "START (0x%.8x)"
+esp_pci_sbac_read(uint32_t reg) "sbac: 0x%8.8x"
+esp_pci_sbac_write(uint32_t reg, uint32_t val) "sbac: 0x%8.8x -> 0x%8.8x"
+
+# spapr_vscsi.c
+spapr_vscsi_send_rsp(uint8_t status, int32_t res_in, int32_t res_out) "status: 0x%x, res_in: %"PRId32", res_out: %"PRId32
+spapr_vscsi_fetch_desc_no_data(void) "no data descriptor"
+spapr_vscsi_fetch_desc_direct(void) "direct segment"
+spapr_vscsi_fetch_desc_indirect(uint32_t qtag, unsigned desc, unsigned local_desc) "indirect segment local tag=0x%"PRIx32" desc#%u/%u"
+spapr_vscsi_fetch_desc_out_of_range(unsigned desc, unsigned desc_offset) "#%u is ouf of range (%u bytes)"
+spapr_vscsi_fetch_desc_dma_read_error(int rc) "spapr_vio_dma_read -> %d reading ext_desc"
+spapr_vscsi_fetch_desc_indirect_seg_ext(uint32_t qtag, unsigned n, unsigned desc, uint64_t va, uint32_t len) "indirect segment ext. tag=0x%"PRIx32" desc#%u/%u { va=0x%"PRIx64" len=0x%"PRIx32" }"
+spapr_vscsi_fetch_desc_out_of_desc(void) "Out of descriptors !"
+spapr_vscsi_fetch_desc_out_of_desc_boundary(unsigned offset, unsigned desc, uint32_t len) " offset=0x%x is out of a descriptor #%u boundary=0x%"PRIx32
+spapr_vscsi_fetch_desc_done(unsigned desc_num, unsigned desc_offset, uint64_t va, uint32_t len) " cur=%u offs=0x%x ret { va=0x%"PRIx64" len=0x%"PRIx32" }"
+spapr_vscsi_srp_indirect_data(uint32_t len) "indirect segment 0x%"PRIx32" bytes"
+spapr_vscsi_srp_indirect_data_rw(int writing, int rc) "spapr_vio_dma_r/w(%d) -> %d"
+spapr_vscsi_srp_indirect_data_buf(unsigned a, unsigned b, unsigned c, unsigned d) " data: %02x %02x %02x %02x..."
+spapr_vscsi_srp_transfer_data(uint32_t len) "no data desc transfer, skipping 0x%"PRIx32" bytes"
+spapr_vscsi_transfer_data(uint32_t tag, uint32_t len, void *req) "SCSI xfer complete tag=0x%"PRIx32" len=0x%"PRIx32", req=%p"
+spapr_vscsi_command_complete(uint32_t tag, uint32_t status, void *req) "SCSI cmd complete, tag=0x%"PRIx32" status=0x%"PRIx32", req=%p"
+spapr_vscsi_command_complete_sense_data1(uint32_t len, unsigned s0, unsigned s1, unsigned s2, unsigned s3, unsigned s4, unsigned s5, unsigned s6, unsigned s7) "Sense data, %d bytes: %02x %02x %02x %02x %02x %02x %02x %02x"
+spapr_vscsi_command_complete_sense_data2(unsigned s8, unsigned s9, unsigned s10, unsigned s11, unsigned s12, unsigned s13, unsigned s14, unsigned s15) " %02x %02x %02x %02x %02x %02x %02x %02x"
+spapr_vscsi_command_complete_status(uint32_t status) "Command complete err=%"PRIu32
+spapr_vscsi_save_request(uint32_t qtag, unsigned desc, unsigned offset) "saving tag=%"PRIu32", current desc#%u, offset=0x%x"
+spapr_vscsi_load_request(uint32_t qtag, unsigned desc, unsigned offset) "restoring tag=%"PRIu32", current desc#%u, offset=0x%x"
+spapr_vscsi_process_login(void) "Got login, sending response !"
+spapr_vscsi_process_tsk_mgmt(uint8_t func) "tsk_mgmt_func 0x%02x"
+spapr_vscsi_queue_cmd_no_drive(uint64_t lun) "Command for lun 0x%08" PRIx64 " with no drive"
+spapr_vscsi_queue_cmd(uint32_t qtag, unsigned cdb, const char *cmd, int lun, int ret) "Queued command tag 0x%"PRIx32" CMD 0x%x=%s LUN %d ret: %d"
+spapr_vscsi_do_crq(unsigned c0, unsigned c1) "crq: %02x %02x ..."
+
+# lsi53c895a.c
+lsi_reset(void) "Reset"
+lsi_update_irq(int level, uint8_t dstat, uint8_t sist1, uint8_t sist0) "Update IRQ level %d dstat 0x%02x sist 0x%02x0x%02x"
+lsi_update_irq_disconnected(void) "Handled IRQs & disconnected, looking for pending processes"
+lsi_script_scsi_interrupt(uint8_t stat1, uint8_t stat0, uint8_t sist1, uint8_t sist0) "SCSI Interrupt 0x%02x0x%02x prev 0x%02x0x%02x"
+lsi_script_dma_interrupt(uint8_t stat, uint8_t dstat) "DMA Interrupt 0x%x prev 0x%x"
+lsi_bad_phase_jump(uint32_t dsp) "Data phase mismatch jump to 0x%"PRIX32
+lsi_bad_phase_interrupt(void) "Phase mismatch interrupt"
+lsi_bad_selection(uint32_t id) "Selected absent target %"PRIu32
+lsi_do_dma_unavailable(void) "DMA no data available"
+lsi_do_dma(uint64_t addr, int len) "DMA addr=0x%"PRIx64" len=%d"
+lsi_queue_command(uint32_t tag) "Queueing tag=0x%"PRIx32
+lsi_add_msg_byte_error(void) "MSG IN data too long"
+lsi_add_msg_byte(uint8_t data) "MSG IN 0x%02x"
+lsi_reselect(int id) "Reselected target %d"
+lsi_queue_req_error(void *p) "Multiple IO pending for request %p"
+lsi_queue_req(uint32_t tag) "Queueing IO tag=0x%"PRIx32
+lsi_command_complete(uint32_t status) "Command complete status=%"PRId32
+lsi_transfer_data(uint32_t tag, uint32_t len) "Data ready tag=0x%"PRIx32" len=%"PRId32
+lsi_do_command(uint32_t dbc) "Send command len=%"PRId32
+lsi_do_status(uint32_t dbc, uint8_t status) "Get status len=%"PRId32" status=%d"
+lsi_do_status_error(void) "Bad Status move"
+lsi_do_msgin(uint32_t dbc, int len) "Message in len=%"PRId32" %d"
+lsi_do_msgout(uint32_t dbc) "MSG out len=%"PRId32
+lsi_do_msgout_disconnect(void) "MSG: Disconnect"
+lsi_do_msgout_noop(void) "MSG: No Operation"
+lsi_do_msgout_extended(uint8_t msg, uint8_t len) "Extended message 0x%x (len %d)"
+lsi_do_msgout_ignored(const char *msg) "%s (ignored)"
+lsi_do_msgout_simplequeue(uint8_t select_tag) "SIMPLE queue tag=0x%x"
+lsi_do_msgout_abort(uint32_t tag) "MSG: ABORT TAG tag=0x%"PRIx32
+lsi_do_msgout_clearqueue(uint32_t tag) "MSG: CLEAR QUEUE tag=0x%"PRIx32
+lsi_do_msgout_busdevicereset(uint32_t tag) "MSG: BUS DEVICE RESET tag=0x%"PRIx32
+lsi_do_msgout_select(int id) "Select LUN %d"
+lsi_memcpy(uint32_t dest, uint32_t src, int count) "memcpy dest 0x%"PRIx32" src 0x%"PRIx32" count %d"
+lsi_wait_reselect(void) "Wait Reselect"
+lsi_execute_script(uint32_t dsp, uint32_t insn, uint32_t addr) "SCRIPTS dsp=0x%"PRIx32" opcode 0x%"PRIx32" arg 0x%"PRIx32
+lsi_execute_script_blockmove_delayed(void) "Delayed select timeout"
+lsi_execute_script_blockmove_badphase(const char *phase, const char *expected) "Wrong phase got %s expected %s"
+lsi_execute_script_io_alreadyreselected(void) "Already reselected, jumping to alternative address"
+lsi_execute_script_io_selected(uint8_t id, const char *atn) "Selected target %d%s"
+lsi_execute_script_io_disconnect(void) "Wait Disconnect"
+lsi_execute_script_io_set(const char *atn, const char *ack, const char *tm, const char *cc) "Set%s%s%s%s"
+lsi_execute_script_io_clear(const char *atn, const char *ack, const char *tm, const char *cc) "Clear%s%s%s%s"
+lsi_execute_script_io_opcode(const char *opcode, int reg, const char *opname, uint8_t data8, uint32_t sfbr, const char *ssfbr) "%s reg 0x%x %s data8=0x%02x sfbr=0x%02x%s"
+lsi_execute_script_tc_nop(void) "NOP"
+lsi_execute_script_tc_delayedselect_timeout(void) "Delayed select timeout"
+lsi_execute_script_tc_compc(int result) "Compare carry %d"
+lsi_execute_script_tc_compp(const char *phase, char op, const char *insn_phase) "Compare phase %s %c= %s"
+lsi_execute_script_tc_compd(uint32_t sfbr, uint8_t mask, char op, int result) "Compare data 0x%"PRIx32" & 0x%x %c= 0x%x"
+lsi_execute_script_tc_jump(uint32_t addr) "Jump to 0x%"PRIx32
+lsi_execute_script_tc_call(uint32_t addr) "Call 0x%"PRIx32
+lsi_execute_script_tc_return(uint32_t addr) "Return to 0x%"PRIx32
+lsi_execute_script_tc_interrupt(uint32_t addr) "Interrupt 0x%"PRIx32
+lsi_execute_script_tc_illegal(void) "Illegal transfer control"
+lsi_execute_script_tc_cc_failed(void) "Control condition failed"
+lsi_execute_script_mm_load(int reg, int n, uint32_t addr, int data) "Load reg 0x%x size %d addr 0x%"PRIx32" = 0x%08x"
+lsi_execute_script_mm_store(int reg, int n, uint32_t addr) "Store reg 0x%x size %d addr 0x%"PRIx32
+lsi_execute_script_stop(void) "SCRIPTS execution stopped"
+lsi_awoken(void) "Woken by SIGP"
+lsi_reg_read(const char *name, int offset, uint8_t ret) "Read reg %s 0x%x = 0x%02x"
+lsi_reg_write(const char *name, int offset, uint8_t val) "Write reg %s 0x%x = 0x%02x"
+
+# virtio-scsi.c
+virtio_scsi_cmd_req(int lun, uint32_t tag, uint8_t cmd) "virtio_scsi_cmd_req lun=%u tag=0x%x cmd=0x%x"
+virtio_scsi_cmd_resp(int lun, uint32_t tag, int response, uint8_t status) "virtio_scsi_cmd_resp lun=%u tag=0x%x response=%d status=0x%x"
+virtio_scsi_tmf_req(int lun, uint32_t tag, int subtype) "virtio_scsi_tmf_req lun=%u tag=0x%x subtype=%d"
+virtio_scsi_tmf_resp(int lun, uint32_t tag, int response) "virtio_scsi_tmf_resp lun=%u tag=0x%x response=%d"
+virtio_scsi_an_req(int lun, uint32_t event_requested) "virtio_scsi_an_req lun=%u event_requested=0x%x"
+virtio_scsi_an_resp(int lun, int response) "virtio_scsi_an_resp lun=%u response=%d"
+virtio_scsi_event(int lun, int event, int reason) "virtio_scsi_event lun=%u event=%d reason=%d"
+
+# scsi-disk.c
+scsi_disk_check_condition(uint32_t tag, uint8_t key, uint8_t asc, uint8_t ascq) "Command complete tag=0x%x sense=%d/%d/%d"
+scsi_disk_read_complete(uint32_t tag, size_t size) "Data ready tag=0x%x len=%zd"
+scsi_disk_read_data_count(uint32_t sector_count) "Read sector_count=%d"
+scsi_disk_read_data_invalid(void) "Data transfer direction invalid"
+scsi_disk_write_complete_noio(uint32_t tag, size_t size) "Write complete tag=0x%x more=%zd"
+scsi_disk_write_data_invalid(void) "Data transfer direction invalid"
+scsi_disk_emulate_vpd_page_00(size_t xfer) "Inquiry EVPD[Supported pages] buffer size %zd"
+scsi_disk_emulate_vpd_page_80_not_supported(void) "Inquiry (EVPD[Serial number] not supported"
+scsi_disk_emulate_vpd_page_80(size_t xfer) "Inquiry EVPD[Serial number] buffer size %zd"
+scsi_disk_emulate_vpd_page_83(size_t xfer) "Inquiry EVPD[Device identification] buffer size %zd"
+scsi_disk_emulate_vpd_page_b0_not_supported(void) "Inquiry (EVPD[Block limits] not supported for CDROM"
+scsi_disk_emulate_mode_sense(int cmd, int page, size_t xfer, int control) "Mode Sense(%d) (page %d, xfer %zd, page_control %d)"
+scsi_disk_emulate_read_toc(int start_track, int format, int msf) "Read TOC (track %d format %d msf %d)"
+scsi_disk_emulate_read_data(int buflen) "Read buf_len=%d"
+scsi_disk_emulate_write_data(int buflen) "Write buf_len=%d"
+scsi_disk_emulate_command_SAI_16(void) "SAI READ CAPACITY(16)"
+scsi_disk_emulate_command_SAI_unsupported(void) "Unsupported Service Action In"
+scsi_disk_emulate_command_SEEK_10(uint64_t lba) "Seek(10) (sector %" PRId64 ")"
+scsi_disk_emulate_command_MODE_SELECT(size_t xfer) "Mode Select(6) (len %zd)"
+scsi_disk_emulate_command_MODE_SELECT_10(size_t xfer) "Mode Select(10) (len %zd)"
+scsi_disk_emulate_command_UNMAP(size_t xfer) "Unmap (len %zd)"
+scsi_disk_emulate_command_VERIFY(int bytchk) "Verify (bytchk %d)"
+scsi_disk_emulate_command_WRITE_SAME(int cmd, size_t xfer) "WRITE SAME %d (len %zd)"
+scsi_disk_emulate_command_UNKNOWN(int cmd, const char *name) "Unknown SCSI command (0x%2.2x=%s)"
+scsi_disk_emulate_command_FORMAT_UNIT(size_t xfer) "Format Unit (len %zu)"
+scsi_disk_dma_command_READ(uint64_t lba, uint32_t len) "Read (sector %" PRId64 ", count %u)"
+scsi_disk_dma_command_WRITE(const char *cmd, uint64_t lba, int len) "Write %s(sector %" PRId64 ", count %u)"
+scsi_disk_new_request(uint32_t lun, uint32_t tag, const char *line) "Command: lun=%d tag=0x%x data=%s"
+scsi_disk_aio_sgio_command(uint32_t tag, uint8_t cmd, uint64_t lba, int len, uint32_t timeout) "disk aio sgio: tag=0x%x cmd=0x%x (sector %" PRId64 ", count %d) timeout=%u"
+scsi_disk_mode_select_page_truncated(int page, int len, int page_len) "page %d expected length %d but received length %d"
+scsi_disk_mode_select_set_blocksize(int blocksize) "set block size to %d"
+
+# scsi-generic.c
+scsi_generic_command_complete_noio(void *req, uint32_t tag, int statuc) "Command complete %p tag=0x%x status=%d"
+scsi_generic_read_complete(uint32_t tag, int len) "Data ready tag=0x%x len=%d"
+scsi_generic_read_data(uint32_t tag) "scsi_read_data tag=0x%x"
+scsi_generic_write_complete(int ret) "scsi_write_complete() ret = %d"
+scsi_generic_write_complete_blocksize(int blocksize) "block size %d"
+scsi_generic_write_data(uint32_t tag) "scsi_write_data tag=0x%x"
+scsi_generic_send_command(const char *line) "Command: data=%s"
+scsi_generic_realize_type(int type) "device type %d"
+scsi_generic_realize_blocksize(int blocksize) "block size %d"
+scsi_generic_aio_sgio_command(uint32_t tag, uint8_t cmd, uint32_t timeout) "generic aio sgio: tag=0x%x cmd=0x%x timeout=%u"
+scsi_generic_ioctl_sgio_command(uint8_t cmd, uint32_t timeout) "generic ioctl sgio: cmd=0x%x timeout=%u"
+scsi_generic_ioctl_sgio_done(uint8_t cmd, int ret, uint8_t status, uint8_t host_status) "generic ioctl sgio: cmd=0x%x ret=%d status=0x%x host_status=0x%x"
diff --git a/hw/scsi/trace.h b/hw/scsi/trace.h
new file mode 100644
index 00000000..4ce26735
--- /dev/null
+++ b/hw/scsi/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_scsi.h"
diff --git a/hw/scsi/vhost-scsi-common.c b/hw/scsi/vhost-scsi-common.c
new file mode 100644
index 00000000..18ea5dcf
--- /dev/null
+++ b/hw/scsi/vhost-scsi-common.c
@@ -0,0 +1,171 @@
+/*
+ * vhost-scsi-common
+ *
+ * Copyright (c) 2016 Nutanix Inc. All rights reserved.
+ *
+ * Author:
+ * Felipe Franciosi <felipe@nutanix.com>
+ *
+ * This work is largely based on the "vhost-scsi" implementation by:
+ * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+ * Nicholas Bellinger <nab@risingtidesystems.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "hw/virtio/vhost.h"
+#include "hw/virtio/vhost-scsi-common.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+#include "hw/fw-path-provider.h"
+
+int vhost_scsi_common_start(VHostSCSICommon *vsc)
+{
+ int ret, i;
+ VirtIODevice *vdev = VIRTIO_DEVICE(vsc);
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+
+ VirtIOSCSICommon *vs = (VirtIOSCSICommon *)vsc;
+
+ if (!k->set_guest_notifiers) {
+ error_report("binding does not support guest notifiers");
+ return -ENOSYS;
+ }
+
+ ret = vhost_dev_enable_notifiers(&vsc->dev, vdev);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = k->set_guest_notifiers(qbus->parent, vsc->dev.nvqs, true);
+ if (ret < 0) {
+ error_report("Error binding guest notifier");
+ goto err_host_notifiers;
+ }
+
+ vsc->dev.acked_features = vdev->guest_features;
+
+ assert(vsc->inflight == NULL);
+ vsc->inflight = g_new0(struct vhost_inflight, 1);
+ ret = vhost_dev_get_inflight(&vsc->dev,
+ vs->conf.virtqueue_size,
+ vsc->inflight);
+ if (ret < 0) {
+ error_report("Error get inflight: %d", -ret);
+ goto err_guest_notifiers;
+ }
+
+ ret = vhost_dev_set_inflight(&vsc->dev, vsc->inflight);
+ if (ret < 0) {
+ error_report("Error set inflight: %d", -ret);
+ goto err_guest_notifiers;
+ }
+
+ ret = vhost_dev_start(&vsc->dev, vdev, true);
+ if (ret < 0) {
+ error_report("Error start vhost dev");
+ goto err_guest_notifiers;
+ }
+
+ /* guest_notifier_mask/pending not used yet, so just unmask
+ * everything here. virtio-pci will do the right thing by
+ * enabling/disabling irqfd.
+ */
+ for (i = 0; i < vsc->dev.nvqs; i++) {
+ vhost_virtqueue_mask(&vsc->dev, vdev, vsc->dev.vq_index + i, false);
+ }
+
+ return ret;
+
+err_guest_notifiers:
+ g_free(vsc->inflight);
+ vsc->inflight = NULL;
+
+ k->set_guest_notifiers(qbus->parent, vsc->dev.nvqs, false);
+err_host_notifiers:
+ vhost_dev_disable_notifiers(&vsc->dev, vdev);
+ return ret;
+}
+
+void vhost_scsi_common_stop(VHostSCSICommon *vsc)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vsc);
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ int ret = 0;
+
+ vhost_dev_stop(&vsc->dev, vdev, true);
+
+ if (k->set_guest_notifiers) {
+ ret = k->set_guest_notifiers(qbus->parent, vsc->dev.nvqs, false);
+ if (ret < 0) {
+ error_report("vhost guest notifier cleanup failed: %d", ret);
+ }
+ }
+ assert(ret >= 0);
+
+ if (vsc->inflight) {
+ vhost_dev_free_inflight(vsc->inflight);
+ vsc->inflight = NULL;
+ }
+
+ vhost_dev_disable_notifiers(&vsc->dev, vdev);
+}
+
+uint64_t vhost_scsi_common_get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(vdev);
+
+ /* Turn on predefined features supported by this device */
+ features |= vsc->host_features;
+
+ return vhost_get_features(&vsc->dev, vsc->feature_bits, features);
+}
+
+void vhost_scsi_common_set_config(VirtIODevice *vdev, const uint8_t *config)
+{
+ VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config;
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+
+ if ((uint32_t)virtio_ldl_p(vdev, &scsiconf->sense_size) != vs->sense_size ||
+ (uint32_t)virtio_ldl_p(vdev, &scsiconf->cdb_size) != vs->cdb_size) {
+ error_report("vhost-scsi does not support changing the sense data and "
+ "CDB sizes");
+ exit(1);
+ }
+}
+
+/*
+ * Implementation of an interface to adjust firmware path
+ * for the bootindex property handling.
+ */
+char *vhost_scsi_common_get_fw_dev_path(FWPathProvider *p, BusState *bus,
+ DeviceState *dev)
+{
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(dev);
+ /* format: /channel@channel/vhost-scsi@target,lun */
+ return g_strdup_printf("/channel@%x/%s@%x,%x", vsc->channel,
+ qdev_fw_name(dev), vsc->target, vsc->lun);
+}
+
+static const TypeInfo vhost_scsi_common_info = {
+ .name = TYPE_VHOST_SCSI_COMMON,
+ .parent = TYPE_VIRTIO_SCSI_COMMON,
+ .instance_size = sizeof(VHostSCSICommon),
+ .abstract = true,
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&vhost_scsi_common_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/scsi/vhost-scsi.c b/hw/scsi/vhost-scsi.c
new file mode 100644
index 00000000..6a0fd0df
--- /dev/null
+++ b/hw/scsi/vhost-scsi.c
@@ -0,0 +1,349 @@
+/*
+ * vhost_scsi host device
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+ *
+ * Changes for QEMU mainline + tcm_vhost kernel upstream:
+ * Nicholas Bellinger <nab@risingtidesystems.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include <linux/vhost.h>
+#include <sys/ioctl.h>
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "monitor/monitor.h"
+#include "migration/blocker.h"
+#include "hw/virtio/vhost-scsi.h"
+#include "hw/virtio/vhost.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+#include "hw/fw-path-provider.h"
+#include "hw/qdev-properties.h"
+#include "qemu/cutils.h"
+#include "sysemu/sysemu.h"
+
+/* Features supported by host kernel. */
+static const int kernel_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+ VIRTIO_SCSI_F_HOTPLUG,
+ VIRTIO_F_RING_RESET,
+ VHOST_INVALID_FEATURE_BIT
+};
+
+static int vhost_scsi_set_endpoint(VHostSCSI *s)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ const VhostOps *vhost_ops = vsc->dev.vhost_ops;
+ struct vhost_scsi_target backend;
+ int ret;
+
+ memset(&backend, 0, sizeof(backend));
+ pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn);
+ ret = vhost_ops->vhost_scsi_set_endpoint(&vsc->dev, &backend);
+ if (ret < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static void vhost_scsi_clear_endpoint(VHostSCSI *s)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ struct vhost_scsi_target backend;
+ const VhostOps *vhost_ops = vsc->dev.vhost_ops;
+
+ memset(&backend, 0, sizeof(backend));
+ pstrcpy(backend.vhost_wwpn, sizeof(backend.vhost_wwpn), vs->conf.wwpn);
+ vhost_ops->vhost_scsi_clear_endpoint(&vsc->dev, &backend);
+}
+
+static int vhost_scsi_start(VHostSCSI *s)
+{
+ int ret, abi_version;
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ const VhostOps *vhost_ops = vsc->dev.vhost_ops;
+
+ ret = vhost_ops->vhost_scsi_get_abi_version(&vsc->dev, &abi_version);
+ if (ret < 0) {
+ return -errno;
+ }
+ if (abi_version > VHOST_SCSI_ABI_VERSION) {
+ error_report("vhost-scsi: The running tcm_vhost kernel abi_version:"
+ " %d is greater than vhost_scsi userspace supports: %d,"
+ " please upgrade your version of QEMU", abi_version,
+ VHOST_SCSI_ABI_VERSION);
+ return -ENOSYS;
+ }
+
+ ret = vhost_scsi_common_start(vsc);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = vhost_scsi_set_endpoint(s);
+ if (ret < 0) {
+ error_report("Error setting vhost-scsi endpoint");
+ vhost_scsi_common_stop(vsc);
+ }
+
+ return ret;
+}
+
+static void vhost_scsi_stop(VHostSCSI *s)
+{
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+
+ vhost_scsi_clear_endpoint(s);
+ vhost_scsi_common_stop(vsc);
+}
+
+static void vhost_scsi_set_status(VirtIODevice *vdev, uint8_t val)
+{
+ VHostSCSI *s = VHOST_SCSI(vdev);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ bool start = (val & VIRTIO_CONFIG_S_DRIVER_OK);
+
+ if (!vdev->vm_running) {
+ start = false;
+ }
+
+ if (vhost_dev_is_started(&vsc->dev) == start) {
+ return;
+ }
+
+ if (start) {
+ int ret;
+
+ ret = vhost_scsi_start(s);
+ if (ret < 0) {
+ error_report("unable to start vhost-scsi: %s", strerror(-ret));
+ exit(1);
+ }
+ } else {
+ vhost_scsi_stop(s);
+ }
+}
+
+static void vhost_dummy_handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+static int vhost_scsi_pre_save(void *opaque)
+{
+ VHostSCSICommon *vsc = opaque;
+
+ /* At this point, backend must be stopped, otherwise
+ * it might keep writing to memory. */
+ assert(!vhost_dev_is_started(&vsc->dev));
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_virtio_vhost_scsi = {
+ .name = "virtio-vhost_scsi",
+ .minimum_version_id = 1,
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_VIRTIO_DEVICE,
+ VMSTATE_END_OF_LIST()
+ },
+ .pre_save = vhost_scsi_pre_save,
+};
+
+static void vhost_scsi_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(dev);
+ Error *err = NULL;
+ int vhostfd = -1;
+ int ret;
+ struct vhost_virtqueue *vqs = NULL;
+
+ if (!vs->conf.wwpn) {
+ error_setg(errp, "vhost-scsi: missing wwpn");
+ return;
+ }
+
+ if (vs->conf.vhostfd) {
+ vhostfd = monitor_fd_param(monitor_cur(), vs->conf.vhostfd, errp);
+ if (vhostfd == -1) {
+ error_prepend(errp, "vhost-scsi: unable to parse vhostfd: ");
+ return;
+ }
+ } else {
+ vhostfd = open("/dev/vhost-scsi", O_RDWR);
+ if (vhostfd < 0) {
+ error_setg(errp, "vhost-scsi: open vhost char device failed: %s",
+ strerror(errno));
+ return;
+ }
+ }
+
+ virtio_scsi_common_realize(dev,
+ vhost_dummy_handle_output,
+ vhost_dummy_handle_output,
+ vhost_dummy_handle_output,
+ &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ goto close_fd;
+ }
+
+ if (!vsc->migratable) {
+ error_setg(&vsc->migration_blocker,
+ "vhost-scsi does not support migration in all cases. "
+ "When external environment supports it (Orchestrator migrates "
+ "target SCSI device state or use shared storage over network), "
+ "set 'migratable' property to true to enable migration.");
+ if (migrate_add_blocker(vsc->migration_blocker, errp) < 0) {
+ goto free_virtio;
+ }
+ }
+
+ vsc->dev.nvqs = VHOST_SCSI_VQ_NUM_FIXED + vs->conf.num_queues;
+ vqs = g_new0(struct vhost_virtqueue, vsc->dev.nvqs);
+ vsc->dev.vqs = vqs;
+ vsc->dev.vq_index = 0;
+ vsc->dev.backend_features = 0;
+
+ ret = vhost_dev_init(&vsc->dev, (void *)(uintptr_t)vhostfd,
+ VHOST_BACKEND_TYPE_KERNEL, 0, errp);
+ if (ret < 0) {
+ /*
+ * vhost_dev_init calls vhost_dev_cleanup on error, which closes
+ * vhostfd, don't double close it.
+ */
+ vhostfd = -1;
+ goto free_vqs;
+ }
+
+ /* At present, channel and lun both are 0 for bootable vhost-scsi disk */
+ vsc->channel = 0;
+ vsc->lun = 0;
+ /* Note: we can also get the minimum tpgt from kernel */
+ vsc->target = vs->conf.boot_tpgt;
+
+ return;
+
+ free_vqs:
+ g_free(vqs);
+ if (!vsc->migratable) {
+ migrate_del_blocker(vsc->migration_blocker);
+ }
+ free_virtio:
+ error_free(vsc->migration_blocker);
+ virtio_scsi_common_unrealize(dev);
+ close_fd:
+ if (vhostfd >= 0) {
+ close(vhostfd);
+ }
+ return;
+}
+
+static void vhost_scsi_unrealize(DeviceState *dev)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(dev);
+ struct vhost_virtqueue *vqs = vsc->dev.vqs;
+
+ if (!vsc->migratable) {
+ migrate_del_blocker(vsc->migration_blocker);
+ error_free(vsc->migration_blocker);
+ }
+
+ /* This will stop vhost backend. */
+ vhost_scsi_set_status(vdev, 0);
+
+ vhost_dev_cleanup(&vsc->dev);
+ g_free(vqs);
+
+ virtio_scsi_common_unrealize(dev);
+}
+
+static struct vhost_dev *vhost_scsi_get_vhost(VirtIODevice *vdev)
+{
+ VHostSCSI *s = VHOST_SCSI(vdev);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ return &vsc->dev;
+}
+
+static Property vhost_scsi_properties[] = {
+ DEFINE_PROP_STRING("vhostfd", VirtIOSCSICommon, conf.vhostfd),
+ DEFINE_PROP_STRING("wwpn", VirtIOSCSICommon, conf.wwpn),
+ DEFINE_PROP_UINT32("boot_tpgt", VirtIOSCSICommon, conf.boot_tpgt, 0),
+ DEFINE_PROP_UINT32("num_queues", VirtIOSCSICommon, conf.num_queues,
+ VIRTIO_SCSI_AUTO_NUM_QUEUES),
+ DEFINE_PROP_UINT32("virtqueue_size", VirtIOSCSICommon, conf.virtqueue_size,
+ 128),
+ DEFINE_PROP_BOOL("seg_max_adjust", VirtIOSCSICommon, conf.seg_max_adjust,
+ true),
+ DEFINE_PROP_UINT32("max_sectors", VirtIOSCSICommon, conf.max_sectors,
+ 0xFFFF),
+ DEFINE_PROP_UINT32("cmd_per_lun", VirtIOSCSICommon, conf.cmd_per_lun, 128),
+ DEFINE_PROP_BIT64("t10_pi", VHostSCSICommon, host_features,
+ VIRTIO_SCSI_F_T10_PI,
+ false),
+ DEFINE_PROP_BOOL("migratable", VHostSCSICommon, migratable, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vhost_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ FWPathProviderClass *fwc = FW_PATH_PROVIDER_CLASS(klass);
+
+ device_class_set_props(dc, vhost_scsi_properties);
+ dc->vmsd = &vmstate_virtio_vhost_scsi;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ vdc->realize = vhost_scsi_realize;
+ vdc->unrealize = vhost_scsi_unrealize;
+ vdc->get_features = vhost_scsi_common_get_features;
+ vdc->set_config = vhost_scsi_common_set_config;
+ vdc->set_status = vhost_scsi_set_status;
+ vdc->get_vhost = vhost_scsi_get_vhost;
+ fwc->get_dev_path = vhost_scsi_common_get_fw_dev_path;
+}
+
+static void vhost_scsi_instance_init(Object *obj)
+{
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(obj);
+
+ vsc->feature_bits = kernel_feature_bits;
+
+ device_add_bootindex_property(obj, &vsc->bootindex, "bootindex", NULL,
+ DEVICE(vsc));
+}
+
+static const TypeInfo vhost_scsi_info = {
+ .name = TYPE_VHOST_SCSI,
+ .parent = TYPE_VHOST_SCSI_COMMON,
+ .instance_size = sizeof(VHostSCSI),
+ .class_init = vhost_scsi_class_init,
+ .instance_init = vhost_scsi_instance_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_FW_PATH_PROVIDER },
+ { }
+ },
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&vhost_scsi_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/scsi/vhost-user-scsi.c b/hw/scsi/vhost-user-scsi.c
new file mode 100644
index 00000000..b7a71a80
--- /dev/null
+++ b/hw/scsi/vhost-user-scsi.c
@@ -0,0 +1,240 @@
+/*
+ * vhost-user-scsi host device
+ *
+ * Copyright (c) 2016 Nutanix Inc. All rights reserved.
+ *
+ * Author:
+ * Felipe Franciosi <felipe@nutanix.com>
+ *
+ * This work is largely based on the "vhost-scsi" implementation by:
+ * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+ * Nicholas Bellinger <nab@risingtidesystems.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "hw/fw-path-provider.h"
+#include "hw/qdev-core.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "hw/virtio/vhost.h"
+#include "hw/virtio/vhost-backend.h"
+#include "hw/virtio/vhost-user-scsi.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-access.h"
+#include "chardev/char-fe.h"
+#include "sysemu/sysemu.h"
+
+/* Features supported by the host application */
+static const int user_feature_bits[] = {
+ VIRTIO_F_NOTIFY_ON_EMPTY,
+ VIRTIO_RING_F_INDIRECT_DESC,
+ VIRTIO_RING_F_EVENT_IDX,
+ VIRTIO_SCSI_F_HOTPLUG,
+ VIRTIO_F_RING_RESET,
+ VHOST_INVALID_FEATURE_BIT
+};
+
+enum VhostUserProtocolFeature {
+ VHOST_USER_PROTOCOL_F_RESET_DEVICE = 13,
+};
+
+static void vhost_user_scsi_set_status(VirtIODevice *vdev, uint8_t status)
+{
+ VHostUserSCSI *s = (VHostUserSCSI *)vdev;
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ bool start = (status & VIRTIO_CONFIG_S_DRIVER_OK) && vdev->vm_running;
+
+ if (vhost_dev_is_started(&vsc->dev) == start) {
+ return;
+ }
+
+ if (start) {
+ int ret;
+
+ ret = vhost_scsi_common_start(vsc);
+ if (ret < 0) {
+ error_report("unable to start vhost-user-scsi: %s", strerror(-ret));
+ exit(1);
+ }
+ } else {
+ vhost_scsi_common_stop(vsc);
+ }
+}
+
+static void vhost_user_scsi_reset(VirtIODevice *vdev)
+{
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(vdev);
+ struct vhost_dev *dev = &vsc->dev;
+
+ /*
+ * Historically, reset was not implemented so only reset devices
+ * that are expecting it.
+ */
+ if (!virtio_has_feature(dev->protocol_features,
+ VHOST_USER_PROTOCOL_F_RESET_DEVICE)) {
+ return;
+ }
+
+ if (dev->vhost_ops->vhost_reset_device) {
+ dev->vhost_ops->vhost_reset_device(dev);
+ }
+}
+
+static void vhost_dummy_handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+static void vhost_user_scsi_realize(DeviceState *dev, Error **errp)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
+ VHostUserSCSI *s = VHOST_USER_SCSI(dev);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ struct vhost_virtqueue *vqs = NULL;
+ Error *err = NULL;
+ int ret;
+
+ if (!vs->conf.chardev.chr) {
+ error_setg(errp, "vhost-user-scsi: missing chardev");
+ return;
+ }
+
+ virtio_scsi_common_realize(dev, vhost_dummy_handle_output,
+ vhost_dummy_handle_output,
+ vhost_dummy_handle_output, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ if (!vhost_user_init(&s->vhost_user, &vs->conf.chardev, errp)) {
+ goto free_virtio;
+ }
+
+ vsc->dev.nvqs = VIRTIO_SCSI_VQ_NUM_FIXED + vs->conf.num_queues;
+ vsc->dev.vqs = g_new0(struct vhost_virtqueue, vsc->dev.nvqs);
+ vsc->dev.vq_index = 0;
+ vsc->dev.backend_features = 0;
+ vqs = vsc->dev.vqs;
+
+ ret = vhost_dev_init(&vsc->dev, &s->vhost_user,
+ VHOST_BACKEND_TYPE_USER, 0, errp);
+ if (ret < 0) {
+ goto free_vhost;
+ }
+
+ /* Channel and lun both are 0 for bootable vhost-user-scsi disk */
+ vsc->channel = 0;
+ vsc->lun = 0;
+ vsc->target = vs->conf.boot_tpgt;
+
+ return;
+
+free_vhost:
+ vhost_user_cleanup(&s->vhost_user);
+ g_free(vqs);
+free_virtio:
+ virtio_scsi_common_unrealize(dev);
+}
+
+static void vhost_user_scsi_unrealize(DeviceState *dev)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VHostUserSCSI *s = VHOST_USER_SCSI(dev);
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(s);
+ struct vhost_virtqueue *vqs = vsc->dev.vqs;
+
+ /* This will stop the vhost backend. */
+ vhost_user_scsi_set_status(vdev, 0);
+
+ vhost_dev_cleanup(&vsc->dev);
+ g_free(vqs);
+
+ virtio_scsi_common_unrealize(dev);
+ vhost_user_cleanup(&s->vhost_user);
+}
+
+static Property vhost_user_scsi_properties[] = {
+ DEFINE_PROP_CHR("chardev", VirtIOSCSICommon, conf.chardev),
+ DEFINE_PROP_UINT32("boot_tpgt", VirtIOSCSICommon, conf.boot_tpgt, 0),
+ DEFINE_PROP_UINT32("num_queues", VirtIOSCSICommon, conf.num_queues,
+ VIRTIO_SCSI_AUTO_NUM_QUEUES),
+ DEFINE_PROP_UINT32("virtqueue_size", VirtIOSCSICommon, conf.virtqueue_size,
+ 128),
+ DEFINE_PROP_UINT32("max_sectors", VirtIOSCSICommon, conf.max_sectors,
+ 0xFFFF),
+ DEFINE_PROP_UINT32("cmd_per_lun", VirtIOSCSICommon, conf.cmd_per_lun, 128),
+ DEFINE_PROP_BIT64("hotplug", VHostSCSICommon, host_features,
+ VIRTIO_SCSI_F_HOTPLUG,
+ true),
+ DEFINE_PROP_BIT64("param_change", VHostSCSICommon, host_features,
+ VIRTIO_SCSI_F_CHANGE,
+ true),
+ DEFINE_PROP_BIT64("t10_pi", VHostSCSICommon, host_features,
+ VIRTIO_SCSI_F_T10_PI,
+ false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_vhost_scsi = {
+ .name = "virtio-scsi",
+ .minimum_version_id = 1,
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_VIRTIO_DEVICE,
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void vhost_user_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ FWPathProviderClass *fwc = FW_PATH_PROVIDER_CLASS(klass);
+
+ device_class_set_props(dc, vhost_user_scsi_properties);
+ dc->vmsd = &vmstate_vhost_scsi;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ vdc->realize = vhost_user_scsi_realize;
+ vdc->unrealize = vhost_user_scsi_unrealize;
+ vdc->get_features = vhost_scsi_common_get_features;
+ vdc->set_config = vhost_scsi_common_set_config;
+ vdc->set_status = vhost_user_scsi_set_status;
+ vdc->reset = vhost_user_scsi_reset;
+ fwc->get_dev_path = vhost_scsi_common_get_fw_dev_path;
+}
+
+static void vhost_user_scsi_instance_init(Object *obj)
+{
+ VHostSCSICommon *vsc = VHOST_SCSI_COMMON(obj);
+
+ vsc->feature_bits = user_feature_bits;
+
+ /* Add the bootindex property for this object */
+ device_add_bootindex_property(obj, &vsc->bootindex, "bootindex", NULL,
+ DEVICE(vsc));
+}
+
+static const TypeInfo vhost_user_scsi_info = {
+ .name = TYPE_VHOST_USER_SCSI,
+ .parent = TYPE_VHOST_SCSI_COMMON,
+ .instance_size = sizeof(VHostUserSCSI),
+ .class_init = vhost_user_scsi_class_init,
+ .instance_init = vhost_user_scsi_instance_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_FW_PATH_PROVIDER },
+ { }
+ },
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&vhost_user_scsi_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/scsi/viosrp.h b/hw/scsi/viosrp.h
new file mode 100644
index 00000000..e5f9768e
--- /dev/null
+++ b/hw/scsi/viosrp.h
@@ -0,0 +1,217 @@
+/*****************************************************************************/
+/* srp.h -- SCSI RDMA Protocol definitions */
+/* */
+/* Written By: Colin Devilbis, IBM Corporation */
+/* */
+/* Copyright (C) 2003 IBM Corporation */
+/* */
+/* This program is free software; you can redistribute it and/or modify */
+/* it under the terms of the GNU General Public License as published by */
+/* the Free Software Foundation; either version 2 of the License, or */
+/* (at your option) any later version. */
+/* */
+/* This program is distributed in the hope that it will be useful, */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
+/* GNU General Public License for more details. */
+/* */
+/* You should have received a copy of the GNU General Public License */
+/* along with this program; if not, write to the Free Software */
+/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+/* */
+/* */
+/* This file contains structures and definitions for IBM RPA (RS/6000 */
+/* platform architecture) implementation of the SRP (SCSI RDMA Protocol) */
+/* standard. SRP is used on IBM iSeries and pSeries platforms to send SCSI */
+/* commands between logical partitions. */
+/* */
+/* SRP Information Units (IUs) are sent on a "Command/Response Queue" (CRQ) */
+/* between partitions. The definitions in this file are architected, */
+/* and cannot be changed without breaking compatibility with other versions */
+/* of Linux and other operating systems (AIX, OS/400) that talk this protocol*/
+/* between logical partitions */
+/*****************************************************************************/
+#ifndef PPC_VIOSRP_H
+#define PPC_VIOSRP_H
+
+#include "hw/scsi/srp.h"
+
+#define SRP_VERSION "16.a"
+#define SRP_MAX_IU_LEN 256
+#define SRP_MAX_LOC_LEN 32
+
+union srp_iu {
+ struct srp_login_req login_req;
+ struct srp_login_rsp login_rsp;
+ struct srp_login_rej login_rej;
+ struct srp_i_logout i_logout;
+ struct srp_t_logout t_logout;
+ struct srp_tsk_mgmt tsk_mgmt;
+ struct srp_cmd cmd;
+ struct srp_rsp rsp;
+};
+
+enum viosrp_crq_formats {
+ VIOSRP_SRP_FORMAT = 0x01,
+ VIOSRP_MAD_FORMAT = 0x02,
+ VIOSRP_OS400_FORMAT = 0x03,
+ VIOSRP_AIX_FORMAT = 0x04,
+ VIOSRP_LINUX_FORMAT = 0x06,
+ VIOSRP_INLINE_FORMAT = 0x07
+};
+
+enum viosrp_crq_status {
+ VIOSRP_OK = 0x0,
+ VIOSRP_NONRECOVERABLE_ERR = 0x1,
+ VIOSRP_VIOLATES_MAX_XFER = 0x2,
+ VIOSRP_PARTNER_PANIC = 0x3,
+ VIOSRP_DEVICE_BUSY = 0x8,
+ VIOSRP_ADAPTER_FAIL = 0x10,
+ VIOSRP_OK2 = 0x99,
+};
+
+struct viosrp_crq {
+ uint8_t valid; /* used by RPA */
+ uint8_t format; /* SCSI vs out-of-band */
+ uint8_t reserved;
+ uint8_t status; /* non-scsi failure? (e.g. DMA failure) */
+ uint16_t timeout; /* in seconds */
+ uint16_t IU_length; /* in bytes */
+ uint64_t IU_data_ptr; /* the TCE for transferring data */
+};
+
+/* MADs are Management requests above and beyond the IUs defined in the SRP
+ * standard.
+ */
+enum viosrp_mad_types {
+ VIOSRP_EMPTY_IU_TYPE = 0x01,
+ VIOSRP_ERROR_LOG_TYPE = 0x02,
+ VIOSRP_ADAPTER_INFO_TYPE = 0x03,
+ VIOSRP_HOST_CONFIG_TYPE = 0x04,
+ VIOSRP_CAPABILITIES_TYPE = 0x05,
+ VIOSRP_ENABLE_FAST_FAIL = 0x08,
+};
+
+enum viosrp_mad_status {
+ VIOSRP_MAD_SUCCESS = 0x00,
+ VIOSRP_MAD_NOT_SUPPORTED = 0xF1,
+ VIOSRP_MAD_FAILED = 0xF7,
+};
+
+enum viosrp_capability_type {
+ MIGRATION_CAPABILITIES = 0x01,
+ RESERVATION_CAPABILITIES = 0x02,
+};
+
+enum viosrp_capability_support {
+ SERVER_DOES_NOT_SUPPORTS_CAP = 0x0,
+ SERVER_SUPPORTS_CAP = 0x01,
+ SERVER_CAP_DATA = 0x02,
+};
+
+enum viosrp_reserve_type {
+ CLIENT_RESERVE_SCSI_2 = 0x01,
+};
+
+enum viosrp_capability_flag {
+ CLIENT_MIGRATED = 0x01,
+ CLIENT_RECONNECT = 0x02,
+ CAP_LIST_SUPPORTED = 0x04,
+ CAP_LIST_DATA = 0x08,
+};
+
+/*
+ * Common MAD header
+ */
+struct mad_common {
+ uint32_t type;
+ uint16_t status;
+ uint16_t length;
+ uint64_t tag;
+};
+
+/*
+ * All SRP (and MAD) requests normally flow from the
+ * client to the server. There is no way for the server to send
+ * an asynchronous message back to the client. The Empty IU is used
+ * to hang out a meaningless request to the server so that it can respond
+ * asynchrouously with something like a SCSI AER
+ */
+struct viosrp_empty_iu {
+ struct mad_common common;
+ uint64_t buffer;
+ uint32_t port;
+};
+
+struct viosrp_error_log {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct viosrp_adapter_info {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct viosrp_host_config {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct viosrp_fast_fail {
+ struct mad_common common;
+};
+
+struct viosrp_capabilities {
+ struct mad_common common;
+ uint64_t buffer;
+};
+
+struct mad_capability_common {
+ uint32_t cap_type;
+ uint16_t length;
+ uint16_t server_support;
+};
+
+struct mad_reserve_cap {
+ struct mad_capability_common common;
+ uint32_t type;
+};
+
+struct mad_migration_cap {
+ struct mad_capability_common common;
+ uint32_t ecl;
+};
+
+struct capabilities {
+ uint32_t flags;
+ char name[SRP_MAX_LOC_LEN];
+ char loc[SRP_MAX_LOC_LEN];
+ struct mad_migration_cap migration;
+ struct mad_reserve_cap reserve;
+};
+
+union mad_iu {
+ struct viosrp_empty_iu empty_iu;
+ struct viosrp_error_log error_log;
+ struct viosrp_adapter_info adapter_info;
+ struct viosrp_host_config host_config;
+ struct viosrp_fast_fail fast_fail;
+ struct viosrp_capabilities capabilities;
+};
+
+union viosrp_iu {
+ union srp_iu srp;
+ union mad_iu mad;
+};
+
+struct mad_adapter_info_data {
+ char srp_version[8];
+ char partition_name[96];
+ uint32_t partition_number;
+ uint32_t mad_version;
+ uint32_t os_type;
+ uint32_t port_max_txu[8]; /* per-port maximum transfer */
+};
+
+#endif
diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c
new file mode 100644
index 00000000..20bb9176
--- /dev/null
+++ b/hw/scsi/virtio-scsi-dataplane.c
@@ -0,0 +1,230 @@
+/*
+ * Virtio SCSI dataplane
+ *
+ * Copyright Red Hat, Inc. 2014
+ *
+ * Authors:
+ * Fam Zheng <famz@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "qemu/error-report.h"
+#include "sysemu/block-backend.h"
+#include "hw/scsi/scsi.h"
+#include "scsi/constants.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+
+/* Context: QEMU global mutex held */
+void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+
+ if (vs->conf.iothread) {
+ if (!k->set_guest_notifiers || !k->ioeventfd_assign) {
+ error_setg(errp,
+ "device is incompatible with iothread "
+ "(transport does not support notifiers)");
+ return;
+ }
+ if (!virtio_device_ioeventfd_enabled(vdev)) {
+ error_setg(errp, "ioeventfd is required for iothread");
+ return;
+ }
+ s->ctx = iothread_get_aio_context(vs->conf.iothread);
+ } else {
+ if (!virtio_device_ioeventfd_enabled(vdev)) {
+ return;
+ }
+ s->ctx = qemu_get_aio_context();
+ }
+}
+
+static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
+{
+ BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s)));
+ int rc;
+
+ /* Set up virtqueue notify */
+ rc = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), n, true);
+ if (rc != 0) {
+ fprintf(stderr, "virtio-scsi: Failed to set host notifier (%d)\n",
+ rc);
+ s->dataplane_fenced = true;
+ return rc;
+ }
+
+ return 0;
+}
+
+/* Context: BH in IOThread */
+static void virtio_scsi_dataplane_stop_bh(void *opaque)
+{
+ VirtIOSCSI *s = opaque;
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ int i;
+
+ virtio_queue_aio_detach_host_notifier(vs->ctrl_vq, s->ctx);
+ virtio_queue_aio_detach_host_notifier(vs->event_vq, s->ctx);
+ for (i = 0; i < vs->conf.num_queues; i++) {
+ virtio_queue_aio_detach_host_notifier(vs->cmd_vqs[i], s->ctx);
+ }
+}
+
+/* Context: QEMU global mutex held */
+int virtio_scsi_dataplane_start(VirtIODevice *vdev)
+{
+ int i;
+ int rc;
+ int vq_init_count = 0;
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+
+ if (s->dataplane_started ||
+ s->dataplane_starting ||
+ s->dataplane_fenced) {
+ return 0;
+ }
+
+ s->dataplane_starting = true;
+
+ /* Set up guest notifier (irq) */
+ rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true);
+ if (rc != 0) {
+ error_report("virtio-scsi: Failed to set guest notifiers (%d), "
+ "ensure -accel kvm is set.", rc);
+ goto fail_guest_notifiers;
+ }
+
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
+ memory_region_transaction_begin();
+
+ rc = virtio_scsi_set_host_notifier(s, vs->ctrl_vq, 0);
+ if (rc != 0) {
+ goto fail_host_notifiers;
+ }
+
+ vq_init_count++;
+ rc = virtio_scsi_set_host_notifier(s, vs->event_vq, 1);
+ if (rc != 0) {
+ goto fail_host_notifiers;
+ }
+
+ vq_init_count++;
+
+ for (i = 0; i < vs->conf.num_queues; i++) {
+ rc = virtio_scsi_set_host_notifier(s, vs->cmd_vqs[i], i + 2);
+ if (rc) {
+ goto fail_host_notifiers;
+ }
+ vq_init_count++;
+ }
+
+ memory_region_transaction_commit();
+
+ /*
+ * These fields are visible to the IOThread so we rely on implicit barriers
+ * in aio_context_acquire() on the write side and aio_notify_accept() on
+ * the read side.
+ */
+ s->dataplane_starting = false;
+ s->dataplane_started = true;
+
+ aio_context_acquire(s->ctx);
+ virtio_queue_aio_attach_host_notifier(vs->ctrl_vq, s->ctx);
+ virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq, s->ctx);
+
+ for (i = 0; i < vs->conf.num_queues; i++) {
+ virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], s->ctx);
+ }
+ aio_context_release(s->ctx);
+ return 0;
+
+fail_host_notifiers:
+ for (i = 0; i < vq_init_count; i++) {
+ virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
+ }
+
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
+ memory_region_transaction_commit();
+
+ for (i = 0; i < vq_init_count; i++) {
+ virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
+ }
+ k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
+fail_guest_notifiers:
+ s->dataplane_fenced = true;
+ s->dataplane_starting = false;
+ s->dataplane_started = true;
+ return -ENOSYS;
+}
+
+/* Context: QEMU global mutex held */
+void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
+{
+ BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
+ VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+ int i;
+
+ if (!s->dataplane_started || s->dataplane_stopping) {
+ return;
+ }
+
+ /* Better luck next time. */
+ if (s->dataplane_fenced) {
+ s->dataplane_fenced = false;
+ s->dataplane_started = false;
+ return;
+ }
+ s->dataplane_stopping = true;
+
+ aio_context_acquire(s->ctx);
+ aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s);
+ aio_context_release(s->ctx);
+
+ blk_drain_all(); /* ensure there are no in-flight requests */
+
+ /*
+ * Batch all the host notifiers in a single transaction to avoid
+ * quadratic time complexity in address_space_update_ioeventfds().
+ */
+ memory_region_transaction_begin();
+
+ for (i = 0; i < vs->conf.num_queues + 2; i++) {
+ virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false);
+ }
+
+ /*
+ * The transaction expects the ioeventfds to be open when it
+ * commits. Do it now, before the cleanup loop.
+ */
+ memory_region_transaction_commit();
+
+ for (i = 0; i < vs->conf.num_queues + 2; i++) {
+ virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i);
+ }
+
+ /* Clean up guest notifier (irq) */
+ k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false);
+ s->dataplane_stopping = false;
+ s->dataplane_started = false;
+}
diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
new file mode 100644
index 00000000..6f6e2e32
--- /dev/null
+++ b/hw/scsi/virtio-scsi.c
@@ -0,0 +1,1181 @@
+/*
+ * Virtio SCSI HBA
+ *
+ * Copyright IBM, Corp. 2010
+ * Copyright Red Hat, Inc. 2011
+ *
+ * Authors:
+ * Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
+ * Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "standard-headers/linux/virtio_ids.h"
+#include "hw/virtio/virtio-scsi.h"
+#include "migration/qemu-file-types.h"
+#include "qemu/error-report.h"
+#include "qemu/iov.h"
+#include "qemu/module.h"
+#include "sysemu/block-backend.h"
+#include "hw/qdev-properties.h"
+#include "hw/scsi/scsi.h"
+#include "scsi/constants.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-access.h"
+#include "trace.h"
+
+typedef struct VirtIOSCSIReq {
+ /*
+ * Note:
+ * - fields up to resp_iov are initialized by virtio_scsi_init_req;
+ * - fields starting at vring are zeroed by virtio_scsi_init_req.
+ */
+ VirtQueueElement elem;
+
+ VirtIOSCSI *dev;
+ VirtQueue *vq;
+ QEMUSGList qsgl;
+ QEMUIOVector resp_iov;
+
+ union {
+ /* Used for two-stage request submission */
+ QTAILQ_ENTRY(VirtIOSCSIReq) next;
+
+ /* Used for cancellation of request during TMFs */
+ int remaining;
+ };
+
+ SCSIRequest *sreq;
+ size_t resp_size;
+ enum SCSIXferMode mode;
+ union {
+ VirtIOSCSICmdResp cmd;
+ VirtIOSCSICtrlTMFResp tmf;
+ VirtIOSCSICtrlANResp an;
+ VirtIOSCSIEvent event;
+ } resp;
+ union {
+ VirtIOSCSICmdReq cmd;
+ VirtIOSCSICtrlTMFReq tmf;
+ VirtIOSCSICtrlANReq an;
+ } req;
+} VirtIOSCSIReq;
+
+static inline int virtio_scsi_get_lun(uint8_t *lun)
+{
+ return ((lun[2] << 8) | lun[3]) & 0x3FFF;
+}
+
+static inline SCSIDevice *virtio_scsi_device_get(VirtIOSCSI *s, uint8_t *lun)
+{
+ if (lun[0] != 1) {
+ return NULL;
+ }
+ if (lun[2] != 0 && !(lun[2] >= 0x40 && lun[2] < 0x80)) {
+ return NULL;
+ }
+ return scsi_device_get(&s->bus, 0, lun[1], virtio_scsi_get_lun(lun));
+}
+
+static void virtio_scsi_init_req(VirtIOSCSI *s, VirtQueue *vq, VirtIOSCSIReq *req)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ const size_t zero_skip =
+ offsetof(VirtIOSCSIReq, resp_iov) + sizeof(req->resp_iov);
+
+ req->vq = vq;
+ req->dev = s;
+ qemu_sglist_init(&req->qsgl, DEVICE(s), 8, vdev->dma_as);
+ qemu_iovec_init(&req->resp_iov, 1);
+ memset((uint8_t *)req + zero_skip, 0, sizeof(*req) - zero_skip);
+}
+
+static void virtio_scsi_free_req(VirtIOSCSIReq *req)
+{
+ qemu_iovec_destroy(&req->resp_iov);
+ qemu_sglist_destroy(&req->qsgl);
+ g_free(req);
+}
+
+static void virtio_scsi_complete_req(VirtIOSCSIReq *req)
+{
+ VirtIOSCSI *s = req->dev;
+ VirtQueue *vq = req->vq;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ qemu_iovec_from_buf(&req->resp_iov, 0, &req->resp, req->resp_size);
+ virtqueue_push(vq, &req->elem, req->qsgl.size + req->resp_iov.size);
+ if (s->dataplane_started && !s->dataplane_fenced) {
+ virtio_notify_irqfd(vdev, vq);
+ } else {
+ virtio_notify(vdev, vq);
+ }
+
+ if (req->sreq) {
+ req->sreq->hba_private = NULL;
+ scsi_req_unref(req->sreq);
+ }
+ virtio_scsi_free_req(req);
+}
+
+static void virtio_scsi_bad_req(VirtIOSCSIReq *req)
+{
+ virtio_error(VIRTIO_DEVICE(req->dev), "wrong size for virtio-scsi headers");
+ virtqueue_detach_element(req->vq, &req->elem, 0);
+ virtio_scsi_free_req(req);
+}
+
+static size_t qemu_sgl_concat(VirtIOSCSIReq *req, struct iovec *iov,
+ hwaddr *addr, int num, size_t skip)
+{
+ QEMUSGList *qsgl = &req->qsgl;
+ size_t copied = 0;
+
+ while (num) {
+ if (skip >= iov->iov_len) {
+ skip -= iov->iov_len;
+ } else {
+ qemu_sglist_add(qsgl, *addr + skip, iov->iov_len - skip);
+ copied += iov->iov_len - skip;
+ skip = 0;
+ }
+ iov++;
+ addr++;
+ num--;
+ }
+
+ assert(skip == 0);
+ return copied;
+}
+
+static int virtio_scsi_parse_req(VirtIOSCSIReq *req,
+ unsigned req_size, unsigned resp_size)
+{
+ VirtIODevice *vdev = (VirtIODevice *) req->dev;
+ size_t in_size, out_size;
+
+ if (iov_to_buf(req->elem.out_sg, req->elem.out_num, 0,
+ &req->req, req_size) < req_size) {
+ return -EINVAL;
+ }
+
+ if (qemu_iovec_concat_iov(&req->resp_iov,
+ req->elem.in_sg, req->elem.in_num, 0,
+ resp_size) < resp_size) {
+ return -EINVAL;
+ }
+
+ req->resp_size = resp_size;
+
+ /* Old BIOSes left some padding by mistake after the req_size/resp_size.
+ * As a workaround, always consider the first buffer as the virtio-scsi
+ * request/response, making the payload start at the second element
+ * of the iovec.
+ *
+ * The actual length of the response header, stored in req->resp_size,
+ * does not change.
+ *
+ * TODO: always disable this workaround for virtio 1.0 devices.
+ */
+ if (!virtio_vdev_has_feature(vdev, VIRTIO_F_ANY_LAYOUT)) {
+ if (req->elem.out_num) {
+ req_size = req->elem.out_sg[0].iov_len;
+ }
+ if (req->elem.in_num) {
+ resp_size = req->elem.in_sg[0].iov_len;
+ }
+ }
+
+ out_size = qemu_sgl_concat(req, req->elem.out_sg,
+ &req->elem.out_addr[0], req->elem.out_num,
+ req_size);
+ in_size = qemu_sgl_concat(req, req->elem.in_sg,
+ &req->elem.in_addr[0], req->elem.in_num,
+ resp_size);
+
+ if (out_size && in_size) {
+ return -ENOTSUP;
+ }
+
+ if (out_size) {
+ req->mode = SCSI_XFER_TO_DEV;
+ } else if (in_size) {
+ req->mode = SCSI_XFER_FROM_DEV;
+ }
+
+ return 0;
+}
+
+static VirtIOSCSIReq *virtio_scsi_pop_req(VirtIOSCSI *s, VirtQueue *vq)
+{
+ VirtIOSCSICommon *vs = (VirtIOSCSICommon *)s;
+ VirtIOSCSIReq *req;
+
+ req = virtqueue_pop(vq, sizeof(VirtIOSCSIReq) + vs->cdb_size);
+ if (!req) {
+ return NULL;
+ }
+ virtio_scsi_init_req(s, vq, req);
+ return req;
+}
+
+static void virtio_scsi_save_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ VirtIOSCSIReq *req = sreq->hba_private;
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(req->dev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
+ uint32_t n = virtio_get_queue_index(req->vq) - VIRTIO_SCSI_VQ_NUM_FIXED;
+
+ assert(n < vs->conf.num_queues);
+ qemu_put_be32s(f, &n);
+ qemu_put_virtqueue_element(vdev, f, &req->elem);
+}
+
+static void *virtio_scsi_load_request(QEMUFile *f, SCSIRequest *sreq)
+{
+ SCSIBus *bus = sreq->bus;
+ VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+ VirtIOSCSIReq *req;
+ uint32_t n;
+
+ qemu_get_be32s(f, &n);
+ assert(n < vs->conf.num_queues);
+ req = qemu_get_virtqueue_element(vdev, f,
+ sizeof(VirtIOSCSIReq) + vs->cdb_size);
+ virtio_scsi_init_req(s, vs->cmd_vqs[n], req);
+
+ if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + vs->cdb_size,
+ sizeof(VirtIOSCSICmdResp) + vs->sense_size) < 0) {
+ error_report("invalid SCSI request migration data");
+ exit(1);
+ }
+
+ scsi_req_ref(sreq);
+ req->sreq = sreq;
+ if (req->sreq->cmd.mode != SCSI_XFER_NONE) {
+ assert(req->sreq->cmd.mode == req->mode);
+ }
+ return req;
+}
+
+typedef struct {
+ Notifier notifier;
+ VirtIOSCSIReq *tmf_req;
+} VirtIOSCSICancelNotifier;
+
+static void virtio_scsi_cancel_notify(Notifier *notifier, void *data)
+{
+ VirtIOSCSICancelNotifier *n = container_of(notifier,
+ VirtIOSCSICancelNotifier,
+ notifier);
+
+ if (--n->tmf_req->remaining == 0) {
+ VirtIOSCSIReq *req = n->tmf_req;
+
+ trace_virtio_scsi_tmf_resp(virtio_scsi_get_lun(req->req.tmf.lun),
+ req->req.tmf.tag, req->resp.tmf.response);
+ virtio_scsi_complete_req(req);
+ }
+ g_free(n);
+}
+
+static inline void virtio_scsi_ctx_check(VirtIOSCSI *s, SCSIDevice *d)
+{
+ if (s->dataplane_started && d && blk_is_available(d->conf.blk)) {
+ assert(blk_get_aio_context(d->conf.blk) == s->ctx);
+ }
+}
+
+/* Return 0 if the request is ready to be completed and return to guest;
+ * -EINPROGRESS if the request is submitted and will be completed later, in the
+ * case of async cancellation. */
+static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ SCSIDevice *d = virtio_scsi_device_get(s, req->req.tmf.lun);
+ SCSIRequest *r, *next;
+ BusChild *kid;
+ int target;
+ int ret = 0;
+
+ virtio_scsi_ctx_check(s, d);
+ /* Here VIRTIO_SCSI_S_OK means "FUNCTION COMPLETE". */
+ req->resp.tmf.response = VIRTIO_SCSI_S_OK;
+
+ /*
+ * req->req.tmf has the QEMU_PACKED attribute. Don't use virtio_tswap32s()
+ * to avoid compiler errors.
+ */
+ req->req.tmf.subtype =
+ virtio_tswap32(VIRTIO_DEVICE(s), req->req.tmf.subtype);
+
+ trace_virtio_scsi_tmf_req(virtio_scsi_get_lun(req->req.tmf.lun),
+ req->req.tmf.tag, req->req.tmf.subtype);
+
+ switch (req->req.tmf.subtype) {
+ case VIRTIO_SCSI_T_TMF_ABORT_TASK:
+ case VIRTIO_SCSI_T_TMF_QUERY_TASK:
+ if (!d) {
+ goto fail;
+ }
+ if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
+ goto incorrect_lun;
+ }
+ QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
+ VirtIOSCSIReq *cmd_req = r->hba_private;
+ if (cmd_req && cmd_req->req.cmd.tag == req->req.tmf.tag) {
+ break;
+ }
+ }
+ if (r) {
+ /*
+ * Assert that the request has not been completed yet, we
+ * check for it in the loop above.
+ */
+ assert(r->hba_private);
+ if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK) {
+ /* "If the specified command is present in the task set, then
+ * return a service response set to FUNCTION SUCCEEDED".
+ */
+ req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
+ } else {
+ VirtIOSCSICancelNotifier *notifier;
+
+ req->remaining = 1;
+ notifier = g_new(VirtIOSCSICancelNotifier, 1);
+ notifier->tmf_req = req;
+ notifier->notifier.notify = virtio_scsi_cancel_notify;
+ scsi_req_cancel_async(r, &notifier->notifier);
+ ret = -EINPROGRESS;
+ }
+ }
+ break;
+
+ case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET:
+ if (!d) {
+ goto fail;
+ }
+ if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
+ goto incorrect_lun;
+ }
+ s->resetting++;
+ device_cold_reset(&d->qdev);
+ s->resetting--;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET:
+ case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET:
+ case VIRTIO_SCSI_T_TMF_QUERY_TASK_SET:
+ if (!d) {
+ goto fail;
+ }
+ if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
+ goto incorrect_lun;
+ }
+
+ /* Add 1 to "remaining" until virtio_scsi_do_tmf returns.
+ * This way, if the bus starts calling back to the notifiers
+ * even before we finish the loop, virtio_scsi_cancel_notify
+ * will not complete the TMF too early.
+ */
+ req->remaining = 1;
+ QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
+ if (r->hba_private) {
+ if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK_SET) {
+ /* "If there is any command present in the task set, then
+ * return a service response set to FUNCTION SUCCEEDED".
+ */
+ req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
+ break;
+ } else {
+ VirtIOSCSICancelNotifier *notifier;
+
+ req->remaining++;
+ notifier = g_new(VirtIOSCSICancelNotifier, 1);
+ notifier->notifier.notify = virtio_scsi_cancel_notify;
+ notifier->tmf_req = req;
+ scsi_req_cancel_async(r, &notifier->notifier);
+ }
+ }
+ }
+ if (--req->remaining > 0) {
+ ret = -EINPROGRESS;
+ }
+ break;
+
+ case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET:
+ target = req->req.tmf.lun[1];
+ s->resetting++;
+
+ rcu_read_lock();
+ QTAILQ_FOREACH_RCU(kid, &s->bus.qbus.children, sibling) {
+ SCSIDevice *d1 = SCSI_DEVICE(kid->child);
+ if (d1->channel == 0 && d1->id == target) {
+ device_cold_reset(&d1->qdev);
+ }
+ }
+ rcu_read_unlock();
+
+ s->resetting--;
+ break;
+
+ case VIRTIO_SCSI_T_TMF_CLEAR_ACA:
+ default:
+ req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_REJECTED;
+ break;
+ }
+
+ object_unref(OBJECT(d));
+ return ret;
+
+incorrect_lun:
+ req->resp.tmf.response = VIRTIO_SCSI_S_INCORRECT_LUN;
+ object_unref(OBJECT(d));
+ return ret;
+
+fail:
+ req->resp.tmf.response = VIRTIO_SCSI_S_BAD_TARGET;
+ object_unref(OBJECT(d));
+ return ret;
+}
+
+static void virtio_scsi_handle_ctrl_req(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ VirtIODevice *vdev = (VirtIODevice *)s;
+ uint32_t type;
+ int r = 0;
+
+ if (iov_to_buf(req->elem.out_sg, req->elem.out_num, 0,
+ &type, sizeof(type)) < sizeof(type)) {
+ virtio_scsi_bad_req(req);
+ return;
+ }
+
+ virtio_tswap32s(vdev, &type);
+ if (type == VIRTIO_SCSI_T_TMF) {
+ if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICtrlTMFReq),
+ sizeof(VirtIOSCSICtrlTMFResp)) < 0) {
+ virtio_scsi_bad_req(req);
+ return;
+ } else {
+ r = virtio_scsi_do_tmf(s, req);
+ }
+
+ } else if (type == VIRTIO_SCSI_T_AN_QUERY ||
+ type == VIRTIO_SCSI_T_AN_SUBSCRIBE) {
+ if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICtrlANReq),
+ sizeof(VirtIOSCSICtrlANResp)) < 0) {
+ virtio_scsi_bad_req(req);
+ return;
+ } else {
+ req->req.an.event_requested =
+ virtio_tswap32(VIRTIO_DEVICE(s), req->req.an.event_requested);
+ trace_virtio_scsi_an_req(virtio_scsi_get_lun(req->req.an.lun),
+ req->req.an.event_requested);
+ req->resp.an.event_actual = 0;
+ req->resp.an.response = VIRTIO_SCSI_S_OK;
+ }
+ }
+ if (r == 0) {
+ if (type == VIRTIO_SCSI_T_TMF)
+ trace_virtio_scsi_tmf_resp(virtio_scsi_get_lun(req->req.tmf.lun),
+ req->req.tmf.tag,
+ req->resp.tmf.response);
+ else if (type == VIRTIO_SCSI_T_AN_QUERY ||
+ type == VIRTIO_SCSI_T_AN_SUBSCRIBE)
+ trace_virtio_scsi_an_resp(virtio_scsi_get_lun(req->req.an.lun),
+ req->resp.an.response);
+ virtio_scsi_complete_req(req);
+ } else {
+ assert(r == -EINPROGRESS);
+ }
+}
+
+static void virtio_scsi_handle_ctrl_vq(VirtIOSCSI *s, VirtQueue *vq)
+{
+ VirtIOSCSIReq *req;
+
+ while ((req = virtio_scsi_pop_req(s, vq))) {
+ virtio_scsi_handle_ctrl_req(s, req);
+ }
+}
+
+/*
+ * If dataplane is configured but not yet started, do so now and return true on
+ * success.
+ *
+ * Dataplane is started by the core virtio code but virtqueue handler functions
+ * can also be invoked when a guest kicks before DRIVER_OK, so this helper
+ * function helps us deal with manually starting ioeventfd in that case.
+ */
+static bool virtio_scsi_defer_to_dataplane(VirtIOSCSI *s)
+{
+ if (!s->ctx || s->dataplane_started) {
+ return false;
+ }
+
+ virtio_device_start_ioeventfd(&s->parent_obj.parent_obj);
+ return !s->dataplane_fenced;
+}
+
+static void virtio_scsi_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSCSI *s = (VirtIOSCSI *)vdev;
+
+ if (virtio_scsi_defer_to_dataplane(s)) {
+ return;
+ }
+
+ virtio_scsi_acquire(s);
+ virtio_scsi_handle_ctrl_vq(s, vq);
+ virtio_scsi_release(s);
+}
+
+static void virtio_scsi_complete_cmd_req(VirtIOSCSIReq *req)
+{
+ trace_virtio_scsi_cmd_resp(virtio_scsi_get_lun(req->req.cmd.lun),
+ req->req.cmd.tag,
+ req->resp.cmd.response,
+ req->resp.cmd.status);
+ /* Sense data is not in req->resp and is copied separately
+ * in virtio_scsi_command_complete.
+ */
+ req->resp_size = sizeof(VirtIOSCSICmdResp);
+ virtio_scsi_complete_req(req);
+}
+
+static void virtio_scsi_command_failed(SCSIRequest *r)
+{
+ VirtIOSCSIReq *req = r->hba_private;
+
+ if (r->io_canceled) {
+ return;
+ }
+
+ req->resp.cmd.status = GOOD;
+ switch (r->host_status) {
+ case SCSI_HOST_NO_LUN:
+ req->resp.cmd.response = VIRTIO_SCSI_S_INCORRECT_LUN;
+ break;
+ case SCSI_HOST_BUSY:
+ req->resp.cmd.response = VIRTIO_SCSI_S_BUSY;
+ break;
+ case SCSI_HOST_TIME_OUT:
+ case SCSI_HOST_ABORTED:
+ req->resp.cmd.response = VIRTIO_SCSI_S_ABORTED;
+ break;
+ case SCSI_HOST_BAD_RESPONSE:
+ req->resp.cmd.response = VIRTIO_SCSI_S_BAD_TARGET;
+ break;
+ case SCSI_HOST_RESET:
+ req->resp.cmd.response = VIRTIO_SCSI_S_RESET;
+ break;
+ case SCSI_HOST_TRANSPORT_DISRUPTED:
+ req->resp.cmd.response = VIRTIO_SCSI_S_TRANSPORT_FAILURE;
+ break;
+ case SCSI_HOST_TARGET_FAILURE:
+ req->resp.cmd.response = VIRTIO_SCSI_S_TARGET_FAILURE;
+ break;
+ case SCSI_HOST_RESERVATION_ERROR:
+ req->resp.cmd.response = VIRTIO_SCSI_S_NEXUS_FAILURE;
+ break;
+ case SCSI_HOST_ALLOCATION_FAILURE:
+ case SCSI_HOST_MEDIUM_ERROR:
+ case SCSI_HOST_ERROR:
+ default:
+ req->resp.cmd.response = VIRTIO_SCSI_S_FAILURE;
+ break;
+ }
+ virtio_scsi_complete_cmd_req(req);
+}
+
+static void virtio_scsi_command_complete(SCSIRequest *r, size_t resid)
+{
+ VirtIOSCSIReq *req = r->hba_private;
+ uint8_t sense[SCSI_SENSE_BUF_SIZE];
+ uint32_t sense_len;
+ VirtIODevice *vdev = VIRTIO_DEVICE(req->dev);
+
+ if (r->io_canceled) {
+ return;
+ }
+
+ req->resp.cmd.response = VIRTIO_SCSI_S_OK;
+ req->resp.cmd.status = r->status;
+ if (req->resp.cmd.status == GOOD) {
+ req->resp.cmd.resid = virtio_tswap32(vdev, resid);
+ } else {
+ req->resp.cmd.resid = 0;
+ sense_len = scsi_req_get_sense(r, sense, sizeof(sense));
+ sense_len = MIN(sense_len, req->resp_iov.size - sizeof(req->resp.cmd));
+ qemu_iovec_from_buf(&req->resp_iov, sizeof(req->resp.cmd),
+ sense, sense_len);
+ req->resp.cmd.sense_len = virtio_tswap32(vdev, sense_len);
+ }
+ virtio_scsi_complete_cmd_req(req);
+}
+
+static int virtio_scsi_parse_cdb(SCSIDevice *dev, SCSICommand *cmd,
+ uint8_t *buf, size_t buf_len,
+ void *hba_private)
+{
+ VirtIOSCSIReq *req = hba_private;
+
+ if (cmd->len == 0) {
+ cmd->len = MIN(VIRTIO_SCSI_CDB_DEFAULT_SIZE, SCSI_CMD_BUF_SIZE);
+ memcpy(cmd->buf, buf, cmd->len);
+ }
+
+ /* Extract the direction and mode directly from the request, for
+ * host device passthrough.
+ */
+ cmd->xfer = req->qsgl.size;
+ cmd->mode = req->mode;
+ return 0;
+}
+
+static QEMUSGList *virtio_scsi_get_sg_list(SCSIRequest *r)
+{
+ VirtIOSCSIReq *req = r->hba_private;
+
+ return &req->qsgl;
+}
+
+static void virtio_scsi_request_cancelled(SCSIRequest *r)
+{
+ VirtIOSCSIReq *req = r->hba_private;
+
+ if (!req) {
+ return;
+ }
+ if (req->dev->resetting) {
+ req->resp.cmd.response = VIRTIO_SCSI_S_RESET;
+ } else {
+ req->resp.cmd.response = VIRTIO_SCSI_S_ABORTED;
+ }
+ virtio_scsi_complete_cmd_req(req);
+}
+
+static void virtio_scsi_fail_cmd_req(VirtIOSCSIReq *req)
+{
+ req->resp.cmd.response = VIRTIO_SCSI_S_FAILURE;
+ virtio_scsi_complete_cmd_req(req);
+}
+
+static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ VirtIOSCSICommon *vs = &s->parent_obj;
+ SCSIDevice *d;
+ int rc;
+
+ rc = virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + vs->cdb_size,
+ sizeof(VirtIOSCSICmdResp) + vs->sense_size);
+ if (rc < 0) {
+ if (rc == -ENOTSUP) {
+ virtio_scsi_fail_cmd_req(req);
+ return -ENOTSUP;
+ } else {
+ virtio_scsi_bad_req(req);
+ return -EINVAL;
+ }
+ }
+ trace_virtio_scsi_cmd_req(virtio_scsi_get_lun(req->req.cmd.lun),
+ req->req.cmd.tag, req->req.cmd.cdb[0]);
+
+ d = virtio_scsi_device_get(s, req->req.cmd.lun);
+ if (!d) {
+ req->resp.cmd.response = VIRTIO_SCSI_S_BAD_TARGET;
+ virtio_scsi_complete_cmd_req(req);
+ return -ENOENT;
+ }
+ virtio_scsi_ctx_check(s, d);
+ req->sreq = scsi_req_new(d, req->req.cmd.tag,
+ virtio_scsi_get_lun(req->req.cmd.lun),
+ req->req.cmd.cdb, vs->cdb_size, req);
+
+ if (req->sreq->cmd.mode != SCSI_XFER_NONE
+ && (req->sreq->cmd.mode != req->mode ||
+ req->sreq->cmd.xfer > req->qsgl.size)) {
+ req->resp.cmd.response = VIRTIO_SCSI_S_OVERRUN;
+ virtio_scsi_complete_cmd_req(req);
+ object_unref(OBJECT(d));
+ return -ENOBUFS;
+ }
+ scsi_req_ref(req->sreq);
+ blk_io_plug(d->conf.blk);
+ object_unref(OBJECT(d));
+ return 0;
+}
+
+static void virtio_scsi_handle_cmd_req_submit(VirtIOSCSI *s, VirtIOSCSIReq *req)
+{
+ SCSIRequest *sreq = req->sreq;
+ if (scsi_req_enqueue(sreq)) {
+ scsi_req_continue(sreq);
+ }
+ blk_io_unplug(sreq->dev->conf.blk);
+ scsi_req_unref(sreq);
+}
+
+static void virtio_scsi_handle_cmd_vq(VirtIOSCSI *s, VirtQueue *vq)
+{
+ VirtIOSCSIReq *req, *next;
+ int ret = 0;
+ bool suppress_notifications = virtio_queue_get_notification(vq);
+
+ QTAILQ_HEAD(, VirtIOSCSIReq) reqs = QTAILQ_HEAD_INITIALIZER(reqs);
+
+ do {
+ if (suppress_notifications) {
+ virtio_queue_set_notification(vq, 0);
+ }
+
+ while ((req = virtio_scsi_pop_req(s, vq))) {
+ ret = virtio_scsi_handle_cmd_req_prepare(s, req);
+ if (!ret) {
+ QTAILQ_INSERT_TAIL(&reqs, req, next);
+ } else if (ret == -EINVAL) {
+ /* The device is broken and shouldn't process any request */
+ while (!QTAILQ_EMPTY(&reqs)) {
+ req = QTAILQ_FIRST(&reqs);
+ QTAILQ_REMOVE(&reqs, req, next);
+ blk_io_unplug(req->sreq->dev->conf.blk);
+ scsi_req_unref(req->sreq);
+ virtqueue_detach_element(req->vq, &req->elem, 0);
+ virtio_scsi_free_req(req);
+ }
+ }
+ }
+
+ if (suppress_notifications) {
+ virtio_queue_set_notification(vq, 1);
+ }
+ } while (ret != -EINVAL && !virtio_queue_empty(vq));
+
+ QTAILQ_FOREACH_SAFE(req, &reqs, next, next) {
+ virtio_scsi_handle_cmd_req_submit(s, req);
+ }
+}
+
+static void virtio_scsi_handle_cmd(VirtIODevice *vdev, VirtQueue *vq)
+{
+ /* use non-QOM casts in the data path */
+ VirtIOSCSI *s = (VirtIOSCSI *)vdev;
+
+ if (virtio_scsi_defer_to_dataplane(s)) {
+ return;
+ }
+
+ virtio_scsi_acquire(s);
+ virtio_scsi_handle_cmd_vq(s, vq);
+ virtio_scsi_release(s);
+}
+
+static void virtio_scsi_get_config(VirtIODevice *vdev,
+ uint8_t *config)
+{
+ VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config;
+ VirtIOSCSICommon *s = VIRTIO_SCSI_COMMON(vdev);
+
+ virtio_stl_p(vdev, &scsiconf->num_queues, s->conf.num_queues);
+ virtio_stl_p(vdev, &scsiconf->seg_max,
+ s->conf.seg_max_adjust ? s->conf.virtqueue_size - 2 : 128 - 2);
+ virtio_stl_p(vdev, &scsiconf->max_sectors, s->conf.max_sectors);
+ virtio_stl_p(vdev, &scsiconf->cmd_per_lun, s->conf.cmd_per_lun);
+ virtio_stl_p(vdev, &scsiconf->event_info_size, sizeof(VirtIOSCSIEvent));
+ virtio_stl_p(vdev, &scsiconf->sense_size, s->sense_size);
+ virtio_stl_p(vdev, &scsiconf->cdb_size, s->cdb_size);
+ virtio_stw_p(vdev, &scsiconf->max_channel, VIRTIO_SCSI_MAX_CHANNEL);
+ virtio_stw_p(vdev, &scsiconf->max_target, VIRTIO_SCSI_MAX_TARGET);
+ virtio_stl_p(vdev, &scsiconf->max_lun, VIRTIO_SCSI_MAX_LUN);
+}
+
+static void virtio_scsi_set_config(VirtIODevice *vdev,
+ const uint8_t *config)
+{
+ VirtIOSCSIConfig *scsiconf = (VirtIOSCSIConfig *)config;
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+
+ if ((uint32_t) virtio_ldl_p(vdev, &scsiconf->sense_size) >= 65536 ||
+ (uint32_t) virtio_ldl_p(vdev, &scsiconf->cdb_size) >= 256) {
+ virtio_error(vdev,
+ "bad data written to virtio-scsi configuration space");
+ return;
+ }
+
+ vs->sense_size = virtio_ldl_p(vdev, &scsiconf->sense_size);
+ vs->cdb_size = virtio_ldl_p(vdev, &scsiconf->cdb_size);
+}
+
+static uint64_t virtio_scsi_get_features(VirtIODevice *vdev,
+ uint64_t requested_features,
+ Error **errp)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+
+ /* Firstly sync all virtio-scsi possible supported features */
+ requested_features |= s->host_features;
+ return requested_features;
+}
+
+static void virtio_scsi_reset(VirtIODevice *vdev)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev);
+
+ assert(!s->dataplane_started);
+ s->resetting++;
+ bus_cold_reset(BUS(&s->bus));
+ s->resetting--;
+
+ vs->sense_size = VIRTIO_SCSI_SENSE_DEFAULT_SIZE;
+ vs->cdb_size = VIRTIO_SCSI_CDB_DEFAULT_SIZE;
+ s->events_dropped = false;
+}
+
+static void virtio_scsi_push_event(VirtIOSCSI *s, SCSIDevice *dev,
+ uint32_t event, uint32_t reason)
+{
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
+ VirtIOSCSIReq *req;
+ VirtIOSCSIEvent *evt;
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ return;
+ }
+
+ req = virtio_scsi_pop_req(s, vs->event_vq);
+ if (!req) {
+ s->events_dropped = true;
+ return;
+ }
+
+ if (s->events_dropped) {
+ event |= VIRTIO_SCSI_T_EVENTS_MISSED;
+ s->events_dropped = false;
+ }
+
+ if (virtio_scsi_parse_req(req, 0, sizeof(VirtIOSCSIEvent))) {
+ virtio_scsi_bad_req(req);
+ return;
+ }
+
+ evt = &req->resp.event;
+ memset(evt, 0, sizeof(VirtIOSCSIEvent));
+ evt->event = virtio_tswap32(vdev, event);
+ evt->reason = virtio_tswap32(vdev, reason);
+ if (!dev) {
+ assert(event == VIRTIO_SCSI_T_EVENTS_MISSED);
+ } else {
+ evt->lun[0] = 1;
+ evt->lun[1] = dev->id;
+
+ /* Linux wants us to keep the same encoding we use for REPORT LUNS. */
+ if (dev->lun >= 256) {
+ evt->lun[2] = (dev->lun >> 8) | 0x40;
+ }
+ evt->lun[3] = dev->lun & 0xFF;
+ }
+ trace_virtio_scsi_event(virtio_scsi_get_lun(evt->lun), event, reason);
+
+ virtio_scsi_complete_req(req);
+}
+
+static void virtio_scsi_handle_event_vq(VirtIOSCSI *s, VirtQueue *vq)
+{
+ if (s->events_dropped) {
+ virtio_scsi_push_event(s, NULL, VIRTIO_SCSI_T_NO_EVENT, 0);
+ }
+}
+
+static void virtio_scsi_handle_event(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+
+ if (virtio_scsi_defer_to_dataplane(s)) {
+ return;
+ }
+
+ virtio_scsi_acquire(s);
+ virtio_scsi_handle_event_vq(s, vq);
+ virtio_scsi_release(s);
+}
+
+static void virtio_scsi_change(SCSIBus *bus, SCSIDevice *dev, SCSISense sense)
+{
+ VirtIOSCSI *s = container_of(bus, VirtIOSCSI, bus);
+ VirtIODevice *vdev = VIRTIO_DEVICE(s);
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_CHANGE) &&
+ dev->type != TYPE_ROM) {
+ virtio_scsi_acquire(s);
+ virtio_scsi_push_event(s, dev, VIRTIO_SCSI_T_PARAM_CHANGE,
+ sense.asc | (sense.ascq << 8));
+ virtio_scsi_release(s);
+ }
+}
+
+static void virtio_scsi_pre_hotplug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ SCSIDevice *sd = SCSI_DEVICE(dev);
+ sd->hba_supports_iothread = true;
+}
+
+static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev);
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+ SCSIDevice *sd = SCSI_DEVICE(dev);
+ AioContext *old_context;
+ int ret;
+
+ if (s->ctx && !s->dataplane_fenced) {
+ if (blk_op_is_blocked(sd->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) {
+ return;
+ }
+ old_context = blk_get_aio_context(sd->conf.blk);
+ aio_context_acquire(old_context);
+ ret = blk_set_aio_context(sd->conf.blk, s->ctx, errp);
+ aio_context_release(old_context);
+ if (ret < 0) {
+ return;
+ }
+ }
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_HOTPLUG)) {
+ virtio_scsi_acquire(s);
+ virtio_scsi_push_event(s, sd,
+ VIRTIO_SCSI_T_TRANSPORT_RESET,
+ VIRTIO_SCSI_EVT_RESET_RESCAN);
+ scsi_bus_set_ua(&s->bus, SENSE_CODE(REPORTED_LUNS_CHANGED));
+ virtio_scsi_release(s);
+ }
+}
+
+static void virtio_scsi_hotunplug(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev);
+ VirtIOSCSI *s = VIRTIO_SCSI(vdev);
+ SCSIDevice *sd = SCSI_DEVICE(dev);
+ AioContext *ctx = s->ctx ?: qemu_get_aio_context();
+
+ if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_HOTPLUG)) {
+ virtio_scsi_acquire(s);
+ virtio_scsi_push_event(s, sd,
+ VIRTIO_SCSI_T_TRANSPORT_RESET,
+ VIRTIO_SCSI_EVT_RESET_REMOVED);
+ scsi_bus_set_ua(&s->bus, SENSE_CODE(REPORTED_LUNS_CHANGED));
+ virtio_scsi_release(s);
+ }
+
+ aio_disable_external(ctx);
+ qdev_simple_device_unplug_cb(hotplug_dev, dev, errp);
+ aio_enable_external(ctx);
+
+ if (s->ctx) {
+ virtio_scsi_acquire(s);
+ /* If other users keep the BlockBackend in the iothread, that's ok */
+ blk_set_aio_context(sd->conf.blk, qemu_get_aio_context(), NULL);
+ virtio_scsi_release(s);
+ }
+}
+
+static struct SCSIBusInfo virtio_scsi_scsi_info = {
+ .tcq = true,
+ .max_channel = VIRTIO_SCSI_MAX_CHANNEL,
+ .max_target = VIRTIO_SCSI_MAX_TARGET,
+ .max_lun = VIRTIO_SCSI_MAX_LUN,
+
+ .complete = virtio_scsi_command_complete,
+ .fail = virtio_scsi_command_failed,
+ .cancel = virtio_scsi_request_cancelled,
+ .change = virtio_scsi_change,
+ .parse_cdb = virtio_scsi_parse_cdb,
+ .get_sg_list = virtio_scsi_get_sg_list,
+ .save_request = virtio_scsi_save_request,
+ .load_request = virtio_scsi_load_request,
+};
+
+void virtio_scsi_common_realize(DeviceState *dev,
+ VirtIOHandleOutput ctrl,
+ VirtIOHandleOutput evt,
+ VirtIOHandleOutput cmd,
+ Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSCSICommon *s = VIRTIO_SCSI_COMMON(dev);
+ int i;
+
+ virtio_init(vdev, VIRTIO_ID_SCSI, sizeof(VirtIOSCSIConfig));
+
+ if (s->conf.num_queues == VIRTIO_SCSI_AUTO_NUM_QUEUES) {
+ s->conf.num_queues = 1;
+ }
+ if (s->conf.num_queues == 0 ||
+ s->conf.num_queues > VIRTIO_QUEUE_MAX - VIRTIO_SCSI_VQ_NUM_FIXED) {
+ error_setg(errp, "Invalid number of queues (= %" PRIu32 "), "
+ "must be a positive integer less than %d.",
+ s->conf.num_queues,
+ VIRTIO_QUEUE_MAX - VIRTIO_SCSI_VQ_NUM_FIXED);
+ virtio_cleanup(vdev);
+ return;
+ }
+ if (s->conf.virtqueue_size <= 2) {
+ error_setg(errp, "invalid virtqueue_size property (= %" PRIu32 "), "
+ "must be > 2", s->conf.virtqueue_size);
+ return;
+ }
+ s->cmd_vqs = g_new0(VirtQueue *, s->conf.num_queues);
+ s->sense_size = VIRTIO_SCSI_SENSE_DEFAULT_SIZE;
+ s->cdb_size = VIRTIO_SCSI_CDB_DEFAULT_SIZE;
+
+ s->ctrl_vq = virtio_add_queue(vdev, s->conf.virtqueue_size, ctrl);
+ s->event_vq = virtio_add_queue(vdev, s->conf.virtqueue_size, evt);
+ for (i = 0; i < s->conf.num_queues; i++) {
+ s->cmd_vqs[i] = virtio_add_queue(vdev, s->conf.virtqueue_size, cmd);
+ }
+}
+
+static void virtio_scsi_device_realize(DeviceState *dev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSCSI *s = VIRTIO_SCSI(dev);
+ Error *err = NULL;
+
+ virtio_scsi_common_realize(dev,
+ virtio_scsi_handle_ctrl,
+ virtio_scsi_handle_event,
+ virtio_scsi_handle_cmd,
+ &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ scsi_bus_init_named(&s->bus, sizeof(s->bus), dev,
+ &virtio_scsi_scsi_info, vdev->bus_name);
+ /* override default SCSI bus hotplug-handler, with virtio-scsi's one */
+ qbus_set_hotplug_handler(BUS(&s->bus), OBJECT(dev));
+
+ virtio_scsi_dataplane_setup(s, errp);
+}
+
+void virtio_scsi_common_unrealize(DeviceState *dev)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+ VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(dev);
+ int i;
+
+ virtio_delete_queue(vs->ctrl_vq);
+ virtio_delete_queue(vs->event_vq);
+ for (i = 0; i < vs->conf.num_queues; i++) {
+ virtio_delete_queue(vs->cmd_vqs[i]);
+ }
+ g_free(vs->cmd_vqs);
+ virtio_cleanup(vdev);
+}
+
+static void virtio_scsi_device_unrealize(DeviceState *dev)
+{
+ VirtIOSCSI *s = VIRTIO_SCSI(dev);
+
+ qbus_set_hotplug_handler(BUS(&s->bus), NULL);
+ virtio_scsi_common_unrealize(dev);
+}
+
+static Property virtio_scsi_properties[] = {
+ DEFINE_PROP_UINT32("num_queues", VirtIOSCSI, parent_obj.conf.num_queues,
+ VIRTIO_SCSI_AUTO_NUM_QUEUES),
+ DEFINE_PROP_UINT32("virtqueue_size", VirtIOSCSI,
+ parent_obj.conf.virtqueue_size, 256),
+ DEFINE_PROP_BOOL("seg_max_adjust", VirtIOSCSI,
+ parent_obj.conf.seg_max_adjust, true),
+ DEFINE_PROP_UINT32("max_sectors", VirtIOSCSI, parent_obj.conf.max_sectors,
+ 0xFFFF),
+ DEFINE_PROP_UINT32("cmd_per_lun", VirtIOSCSI, parent_obj.conf.cmd_per_lun,
+ 128),
+ DEFINE_PROP_BIT("hotplug", VirtIOSCSI, host_features,
+ VIRTIO_SCSI_F_HOTPLUG, true),
+ DEFINE_PROP_BIT("param_change", VirtIOSCSI, host_features,
+ VIRTIO_SCSI_F_CHANGE, true),
+ DEFINE_PROP_LINK("iothread", VirtIOSCSI, parent_obj.conf.iothread,
+ TYPE_IOTHREAD, IOThread *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_virtio_scsi = {
+ .name = "virtio-scsi",
+ .minimum_version_id = 1,
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_VIRTIO_DEVICE,
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void virtio_scsi_common_class_init(ObjectClass *klass, void *data)
+{
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ vdc->get_config = virtio_scsi_get_config;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+}
+
+static void virtio_scsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ device_class_set_props(dc, virtio_scsi_properties);
+ dc->vmsd = &vmstate_virtio_scsi;
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ vdc->realize = virtio_scsi_device_realize;
+ vdc->unrealize = virtio_scsi_device_unrealize;
+ vdc->set_config = virtio_scsi_set_config;
+ vdc->get_features = virtio_scsi_get_features;
+ vdc->reset = virtio_scsi_reset;
+ vdc->start_ioeventfd = virtio_scsi_dataplane_start;
+ vdc->stop_ioeventfd = virtio_scsi_dataplane_stop;
+ hc->pre_plug = virtio_scsi_pre_hotplug;
+ hc->plug = virtio_scsi_hotplug;
+ hc->unplug = virtio_scsi_hotunplug;
+}
+
+static const TypeInfo virtio_scsi_common_info = {
+ .name = TYPE_VIRTIO_SCSI_COMMON,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOSCSICommon),
+ .abstract = true,
+ .class_init = virtio_scsi_common_class_init,
+};
+
+static const TypeInfo virtio_scsi_info = {
+ .name = TYPE_VIRTIO_SCSI,
+ .parent = TYPE_VIRTIO_SCSI_COMMON,
+ .instance_size = sizeof(VirtIOSCSI),
+ .class_init = virtio_scsi_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
+};
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_scsi_common_info);
+ type_register_static(&virtio_scsi_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/scsi/vmw_pvscsi.c b/hw/scsi/vmw_pvscsi.c
new file mode 100644
index 00000000..fa766968
--- /dev/null
+++ b/hw/scsi/vmw_pvscsi.c
@@ -0,0 +1,1363 @@
+/*
+ * QEMU VMWARE PVSCSI paravirtual SCSI bus
+ *
+ * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com)
+ *
+ * Developed by Daynix Computing LTD (http://www.daynix.com)
+ *
+ * Based on implementation by Paolo Bonzini
+ * http://lists.gnu.org/archive/html/qemu-devel/2011-08/msg00729.html
+ *
+ * Authors:
+ * Paolo Bonzini <pbonzini@redhat.com>
+ * Dmitry Fleytman <dmitry@daynix.com>
+ * Yan Vugenfirer <yan@daynix.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ * See the COPYING file in the top-level directory.
+ *
+ * NOTE about MSI-X:
+ * MSI-X support has been removed for the moment because it leads Windows OS
+ * to crash on startup. The crash happens because Windows driver requires
+ * MSI-X shared memory to be part of the same BAR used for rings state
+ * registers, etc. This is not supported by QEMU infrastructure so separate
+ * BAR created from MSI-X purposes. Windows driver fails to deal with 2 BARs.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "hw/scsi/scsi.h"
+#include "migration/vmstate.h"
+#include "scsi/constants.h"
+#include "hw/pci/msi.h"
+#include "hw/qdev-properties.h"
+#include "vmw_pvscsi.h"
+#include "trace.h"
+#include "qom/object.h"
+
+
+#define PVSCSI_USE_64BIT (true)
+#define PVSCSI_PER_VECTOR_MASK (false)
+
+#define PVSCSI_MAX_DEVS (64)
+#define PVSCSI_MSIX_NUM_VECTORS (1)
+
+#define PVSCSI_MAX_SG_ELEM 2048
+
+#define PVSCSI_MAX_CMD_DATA_WORDS \
+ (sizeof(PVSCSICmdDescSetupRings)/sizeof(uint32_t))
+
+#define RS_GET_FIELD(pval, m, field) \
+ ldl_le_pci_dma(&container_of(m, PVSCSIState, rings)->parent_obj, \
+ (m)->rs_pa + offsetof(struct PVSCSIRingsState, field), \
+ pval, MEMTXATTRS_UNSPECIFIED)
+#define RS_SET_FIELD(m, field, val) \
+ (stl_le_pci_dma(&container_of(m, PVSCSIState, rings)->parent_obj, \
+ (m)->rs_pa + offsetof(struct PVSCSIRingsState, field), val, \
+ MEMTXATTRS_UNSPECIFIED))
+
+struct PVSCSIClass {
+ PCIDeviceClass parent_class;
+ DeviceRealize parent_dc_realize;
+};
+
+#define TYPE_PVSCSI "pvscsi"
+OBJECT_DECLARE_TYPE(PVSCSIState, PVSCSIClass, PVSCSI)
+
+
+/* Compatibility flags for migration */
+#define PVSCSI_COMPAT_OLD_PCI_CONFIGURATION_BIT 0
+#define PVSCSI_COMPAT_OLD_PCI_CONFIGURATION \
+ (1 << PVSCSI_COMPAT_OLD_PCI_CONFIGURATION_BIT)
+#define PVSCSI_COMPAT_DISABLE_PCIE_BIT 1
+#define PVSCSI_COMPAT_DISABLE_PCIE \
+ (1 << PVSCSI_COMPAT_DISABLE_PCIE_BIT)
+
+#define PVSCSI_USE_OLD_PCI_CONFIGURATION(s) \
+ ((s)->compat_flags & PVSCSI_COMPAT_OLD_PCI_CONFIGURATION)
+#define PVSCSI_MSI_OFFSET(s) \
+ (PVSCSI_USE_OLD_PCI_CONFIGURATION(s) ? 0x50 : 0x7c)
+#define PVSCSI_EXP_EP_OFFSET (0x40)
+
+typedef struct PVSCSIRingInfo {
+ uint64_t rs_pa;
+ uint32_t txr_len_mask;
+ uint32_t rxr_len_mask;
+ uint32_t msg_len_mask;
+ uint64_t req_ring_pages_pa[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ uint64_t cmp_ring_pages_pa[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ uint64_t msg_ring_pages_pa[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
+ uint64_t consumed_ptr;
+ uint64_t filled_cmp_ptr;
+ uint64_t filled_msg_ptr;
+} PVSCSIRingInfo;
+
+typedef struct PVSCSISGState {
+ hwaddr elemAddr;
+ hwaddr dataAddr;
+ uint32_t resid;
+} PVSCSISGState;
+
+typedef QTAILQ_HEAD(, PVSCSIRequest) PVSCSIRequestList;
+
+struct PVSCSIState {
+ PCIDevice parent_obj;
+ MemoryRegion io_space;
+ SCSIBus bus;
+ QEMUBH *completion_worker;
+ PVSCSIRequestList pending_queue;
+ PVSCSIRequestList completion_queue;
+
+ uint64_t reg_interrupt_status; /* Interrupt status register value */
+ uint64_t reg_interrupt_enabled; /* Interrupt mask register value */
+ uint64_t reg_command_status; /* Command status register value */
+
+ /* Command data adoption mechanism */
+ uint64_t curr_cmd; /* Last command arrived */
+ uint32_t curr_cmd_data_cntr; /* Amount of data for last command */
+
+ /* Collector for current command data */
+ uint32_t curr_cmd_data[PVSCSI_MAX_CMD_DATA_WORDS];
+
+ uint8_t rings_info_valid; /* Whether data rings initialized */
+ uint8_t msg_ring_info_valid; /* Whether message ring initialized */
+ uint8_t use_msg; /* Whether to use message ring */
+
+ uint8_t msi_used; /* For migration compatibility */
+ PVSCSIRingInfo rings; /* Data transfer rings manager */
+ uint32_t resetting; /* Reset in progress */
+
+ uint32_t compat_flags;
+};
+
+typedef struct PVSCSIRequest {
+ SCSIRequest *sreq;
+ PVSCSIState *dev;
+ uint8_t sense_key;
+ uint8_t completed;
+ int lun;
+ QEMUSGList sgl;
+ PVSCSISGState sg;
+ struct PVSCSIRingReqDesc req;
+ struct PVSCSIRingCmpDesc cmp;
+ QTAILQ_ENTRY(PVSCSIRequest) next;
+} PVSCSIRequest;
+
+/* Integer binary logarithm */
+static int
+pvscsi_log2(uint32_t input)
+{
+ int log = 0;
+ assert(input > 0);
+ while (input >> ++log) {
+ }
+ return log;
+}
+
+static void
+pvscsi_ring_init_data(PVSCSIRingInfo *m, PVSCSICmdDescSetupRings *ri)
+{
+ int i;
+ uint32_t txr_len_log2, rxr_len_log2;
+ uint32_t req_ring_size, cmp_ring_size;
+ m->rs_pa = ri->ringsStatePPN << VMW_PAGE_SHIFT;
+
+ req_ring_size = ri->reqRingNumPages * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ cmp_ring_size = ri->cmpRingNumPages * PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
+ txr_len_log2 = pvscsi_log2(req_ring_size - 1);
+ rxr_len_log2 = pvscsi_log2(cmp_ring_size - 1);
+
+ m->txr_len_mask = MASK(txr_len_log2);
+ m->rxr_len_mask = MASK(rxr_len_log2);
+
+ m->consumed_ptr = 0;
+ m->filled_cmp_ptr = 0;
+
+ for (i = 0; i < ri->reqRingNumPages; i++) {
+ m->req_ring_pages_pa[i] = ri->reqRingPPNs[i] << VMW_PAGE_SHIFT;
+ }
+
+ for (i = 0; i < ri->cmpRingNumPages; i++) {
+ m->cmp_ring_pages_pa[i] = ri->cmpRingPPNs[i] << VMW_PAGE_SHIFT;
+ }
+
+ RS_SET_FIELD(m, reqProdIdx, 0);
+ RS_SET_FIELD(m, reqConsIdx, 0);
+ RS_SET_FIELD(m, reqNumEntriesLog2, txr_len_log2);
+
+ RS_SET_FIELD(m, cmpProdIdx, 0);
+ RS_SET_FIELD(m, cmpConsIdx, 0);
+ RS_SET_FIELD(m, cmpNumEntriesLog2, rxr_len_log2);
+
+ trace_pvscsi_ring_init_data(txr_len_log2, rxr_len_log2);
+
+ /* Flush ring state page changes */
+ smp_wmb();
+}
+
+static int
+pvscsi_ring_init_msg(PVSCSIRingInfo *m, PVSCSICmdDescSetupMsgRing *ri)
+{
+ int i;
+ uint32_t len_log2;
+ uint32_t ring_size;
+
+ if (!ri->numPages || ri->numPages > PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES) {
+ return -1;
+ }
+ ring_size = ri->numPages * PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
+ len_log2 = pvscsi_log2(ring_size - 1);
+
+ m->msg_len_mask = MASK(len_log2);
+
+ m->filled_msg_ptr = 0;
+
+ for (i = 0; i < ri->numPages; i++) {
+ m->msg_ring_pages_pa[i] = ri->ringPPNs[i] << VMW_PAGE_SHIFT;
+ }
+
+ RS_SET_FIELD(m, msgProdIdx, 0);
+ RS_SET_FIELD(m, msgConsIdx, 0);
+ RS_SET_FIELD(m, msgNumEntriesLog2, len_log2);
+
+ trace_pvscsi_ring_init_msg(len_log2);
+
+ /* Flush ring state page changes */
+ smp_wmb();
+
+ return 0;
+}
+
+static void
+pvscsi_ring_cleanup(PVSCSIRingInfo *mgr)
+{
+ mgr->rs_pa = 0;
+ mgr->txr_len_mask = 0;
+ mgr->rxr_len_mask = 0;
+ mgr->msg_len_mask = 0;
+ mgr->consumed_ptr = 0;
+ mgr->filled_cmp_ptr = 0;
+ mgr->filled_msg_ptr = 0;
+ memset(mgr->req_ring_pages_pa, 0, sizeof(mgr->req_ring_pages_pa));
+ memset(mgr->cmp_ring_pages_pa, 0, sizeof(mgr->cmp_ring_pages_pa));
+ memset(mgr->msg_ring_pages_pa, 0, sizeof(mgr->msg_ring_pages_pa));
+}
+
+static hwaddr
+pvscsi_ring_pop_req_descr(PVSCSIRingInfo *mgr)
+{
+ uint32_t ready_ptr;
+ uint32_t ring_size = PVSCSI_MAX_NUM_PAGES_REQ_RING
+ * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+
+ RS_GET_FIELD(&ready_ptr, mgr, reqProdIdx);
+ if (ready_ptr != mgr->consumed_ptr
+ && ready_ptr - mgr->consumed_ptr < ring_size) {
+ uint32_t next_ready_ptr =
+ mgr->consumed_ptr++ & mgr->txr_len_mask;
+ uint32_t next_ready_page =
+ next_ready_ptr / PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ uint32_t inpage_idx =
+ next_ready_ptr % PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+
+ return mgr->req_ring_pages_pa[next_ready_page] +
+ inpage_idx * sizeof(PVSCSIRingReqDesc);
+ } else {
+ return 0;
+ }
+}
+
+static void
+pvscsi_ring_flush_req(PVSCSIRingInfo *mgr)
+{
+ RS_SET_FIELD(mgr, reqConsIdx, mgr->consumed_ptr);
+}
+
+static hwaddr
+pvscsi_ring_pop_cmp_descr(PVSCSIRingInfo *mgr)
+{
+ /*
+ * According to Linux driver code it explicitly verifies that number
+ * of requests being processed by device is less then the size of
+ * completion queue, so device may omit completion queue overflow
+ * conditions check. We assume that this is true for other (Windows)
+ * drivers as well.
+ */
+
+ uint32_t free_cmp_ptr =
+ mgr->filled_cmp_ptr++ & mgr->rxr_len_mask;
+ uint32_t free_cmp_page =
+ free_cmp_ptr / PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
+ uint32_t inpage_idx =
+ free_cmp_ptr % PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE;
+ return mgr->cmp_ring_pages_pa[free_cmp_page] +
+ inpage_idx * sizeof(PVSCSIRingCmpDesc);
+}
+
+static hwaddr
+pvscsi_ring_pop_msg_descr(PVSCSIRingInfo *mgr)
+{
+ uint32_t free_msg_ptr =
+ mgr->filled_msg_ptr++ & mgr->msg_len_mask;
+ uint32_t free_msg_page =
+ free_msg_ptr / PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
+ uint32_t inpage_idx =
+ free_msg_ptr % PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE;
+ return mgr->msg_ring_pages_pa[free_msg_page] +
+ inpage_idx * sizeof(PVSCSIRingMsgDesc);
+}
+
+static void
+pvscsi_ring_flush_cmp(PVSCSIRingInfo *mgr)
+{
+ /* Flush descriptor changes */
+ smp_wmb();
+
+ trace_pvscsi_ring_flush_cmp(mgr->filled_cmp_ptr);
+
+ RS_SET_FIELD(mgr, cmpProdIdx, mgr->filled_cmp_ptr);
+}
+
+static bool
+pvscsi_ring_msg_has_room(PVSCSIRingInfo *mgr)
+{
+ uint32_t prodIdx;
+ uint32_t consIdx;
+
+ RS_GET_FIELD(&prodIdx, mgr, msgProdIdx);
+ RS_GET_FIELD(&consIdx, mgr, msgConsIdx);
+
+ return (prodIdx - consIdx) < (mgr->msg_len_mask + 1);
+}
+
+static void
+pvscsi_ring_flush_msg(PVSCSIRingInfo *mgr)
+{
+ /* Flush descriptor changes */
+ smp_wmb();
+
+ trace_pvscsi_ring_flush_msg(mgr->filled_msg_ptr);
+
+ RS_SET_FIELD(mgr, msgProdIdx, mgr->filled_msg_ptr);
+}
+
+static void
+pvscsi_reset_state(PVSCSIState *s)
+{
+ s->curr_cmd = PVSCSI_CMD_FIRST;
+ s->curr_cmd_data_cntr = 0;
+ s->reg_command_status = PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+ s->reg_interrupt_status = 0;
+ pvscsi_ring_cleanup(&s->rings);
+ s->rings_info_valid = FALSE;
+ s->msg_ring_info_valid = FALSE;
+ QTAILQ_INIT(&s->pending_queue);
+ QTAILQ_INIT(&s->completion_queue);
+}
+
+static void
+pvscsi_update_irq_status(PVSCSIState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+ bool should_raise = s->reg_interrupt_enabled & s->reg_interrupt_status;
+
+ trace_pvscsi_update_irq_level(should_raise, s->reg_interrupt_enabled,
+ s->reg_interrupt_status);
+
+ if (msi_enabled(d)) {
+ if (should_raise) {
+ trace_pvscsi_update_irq_msi();
+ msi_notify(d, PVSCSI_VECTOR_COMPLETION);
+ }
+ return;
+ }
+
+ pci_set_irq(d, !!should_raise);
+}
+
+static void
+pvscsi_raise_completion_interrupt(PVSCSIState *s)
+{
+ s->reg_interrupt_status |= PVSCSI_INTR_CMPL_0;
+
+ /* Memory barrier to flush interrupt status register changes*/
+ smp_wmb();
+
+ pvscsi_update_irq_status(s);
+}
+
+static void
+pvscsi_raise_message_interrupt(PVSCSIState *s)
+{
+ s->reg_interrupt_status |= PVSCSI_INTR_MSG_0;
+
+ /* Memory barrier to flush interrupt status register changes*/
+ smp_wmb();
+
+ pvscsi_update_irq_status(s);
+}
+
+static void
+pvscsi_cmp_ring_put(PVSCSIState *s, struct PVSCSIRingCmpDesc *cmp_desc)
+{
+ hwaddr cmp_descr_pa;
+
+ cmp_descr_pa = pvscsi_ring_pop_cmp_descr(&s->rings);
+ trace_pvscsi_cmp_ring_put(cmp_descr_pa);
+ cpu_physical_memory_write(cmp_descr_pa, cmp_desc, sizeof(*cmp_desc));
+}
+
+static void
+pvscsi_msg_ring_put(PVSCSIState *s, struct PVSCSIRingMsgDesc *msg_desc)
+{
+ hwaddr msg_descr_pa;
+
+ msg_descr_pa = pvscsi_ring_pop_msg_descr(&s->rings);
+ trace_pvscsi_msg_ring_put(msg_descr_pa);
+ cpu_physical_memory_write(msg_descr_pa, msg_desc, sizeof(*msg_desc));
+}
+
+static void
+pvscsi_process_completion_queue(void *opaque)
+{
+ PVSCSIState *s = opaque;
+ PVSCSIRequest *pvscsi_req;
+ bool has_completed = false;
+
+ while (!QTAILQ_EMPTY(&s->completion_queue)) {
+ pvscsi_req = QTAILQ_FIRST(&s->completion_queue);
+ QTAILQ_REMOVE(&s->completion_queue, pvscsi_req, next);
+ pvscsi_cmp_ring_put(s, &pvscsi_req->cmp);
+ g_free(pvscsi_req);
+ has_completed = true;
+ }
+
+ if (has_completed) {
+ pvscsi_ring_flush_cmp(&s->rings);
+ pvscsi_raise_completion_interrupt(s);
+ }
+}
+
+static void
+pvscsi_reset_adapter(PVSCSIState *s)
+{
+ s->resetting++;
+ bus_cold_reset(BUS(&s->bus));
+ s->resetting--;
+ pvscsi_process_completion_queue(s);
+ assert(QTAILQ_EMPTY(&s->pending_queue));
+ pvscsi_reset_state(s);
+}
+
+static void
+pvscsi_schedule_completion_processing(PVSCSIState *s)
+{
+ /* Try putting more complete requests on the ring. */
+ if (!QTAILQ_EMPTY(&s->completion_queue)) {
+ qemu_bh_schedule(s->completion_worker);
+ }
+}
+
+static void
+pvscsi_complete_request(PVSCSIState *s, PVSCSIRequest *r)
+{
+ assert(!r->completed);
+
+ trace_pvscsi_complete_request(r->cmp.context, r->cmp.dataLen,
+ r->sense_key);
+ if (r->sreq != NULL) {
+ scsi_req_unref(r->sreq);
+ r->sreq = NULL;
+ }
+ r->completed = 1;
+ QTAILQ_REMOVE(&s->pending_queue, r, next);
+ QTAILQ_INSERT_TAIL(&s->completion_queue, r, next);
+ pvscsi_schedule_completion_processing(s);
+}
+
+static QEMUSGList *pvscsi_get_sg_list(SCSIRequest *r)
+{
+ PVSCSIRequest *req = r->hba_private;
+
+ trace_pvscsi_get_sg_list(req->sgl.nsg, req->sgl.size);
+
+ return &req->sgl;
+}
+
+static void
+pvscsi_get_next_sg_elem(PVSCSISGState *sg)
+{
+ struct PVSCSISGElement elem;
+
+ cpu_physical_memory_read(sg->elemAddr, &elem, sizeof(elem));
+ if ((elem.flags & ~PVSCSI_KNOWN_FLAGS) != 0) {
+ /*
+ * There is PVSCSI_SGE_FLAG_CHAIN_ELEMENT flag described in
+ * header file but its value is unknown. This flag requires
+ * additional processing, so we put warning here to catch it
+ * some day and make proper implementation
+ */
+ trace_pvscsi_get_next_sg_elem(elem.flags);
+ }
+
+ sg->elemAddr += sizeof(elem);
+ sg->dataAddr = elem.addr;
+ sg->resid = elem.length;
+}
+
+static void
+pvscsi_write_sense(PVSCSIRequest *r, uint8_t *sense, int len)
+{
+ r->cmp.senseLen = MIN(r->req.senseLen, len);
+ r->sense_key = sense[(sense[0] & 2) ? 1 : 2];
+ cpu_physical_memory_write(r->req.senseAddr, sense, r->cmp.senseLen);
+}
+
+static void
+pvscsi_command_failed(SCSIRequest *req)
+{
+ PVSCSIRequest *pvscsi_req = req->hba_private;
+ PVSCSIState *s;
+
+ if (!pvscsi_req) {
+ trace_pvscsi_command_complete_not_found(req->tag);
+ return;
+ }
+ s = pvscsi_req->dev;
+
+ switch (req->host_status) {
+ case SCSI_HOST_NO_LUN:
+ pvscsi_req->cmp.hostStatus = BTSTAT_LUNMISMATCH;
+ break;
+ case SCSI_HOST_BUSY:
+ pvscsi_req->cmp.hostStatus = BTSTAT_ABORTQUEUE;
+ break;
+ case SCSI_HOST_TIME_OUT:
+ case SCSI_HOST_ABORTED:
+ pvscsi_req->cmp.hostStatus = BTSTAT_SENTRST;
+ break;
+ case SCSI_HOST_BAD_RESPONSE:
+ pvscsi_req->cmp.hostStatus = BTSTAT_SELTIMEO;
+ break;
+ case SCSI_HOST_RESET:
+ pvscsi_req->cmp.hostStatus = BTSTAT_BUSRESET;
+ break;
+ default:
+ pvscsi_req->cmp.hostStatus = BTSTAT_HASOFTWARE;
+ break;
+ }
+ pvscsi_req->cmp.scsiStatus = GOOD;
+ qemu_sglist_destroy(&pvscsi_req->sgl);
+ pvscsi_complete_request(s, pvscsi_req);
+}
+
+static void
+pvscsi_command_complete(SCSIRequest *req, size_t resid)
+{
+ PVSCSIRequest *pvscsi_req = req->hba_private;
+ PVSCSIState *s;
+
+ if (!pvscsi_req) {
+ trace_pvscsi_command_complete_not_found(req->tag);
+ return;
+ }
+ s = pvscsi_req->dev;
+
+ if (resid) {
+ /* Short transfer. */
+ trace_pvscsi_command_complete_data_run();
+ pvscsi_req->cmp.hostStatus = BTSTAT_DATARUN;
+ }
+
+ pvscsi_req->cmp.scsiStatus = req->status;
+ if (pvscsi_req->cmp.scsiStatus == CHECK_CONDITION) {
+ uint8_t sense[SCSI_SENSE_BUF_SIZE];
+ int sense_len =
+ scsi_req_get_sense(pvscsi_req->sreq, sense, sizeof(sense));
+
+ trace_pvscsi_command_complete_sense_len(sense_len);
+ pvscsi_write_sense(pvscsi_req, sense, sense_len);
+ }
+ qemu_sglist_destroy(&pvscsi_req->sgl);
+ pvscsi_complete_request(s, pvscsi_req);
+}
+
+static void
+pvscsi_send_msg(PVSCSIState *s, SCSIDevice *dev, uint32_t msg_type)
+{
+ if (s->msg_ring_info_valid && pvscsi_ring_msg_has_room(&s->rings)) {
+ PVSCSIMsgDescDevStatusChanged msg = {0};
+
+ msg.type = msg_type;
+ msg.bus = dev->channel;
+ msg.target = dev->id;
+ msg.lun[1] = dev->lun;
+
+ pvscsi_msg_ring_put(s, (PVSCSIRingMsgDesc *)&msg);
+ pvscsi_ring_flush_msg(&s->rings);
+ pvscsi_raise_message_interrupt(s);
+ }
+}
+
+static void
+pvscsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp)
+{
+ PVSCSIState *s = PVSCSI(hotplug_dev);
+
+ pvscsi_send_msg(s, SCSI_DEVICE(dev), PVSCSI_MSG_DEV_ADDED);
+}
+
+static void
+pvscsi_hot_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp)
+{
+ PVSCSIState *s = PVSCSI(hotplug_dev);
+
+ pvscsi_send_msg(s, SCSI_DEVICE(dev), PVSCSI_MSG_DEV_REMOVED);
+ qdev_simple_device_unplug_cb(hotplug_dev, dev, errp);
+}
+
+static void
+pvscsi_request_cancelled(SCSIRequest *req)
+{
+ PVSCSIRequest *pvscsi_req = req->hba_private;
+ PVSCSIState *s = pvscsi_req->dev;
+
+ if (pvscsi_req->completed) {
+ return;
+ }
+
+ if (pvscsi_req->dev->resetting) {
+ pvscsi_req->cmp.hostStatus = BTSTAT_BUSRESET;
+ } else {
+ pvscsi_req->cmp.hostStatus = BTSTAT_ABORTQUEUE;
+ }
+
+ pvscsi_complete_request(s, pvscsi_req);
+}
+
+static SCSIDevice*
+pvscsi_device_find(PVSCSIState *s, int channel, int target,
+ uint8_t *requested_lun, uint8_t *target_lun)
+{
+ if (requested_lun[0] || requested_lun[2] || requested_lun[3] ||
+ requested_lun[4] || requested_lun[5] || requested_lun[6] ||
+ requested_lun[7] || (target > PVSCSI_MAX_DEVS)) {
+ return NULL;
+ } else {
+ *target_lun = requested_lun[1];
+ return scsi_device_find(&s->bus, channel, target, *target_lun);
+ }
+}
+
+static PVSCSIRequest *
+pvscsi_queue_pending_descriptor(PVSCSIState *s, SCSIDevice **d,
+ struct PVSCSIRingReqDesc *descr)
+{
+ PVSCSIRequest *pvscsi_req;
+ uint8_t lun;
+
+ pvscsi_req = g_malloc0(sizeof(*pvscsi_req));
+ pvscsi_req->dev = s;
+ pvscsi_req->req = *descr;
+ pvscsi_req->cmp.context = pvscsi_req->req.context;
+ QTAILQ_INSERT_TAIL(&s->pending_queue, pvscsi_req, next);
+
+ *d = pvscsi_device_find(s, descr->bus, descr->target, descr->lun, &lun);
+ if (*d) {
+ pvscsi_req->lun = lun;
+ }
+
+ return pvscsi_req;
+}
+
+static void
+pvscsi_convert_sglist(PVSCSIRequest *r)
+{
+ uint32_t chunk_size, elmcnt = 0;
+ uint64_t data_length = r->req.dataLen;
+ PVSCSISGState sg = r->sg;
+ while (data_length && elmcnt < PVSCSI_MAX_SG_ELEM) {
+ while (!sg.resid && elmcnt++ < PVSCSI_MAX_SG_ELEM) {
+ pvscsi_get_next_sg_elem(&sg);
+ trace_pvscsi_convert_sglist(r->req.context, r->sg.dataAddr,
+ r->sg.resid);
+ }
+ chunk_size = MIN(data_length, sg.resid);
+ if (chunk_size) {
+ qemu_sglist_add(&r->sgl, sg.dataAddr, chunk_size);
+ }
+
+ sg.dataAddr += chunk_size;
+ data_length -= chunk_size;
+ sg.resid -= chunk_size;
+ }
+}
+
+static void
+pvscsi_build_sglist(PVSCSIState *s, PVSCSIRequest *r)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ pci_dma_sglist_init(&r->sgl, d, 1);
+ if (r->req.flags & PVSCSI_FLAG_CMD_WITH_SG_LIST) {
+ pvscsi_convert_sglist(r);
+ } else {
+ qemu_sglist_add(&r->sgl, r->req.dataAddr, r->req.dataLen);
+ }
+}
+
+static void
+pvscsi_process_request_descriptor(PVSCSIState *s,
+ struct PVSCSIRingReqDesc *descr)
+{
+ SCSIDevice *d;
+ PVSCSIRequest *r = pvscsi_queue_pending_descriptor(s, &d, descr);
+ int64_t n;
+
+ trace_pvscsi_process_req_descr(descr->cdb[0], descr->context);
+
+ if (!d) {
+ r->cmp.hostStatus = BTSTAT_SELTIMEO;
+ trace_pvscsi_process_req_descr_unknown_device();
+ pvscsi_complete_request(s, r);
+ return;
+ }
+
+ if (descr->flags & PVSCSI_FLAG_CMD_WITH_SG_LIST) {
+ r->sg.elemAddr = descr->dataAddr;
+ }
+
+ r->sreq = scsi_req_new(d, descr->context, r->lun, descr->cdb, descr->cdbLen, r);
+ if (r->sreq->cmd.mode == SCSI_XFER_FROM_DEV &&
+ (descr->flags & PVSCSI_FLAG_CMD_DIR_TODEVICE)) {
+ r->cmp.hostStatus = BTSTAT_BADMSG;
+ trace_pvscsi_process_req_descr_invalid_dir();
+ scsi_req_cancel(r->sreq);
+ return;
+ }
+ if (r->sreq->cmd.mode == SCSI_XFER_TO_DEV &&
+ (descr->flags & PVSCSI_FLAG_CMD_DIR_TOHOST)) {
+ r->cmp.hostStatus = BTSTAT_BADMSG;
+ trace_pvscsi_process_req_descr_invalid_dir();
+ scsi_req_cancel(r->sreq);
+ return;
+ }
+
+ pvscsi_build_sglist(s, r);
+ n = scsi_req_enqueue(r->sreq);
+
+ if (n) {
+ scsi_req_continue(r->sreq);
+ }
+}
+
+static void
+pvscsi_process_io(PVSCSIState *s)
+{
+ PVSCSIRingReqDesc descr;
+ hwaddr next_descr_pa;
+
+ if (!s->rings_info_valid) {
+ return;
+ }
+
+ while ((next_descr_pa = pvscsi_ring_pop_req_descr(&s->rings)) != 0) {
+
+ /* Only read after production index verification */
+ smp_rmb();
+
+ trace_pvscsi_process_io(next_descr_pa);
+ cpu_physical_memory_read(next_descr_pa, &descr, sizeof(descr));
+ pvscsi_process_request_descriptor(s, &descr);
+ }
+
+ pvscsi_ring_flush_req(&s->rings);
+}
+
+static void
+pvscsi_dbg_dump_tx_rings_config(PVSCSICmdDescSetupRings *rc)
+{
+ int i;
+ trace_pvscsi_tx_rings_ppn("Rings State", rc->ringsStatePPN);
+
+ trace_pvscsi_tx_rings_num_pages("Request Ring", rc->reqRingNumPages);
+ for (i = 0; i < rc->reqRingNumPages; i++) {
+ trace_pvscsi_tx_rings_ppn("Request Ring", rc->reqRingPPNs[i]);
+ }
+
+ trace_pvscsi_tx_rings_num_pages("Confirm Ring", rc->cmpRingNumPages);
+ for (i = 0; i < rc->cmpRingNumPages; i++) {
+ trace_pvscsi_tx_rings_ppn("Confirm Ring", rc->cmpRingPPNs[i]);
+ }
+}
+
+static uint64_t
+pvscsi_on_cmd_config(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_noimpl("PVSCSI_CMD_CONFIG");
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_unplug(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_noimpl("PVSCSI_CMD_DEVICE_UNPLUG");
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_issue_scsi(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_noimpl("PVSCSI_CMD_ISSUE_SCSI");
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_setup_rings(PVSCSIState *s)
+{
+ PVSCSICmdDescSetupRings *rc =
+ (PVSCSICmdDescSetupRings *) s->curr_cmd_data;
+
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_SETUP_RINGS");
+
+ if (!rc->reqRingNumPages
+ || rc->reqRingNumPages > PVSCSI_SETUP_RINGS_MAX_NUM_PAGES
+ || !rc->cmpRingNumPages
+ || rc->cmpRingNumPages > PVSCSI_SETUP_RINGS_MAX_NUM_PAGES) {
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+ }
+
+ pvscsi_dbg_dump_tx_rings_config(rc);
+ pvscsi_ring_init_data(&s->rings, rc);
+
+ s->rings_info_valid = TRUE;
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static uint64_t
+pvscsi_on_cmd_abort(PVSCSIState *s)
+{
+ PVSCSICmdDescAbortCmd *cmd = (PVSCSICmdDescAbortCmd *) s->curr_cmd_data;
+ PVSCSIRequest *r, *next;
+
+ trace_pvscsi_on_cmd_abort(cmd->context, cmd->target);
+
+ QTAILQ_FOREACH_SAFE(r, &s->pending_queue, next, next) {
+ if (r->req.context == cmd->context) {
+ break;
+ }
+ }
+ if (r) {
+ assert(!r->completed);
+ r->cmp.hostStatus = BTSTAT_ABORTQUEUE;
+ scsi_req_cancel(r->sreq);
+ }
+
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static uint64_t
+pvscsi_on_cmd_unknown(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_unknown_data(s->curr_cmd_data[0]);
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_reset_device(PVSCSIState *s)
+{
+ uint8_t target_lun = 0;
+ struct PVSCSICmdDescResetDevice *cmd =
+ (struct PVSCSICmdDescResetDevice *) s->curr_cmd_data;
+ SCSIDevice *sdev;
+
+ sdev = pvscsi_device_find(s, 0, cmd->target, cmd->lun, &target_lun);
+
+ trace_pvscsi_on_cmd_reset_dev(cmd->target, (int) target_lun, sdev);
+
+ if (sdev != NULL) {
+ s->resetting++;
+ device_cold_reset(&sdev->qdev);
+ s->resetting--;
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+ }
+
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+}
+
+static uint64_t
+pvscsi_on_cmd_reset_bus(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_RESET_BUS");
+
+ s->resetting++;
+ bus_cold_reset(BUS(&s->bus));
+ s->resetting--;
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static uint64_t
+pvscsi_on_cmd_setup_msg_ring(PVSCSIState *s)
+{
+ PVSCSICmdDescSetupMsgRing *rc =
+ (PVSCSICmdDescSetupMsgRing *) s->curr_cmd_data;
+
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_SETUP_MSG_RING");
+
+ if (!s->use_msg) {
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+ }
+
+ if (s->rings_info_valid) {
+ if (pvscsi_ring_init_msg(&s->rings, rc) < 0) {
+ return PVSCSI_COMMAND_PROCESSING_FAILED;
+ }
+ s->msg_ring_info_valid = TRUE;
+ }
+ return sizeof(PVSCSICmdDescSetupMsgRing) / sizeof(uint32_t);
+}
+
+static uint64_t
+pvscsi_on_cmd_adapter_reset(PVSCSIState *s)
+{
+ trace_pvscsi_on_cmd_arrived("PVSCSI_CMD_ADAPTER_RESET");
+
+ pvscsi_reset_adapter(s);
+ return PVSCSI_COMMAND_PROCESSING_SUCCEEDED;
+}
+
+static const struct {
+ int data_size;
+ uint64_t (*handler_fn)(PVSCSIState *s);
+} pvscsi_commands[] = {
+ [PVSCSI_CMD_FIRST] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_unknown,
+ },
+
+ /* Not implemented, data size defined based on what arrives on windows */
+ [PVSCSI_CMD_CONFIG] = {
+ .data_size = 6 * sizeof(uint32_t),
+ .handler_fn = pvscsi_on_cmd_config,
+ },
+
+ /* Command not implemented, data size is unknown */
+ [PVSCSI_CMD_ISSUE_SCSI] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_issue_scsi,
+ },
+
+ /* Command not implemented, data size is unknown */
+ [PVSCSI_CMD_DEVICE_UNPLUG] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_unplug,
+ },
+
+ [PVSCSI_CMD_SETUP_RINGS] = {
+ .data_size = sizeof(PVSCSICmdDescSetupRings),
+ .handler_fn = pvscsi_on_cmd_setup_rings,
+ },
+
+ [PVSCSI_CMD_RESET_DEVICE] = {
+ .data_size = sizeof(struct PVSCSICmdDescResetDevice),
+ .handler_fn = pvscsi_on_cmd_reset_device,
+ },
+
+ [PVSCSI_CMD_RESET_BUS] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_reset_bus,
+ },
+
+ [PVSCSI_CMD_SETUP_MSG_RING] = {
+ .data_size = sizeof(PVSCSICmdDescSetupMsgRing),
+ .handler_fn = pvscsi_on_cmd_setup_msg_ring,
+ },
+
+ [PVSCSI_CMD_ADAPTER_RESET] = {
+ .data_size = 0,
+ .handler_fn = pvscsi_on_cmd_adapter_reset,
+ },
+
+ [PVSCSI_CMD_ABORT_CMD] = {
+ .data_size = sizeof(struct PVSCSICmdDescAbortCmd),
+ .handler_fn = pvscsi_on_cmd_abort,
+ },
+};
+
+static void
+pvscsi_do_command_processing(PVSCSIState *s)
+{
+ size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t);
+
+ assert(s->curr_cmd < PVSCSI_CMD_LAST);
+ if (bytes_arrived >= pvscsi_commands[s->curr_cmd].data_size) {
+ s->reg_command_status = pvscsi_commands[s->curr_cmd].handler_fn(s);
+ s->curr_cmd = PVSCSI_CMD_FIRST;
+ s->curr_cmd_data_cntr = 0;
+ }
+}
+
+static void
+pvscsi_on_command_data(PVSCSIState *s, uint32_t value)
+{
+ size_t bytes_arrived = s->curr_cmd_data_cntr * sizeof(uint32_t);
+
+ assert(bytes_arrived < sizeof(s->curr_cmd_data));
+ s->curr_cmd_data[s->curr_cmd_data_cntr++] = value;
+
+ pvscsi_do_command_processing(s);
+}
+
+static void
+pvscsi_on_command(PVSCSIState *s, uint64_t cmd_id)
+{
+ if ((cmd_id > PVSCSI_CMD_FIRST) && (cmd_id < PVSCSI_CMD_LAST)) {
+ s->curr_cmd = cmd_id;
+ } else {
+ s->curr_cmd = PVSCSI_CMD_FIRST;
+ trace_pvscsi_on_cmd_unknown(cmd_id);
+ }
+
+ s->curr_cmd_data_cntr = 0;
+ s->reg_command_status = PVSCSI_COMMAND_NOT_ENOUGH_DATA;
+
+ pvscsi_do_command_processing(s);
+}
+
+static void
+pvscsi_io_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PVSCSIState *s = opaque;
+
+ switch (addr) {
+ case PVSCSI_REG_OFFSET_COMMAND:
+ pvscsi_on_command(s, val);
+ break;
+
+ case PVSCSI_REG_OFFSET_COMMAND_DATA:
+ pvscsi_on_command_data(s, (uint32_t) val);
+ break;
+
+ case PVSCSI_REG_OFFSET_INTR_STATUS:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_INTR_STATUS", val);
+ s->reg_interrupt_status &= ~val;
+ pvscsi_update_irq_status(s);
+ pvscsi_schedule_completion_processing(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_INTR_MASK:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_INTR_MASK", val);
+ s->reg_interrupt_enabled = val;
+ pvscsi_update_irq_status(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_KICK_NON_RW_IO:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_KICK_NON_RW_IO", val);
+ pvscsi_process_io(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_KICK_RW_IO:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_KICK_RW_IO", val);
+ pvscsi_process_io(s);
+ break;
+
+ case PVSCSI_REG_OFFSET_DEBUG:
+ trace_pvscsi_io_write("PVSCSI_REG_OFFSET_DEBUG", val);
+ break;
+
+ default:
+ trace_pvscsi_io_write_unknown(addr, size, val);
+ break;
+ }
+
+}
+
+static uint64_t
+pvscsi_io_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PVSCSIState *s = opaque;
+
+ switch (addr) {
+ case PVSCSI_REG_OFFSET_INTR_STATUS:
+ trace_pvscsi_io_read("PVSCSI_REG_OFFSET_INTR_STATUS",
+ s->reg_interrupt_status);
+ return s->reg_interrupt_status;
+
+ case PVSCSI_REG_OFFSET_INTR_MASK:
+ trace_pvscsi_io_read("PVSCSI_REG_OFFSET_INTR_MASK",
+ s->reg_interrupt_status);
+ return s->reg_interrupt_enabled;
+
+ case PVSCSI_REG_OFFSET_COMMAND_STATUS:
+ trace_pvscsi_io_read("PVSCSI_REG_OFFSET_COMMAND_STATUS",
+ s->reg_interrupt_status);
+ return s->reg_command_status;
+
+ default:
+ trace_pvscsi_io_read_unknown(addr, size);
+ return 0;
+ }
+}
+
+
+static void
+pvscsi_init_msi(PVSCSIState *s)
+{
+ int res;
+ PCIDevice *d = PCI_DEVICE(s);
+
+ res = msi_init(d, PVSCSI_MSI_OFFSET(s), PVSCSI_MSIX_NUM_VECTORS,
+ PVSCSI_USE_64BIT, PVSCSI_PER_VECTOR_MASK, NULL);
+ if (res < 0) {
+ trace_pvscsi_init_msi_fail(res);
+ s->msi_used = false;
+ } else {
+ s->msi_used = true;
+ }
+}
+
+static void
+pvscsi_cleanup_msi(PVSCSIState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ msi_uninit(d);
+}
+
+static const MemoryRegionOps pvscsi_ops = {
+ .read = pvscsi_io_read,
+ .write = pvscsi_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const struct SCSIBusInfo pvscsi_scsi_info = {
+ .tcq = true,
+ .max_target = PVSCSI_MAX_DEVS,
+ .max_channel = 0,
+ .max_lun = 0,
+
+ .get_sg_list = pvscsi_get_sg_list,
+ .complete = pvscsi_command_complete,
+ .cancel = pvscsi_request_cancelled,
+ .fail = pvscsi_command_failed,
+};
+
+static void
+pvscsi_realizefn(PCIDevice *pci_dev, Error **errp)
+{
+ PVSCSIState *s = PVSCSI(pci_dev);
+
+ trace_pvscsi_state("init");
+
+ /* PCI subsystem ID, subsystem vendor ID, revision */
+ if (PVSCSI_USE_OLD_PCI_CONFIGURATION(s)) {
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_ID, 0x1000);
+ } else {
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_VENDOR_ID,
+ PCI_VENDOR_ID_VMWARE);
+ pci_set_word(pci_dev->config + PCI_SUBSYSTEM_ID,
+ PCI_DEVICE_ID_VMWARE_PVSCSI);
+ pci_config_set_revision(pci_dev->config, 0x2);
+ }
+
+ /* PCI latency timer = 255 */
+ pci_dev->config[PCI_LATENCY_TIMER] = 0xff;
+
+ /* Interrupt pin A */
+ pci_config_set_interrupt_pin(pci_dev->config, 1);
+
+ memory_region_init_io(&s->io_space, OBJECT(s), &pvscsi_ops, s,
+ "pvscsi-io", PVSCSI_MEM_SPACE_SIZE);
+ pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->io_space);
+
+ pvscsi_init_msi(s);
+
+ if (pci_is_express(pci_dev) && pci_bus_is_express(pci_get_bus(pci_dev))) {
+ pcie_endpoint_cap_init(pci_dev, PVSCSI_EXP_EP_OFFSET);
+ }
+
+ s->completion_worker = qemu_bh_new(pvscsi_process_completion_queue, s);
+
+ scsi_bus_init(&s->bus, sizeof(s->bus), DEVICE(pci_dev), &pvscsi_scsi_info);
+ /* override default SCSI bus hotplug-handler, with pvscsi's one */
+ qbus_set_hotplug_handler(BUS(&s->bus), OBJECT(s));
+ pvscsi_reset_state(s);
+}
+
+static void
+pvscsi_uninit(PCIDevice *pci_dev)
+{
+ PVSCSIState *s = PVSCSI(pci_dev);
+
+ trace_pvscsi_state("uninit");
+ qemu_bh_delete(s->completion_worker);
+
+ pvscsi_cleanup_msi(s);
+}
+
+static void
+pvscsi_reset(DeviceState *dev)
+{
+ PCIDevice *d = PCI_DEVICE(dev);
+ PVSCSIState *s = PVSCSI(d);
+
+ trace_pvscsi_state("reset");
+ pvscsi_reset_adapter(s);
+}
+
+static int
+pvscsi_pre_save(void *opaque)
+{
+ PVSCSIState *s = (PVSCSIState *) opaque;
+
+ trace_pvscsi_state("presave");
+
+ assert(QTAILQ_EMPTY(&s->pending_queue));
+ assert(QTAILQ_EMPTY(&s->completion_queue));
+
+ return 0;
+}
+
+static int
+pvscsi_post_load(void *opaque, int version_id)
+{
+ trace_pvscsi_state("postload");
+ return 0;
+}
+
+static bool pvscsi_vmstate_need_pcie_device(void *opaque)
+{
+ PVSCSIState *s = PVSCSI(opaque);
+
+ return !(s->compat_flags & PVSCSI_COMPAT_DISABLE_PCIE);
+}
+
+static bool pvscsi_vmstate_test_pci_device(void *opaque, int version_id)
+{
+ return !pvscsi_vmstate_need_pcie_device(opaque);
+}
+
+static const VMStateDescription vmstate_pvscsi_pcie_device = {
+ .name = "pvscsi/pcie",
+ .needed = pvscsi_vmstate_need_pcie_device,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PVSCSIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pvscsi = {
+ .name = "pvscsi",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .pre_save = pvscsi_pre_save,
+ .post_load = pvscsi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_TEST(parent_obj, PVSCSIState,
+ pvscsi_vmstate_test_pci_device, 0,
+ vmstate_pci_device, PCIDevice),
+ VMSTATE_UINT8(msi_used, PVSCSIState),
+ VMSTATE_UINT32(resetting, PVSCSIState),
+ VMSTATE_UINT64(reg_interrupt_status, PVSCSIState),
+ VMSTATE_UINT64(reg_interrupt_enabled, PVSCSIState),
+ VMSTATE_UINT64(reg_command_status, PVSCSIState),
+ VMSTATE_UINT64(curr_cmd, PVSCSIState),
+ VMSTATE_UINT32(curr_cmd_data_cntr, PVSCSIState),
+ VMSTATE_UINT32_ARRAY(curr_cmd_data, PVSCSIState,
+ ARRAY_SIZE(((PVSCSIState *)NULL)->curr_cmd_data)),
+ VMSTATE_UINT8(rings_info_valid, PVSCSIState),
+ VMSTATE_UINT8(msg_ring_info_valid, PVSCSIState),
+ VMSTATE_UINT8(use_msg, PVSCSIState),
+
+ VMSTATE_UINT64(rings.rs_pa, PVSCSIState),
+ VMSTATE_UINT32(rings.txr_len_mask, PVSCSIState),
+ VMSTATE_UINT32(rings.rxr_len_mask, PVSCSIState),
+ VMSTATE_UINT64_ARRAY(rings.req_ring_pages_pa, PVSCSIState,
+ PVSCSI_SETUP_RINGS_MAX_NUM_PAGES),
+ VMSTATE_UINT64_ARRAY(rings.cmp_ring_pages_pa, PVSCSIState,
+ PVSCSI_SETUP_RINGS_MAX_NUM_PAGES),
+ VMSTATE_UINT64(rings.consumed_ptr, PVSCSIState),
+ VMSTATE_UINT64(rings.filled_cmp_ptr, PVSCSIState),
+
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_pvscsi_pcie_device,
+ NULL
+ }
+};
+
+static Property pvscsi_properties[] = {
+ DEFINE_PROP_UINT8("use_msg", PVSCSIState, use_msg, 1),
+ DEFINE_PROP_BIT("x-old-pci-configuration", PVSCSIState, compat_flags,
+ PVSCSI_COMPAT_OLD_PCI_CONFIGURATION_BIT, false),
+ DEFINE_PROP_BIT("x-disable-pcie", PVSCSIState, compat_flags,
+ PVSCSI_COMPAT_DISABLE_PCIE_BIT, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pvscsi_realize(DeviceState *qdev, Error **errp)
+{
+ PVSCSIClass *pvs_c = PVSCSI_GET_CLASS(qdev);
+ PCIDevice *pci_dev = PCI_DEVICE(qdev);
+ PVSCSIState *s = PVSCSI(qdev);
+
+ if (!(s->compat_flags & PVSCSI_COMPAT_DISABLE_PCIE)) {
+ pci_dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
+ }
+
+ pvs_c->parent_dc_realize(qdev, errp);
+}
+
+static void pvscsi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ PVSCSIClass *pvs_k = PVSCSI_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ k->realize = pvscsi_realizefn;
+ k->exit = pvscsi_uninit;
+ k->vendor_id = PCI_VENDOR_ID_VMWARE;
+ k->device_id = PCI_DEVICE_ID_VMWARE_PVSCSI;
+ k->class_id = PCI_CLASS_STORAGE_SCSI;
+ k->subsystem_id = 0x1000;
+ device_class_set_parent_realize(dc, pvscsi_realize,
+ &pvs_k->parent_dc_realize);
+ dc->reset = pvscsi_reset;
+ dc->vmsd = &vmstate_pvscsi;
+ device_class_set_props(dc, pvscsi_properties);
+ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+ hc->unplug = pvscsi_hot_unplug;
+ hc->plug = pvscsi_hotplug;
+}
+
+static const TypeInfo pvscsi_info = {
+ .name = TYPE_PVSCSI,
+ .parent = TYPE_PCI_DEVICE,
+ .class_size = sizeof(PVSCSIClass),
+ .instance_size = sizeof(PVSCSIState),
+ .class_init = pvscsi_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { INTERFACE_PCIE_DEVICE },
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { }
+ }
+};
+
+static void
+pvscsi_register_types(void)
+{
+ type_register_static(&pvscsi_info);
+}
+
+type_init(pvscsi_register_types);
diff --git a/hw/scsi/vmw_pvscsi.h b/hw/scsi/vmw_pvscsi.h
new file mode 100644
index 00000000..17fcf662
--- /dev/null
+++ b/hw/scsi/vmw_pvscsi.h
@@ -0,0 +1,434 @@
+/*
+ * VMware PVSCSI header file
+ *
+ * Copyright (C) 2008-2009, VMware, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; version 2 of the License and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Maintained by: Arvind Kumar <arvindkumar@vmware.com>
+ *
+ */
+
+#ifndef VMW_PVSCSI_H
+#define VMW_PVSCSI_H
+
+#define VMW_PAGE_SIZE (4096)
+#define VMW_PAGE_SHIFT (12)
+
+#define MASK(n) ((1 << (n)) - 1) /* make an n-bit mask */
+
+/*
+ * host adapter status/error codes
+ */
+enum HostBusAdapterStatus {
+ BTSTAT_SUCCESS = 0x00, /* CCB complete normally with no errors */
+ BTSTAT_LINKED_COMMAND_COMPLETED = 0x0a,
+ BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG = 0x0b,
+ BTSTAT_DATA_UNDERRUN = 0x0c,
+ BTSTAT_SELTIMEO = 0x11, /* SCSI selection timeout */
+ BTSTAT_DATARUN = 0x12, /* data overrun/underrun */
+ BTSTAT_BUSFREE = 0x13, /* unexpected bus free */
+ BTSTAT_INVPHASE = 0x14, /* invalid bus phase or sequence */
+ /* requested by target */
+ BTSTAT_LUNMISMATCH = 0x17, /* linked CCB has different LUN */
+ /* from first CCB */
+ BTSTAT_SENSFAILED = 0x1b, /* auto request sense failed */
+ BTSTAT_TAGREJECT = 0x1c, /* SCSI II tagged queueing message */
+ /* rejected by target */
+ BTSTAT_BADMSG = 0x1d, /* unsupported message received by */
+ /* the host adapter */
+ BTSTAT_HAHARDWARE = 0x20, /* host adapter hardware failed */
+ BTSTAT_NORESPONSE = 0x21, /* target did not respond to SCSI ATN, */
+ /* sent a SCSI RST */
+ BTSTAT_SENTRST = 0x22, /* host adapter asserted a SCSI RST */
+ BTSTAT_RECVRST = 0x23, /* other SCSI devices asserted a SCSI RST */
+ BTSTAT_DISCONNECT = 0x24, /* target device reconnected improperly */
+ /* (w/o tag) */
+ BTSTAT_BUSRESET = 0x25, /* host adapter issued BUS device reset */
+ BTSTAT_ABORTQUEUE = 0x26, /* abort queue generated */
+ BTSTAT_HASOFTWARE = 0x27, /* host adapter software error */
+ BTSTAT_HATIMEOUT = 0x30, /* host adapter hardware timeout error */
+ BTSTAT_SCSIPARITY = 0x34, /* SCSI parity error detected */
+};
+
+/*
+ * Register offsets.
+ *
+ * These registers are accessible both via i/o space and mm i/o.
+ */
+
+enum PVSCSIRegOffset {
+ PVSCSI_REG_OFFSET_COMMAND = 0x0,
+ PVSCSI_REG_OFFSET_COMMAND_DATA = 0x4,
+ PVSCSI_REG_OFFSET_COMMAND_STATUS = 0x8,
+ PVSCSI_REG_OFFSET_LAST_STS_0 = 0x100,
+ PVSCSI_REG_OFFSET_LAST_STS_1 = 0x104,
+ PVSCSI_REG_OFFSET_LAST_STS_2 = 0x108,
+ PVSCSI_REG_OFFSET_LAST_STS_3 = 0x10c,
+ PVSCSI_REG_OFFSET_INTR_STATUS = 0x100c,
+ PVSCSI_REG_OFFSET_INTR_MASK = 0x2010,
+ PVSCSI_REG_OFFSET_KICK_NON_RW_IO = 0x3014,
+ PVSCSI_REG_OFFSET_DEBUG = 0x3018,
+ PVSCSI_REG_OFFSET_KICK_RW_IO = 0x4018,
+};
+
+/*
+ * Virtual h/w commands.
+ */
+
+enum PVSCSICommands {
+ PVSCSI_CMD_FIRST = 0, /* has to be first */
+
+ PVSCSI_CMD_ADAPTER_RESET = 1,
+ PVSCSI_CMD_ISSUE_SCSI = 2,
+ PVSCSI_CMD_SETUP_RINGS = 3,
+ PVSCSI_CMD_RESET_BUS = 4,
+ PVSCSI_CMD_RESET_DEVICE = 5,
+ PVSCSI_CMD_ABORT_CMD = 6,
+ PVSCSI_CMD_CONFIG = 7,
+ PVSCSI_CMD_SETUP_MSG_RING = 8,
+ PVSCSI_CMD_DEVICE_UNPLUG = 9,
+
+ PVSCSI_CMD_LAST = 10 /* has to be last */
+};
+
+#define PVSCSI_COMMAND_PROCESSING_SUCCEEDED (0)
+#define PVSCSI_COMMAND_PROCESSING_FAILED (-1)
+#define PVSCSI_COMMAND_NOT_ENOUGH_DATA (-2)
+
+/*
+ * Command descriptor for PVSCSI_CMD_RESET_DEVICE --
+ */
+
+struct PVSCSICmdDescResetDevice {
+ uint32_t target;
+ uint8_t lun[8];
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescResetDevice PVSCSICmdDescResetDevice;
+
+/*
+ * Command descriptor for PVSCSI_CMD_ABORT_CMD --
+ *
+ * - currently does not support specifying the LUN.
+ * - pad should be 0.
+ */
+
+struct PVSCSICmdDescAbortCmd {
+ uint64_t context;
+ uint32_t target;
+ uint32_t pad;
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescAbortCmd PVSCSICmdDescAbortCmd;
+
+/*
+ * Command descriptor for PVSCSI_CMD_SETUP_RINGS --
+ *
+ * Notes:
+ * - reqRingNumPages and cmpRingNumPages need to be power of two.
+ * - reqRingNumPages and cmpRingNumPages need to be different from 0,
+ * - reqRingNumPages and cmpRingNumPages need to be inferior to
+ * PVSCSI_SETUP_RINGS_MAX_NUM_PAGES.
+ */
+
+#define PVSCSI_SETUP_RINGS_MAX_NUM_PAGES 32
+struct PVSCSICmdDescSetupRings {
+ uint32_t reqRingNumPages;
+ uint32_t cmpRingNumPages;
+ uint64_t ringsStatePPN;
+ uint64_t reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ uint64_t cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescSetupRings PVSCSICmdDescSetupRings;
+
+/*
+ * Command descriptor for PVSCSI_CMD_SETUP_MSG_RING --
+ *
+ * Notes:
+ * - this command was not supported in the initial revision of the h/w
+ * interface. Before using it, you need to check that it is supported by
+ * writing PVSCSI_CMD_SETUP_MSG_RING to the 'command' register, then
+ * immediately after read the 'command status' register:
+ * * a value of -1 means that the cmd is NOT supported,
+ * * a value != -1 means that the cmd IS supported.
+ * If it's supported the 'command status' register should return:
+ * sizeof(PVSCSICmdDescSetupMsgRing) / sizeof(uint32_t).
+ * - this command should be issued _after_ the usual SETUP_RINGS so that the
+ * RingsState page is already setup. If not, the command is a nop.
+ * - numPages needs to be a power of two,
+ * - numPages needs to be different from 0,
+ * - pad should be zero.
+ */
+
+#define PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES 16
+
+struct PVSCSICmdDescSetupMsgRing {
+ uint32_t numPages;
+ uint32_t pad;
+ uint64_t ringPPNs[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
+} QEMU_PACKED;
+
+typedef struct PVSCSICmdDescSetupMsgRing PVSCSICmdDescSetupMsgRing;
+
+enum PVSCSIMsgType {
+ PVSCSI_MSG_DEV_ADDED = 0,
+ PVSCSI_MSG_DEV_REMOVED = 1,
+ PVSCSI_MSG_LAST = 2,
+};
+
+/*
+ * Msg descriptor.
+ *
+ * sizeof(struct PVSCSIRingMsgDesc) == 128.
+ *
+ * - type is of type enum PVSCSIMsgType.
+ * - the content of args depend on the type of event being delivered.
+ */
+
+struct PVSCSIRingMsgDesc {
+ uint32_t type;
+ uint32_t args[31];
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingMsgDesc PVSCSIRingMsgDesc;
+
+struct PVSCSIMsgDescDevStatusChanged {
+ uint32_t type; /* PVSCSI_MSG_DEV _ADDED / _REMOVED */
+ uint32_t bus;
+ uint32_t target;
+ uint8_t lun[8];
+ uint32_t pad[27];
+} QEMU_PACKED;
+
+typedef struct PVSCSIMsgDescDevStatusChanged PVSCSIMsgDescDevStatusChanged;
+
+/*
+ * Rings state.
+ *
+ * - the fields:
+ * . msgProdIdx,
+ * . msgConsIdx,
+ * . msgNumEntriesLog2,
+ * .. are only used once the SETUP_MSG_RING cmd has been issued.
+ * - 'pad' helps to ensure that the msg related fields are on their own
+ * cache-line.
+ */
+
+struct PVSCSIRingsState {
+ uint32_t reqProdIdx;
+ uint32_t reqConsIdx;
+ uint32_t reqNumEntriesLog2;
+
+ uint32_t cmpProdIdx;
+ uint32_t cmpConsIdx;
+ uint32_t cmpNumEntriesLog2;
+
+ uint8_t pad[104];
+
+ uint32_t msgProdIdx;
+ uint32_t msgConsIdx;
+ uint32_t msgNumEntriesLog2;
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingsState PVSCSIRingsState;
+
+/*
+ * Request descriptor.
+ *
+ * sizeof(RingReqDesc) = 128
+ *
+ * - context: is a unique identifier of a command. It could normally be any
+ * 64bit value, however we currently store it in the serialNumber variable
+ * of struct SCSI_Command, so we have the following restrictions due to the
+ * way this field is handled in the vmkernel storage stack:
+ * * this value can't be 0,
+ * * the upper 32bit need to be 0 since serialNumber is as a uint32_t.
+ * Currently tracked as PR 292060.
+ * - dataLen: contains the total number of bytes that need to be transferred.
+ * - dataAddr:
+ * * if PVSCSI_FLAG_CMD_WITH_SG_LIST is set: dataAddr is the PA of the first
+ * s/g table segment, each s/g segment is entirely contained on a single
+ * page of physical memory,
+ * * if PVSCSI_FLAG_CMD_WITH_SG_LIST is NOT set, then dataAddr is the PA of
+ * the buffer used for the DMA transfer,
+ * - flags:
+ * * PVSCSI_FLAG_CMD_WITH_SG_LIST: see dataAddr above,
+ * * PVSCSI_FLAG_CMD_DIR_NONE: no DMA involved,
+ * * PVSCSI_FLAG_CMD_DIR_TOHOST: transfer from device to main memory,
+ * * PVSCSI_FLAG_CMD_DIR_TODEVICE: transfer from main memory to device,
+ * * PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB: reserved to handle CDBs larger than
+ * 16bytes. To be specified.
+ * - vcpuHint: vcpuId of the processor that will be most likely waiting for the
+ * completion of the i/o. For guest OSes that use lowest priority message
+ * delivery mode (such as windows), we use this "hint" to deliver the
+ * completion action to the proper vcpu. For now, we can use the vcpuId of
+ * the processor that initiated the i/o as a likely candidate for the vcpu
+ * that will be waiting for the completion..
+ * - bus should be 0: we currently only support bus 0 for now.
+ * - unused should be zero'd.
+ */
+
+#define PVSCSI_FLAG_CMD_WITH_SG_LIST (1 << 0)
+#define PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB (1 << 1)
+#define PVSCSI_FLAG_CMD_DIR_NONE (1 << 2)
+#define PVSCSI_FLAG_CMD_DIR_TOHOST (1 << 3)
+#define PVSCSI_FLAG_CMD_DIR_TODEVICE (1 << 4)
+
+#define PVSCSI_KNOWN_FLAGS \
+ (PVSCSI_FLAG_CMD_WITH_SG_LIST | \
+ PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB | \
+ PVSCSI_FLAG_CMD_DIR_NONE | \
+ PVSCSI_FLAG_CMD_DIR_TOHOST | \
+ PVSCSI_FLAG_CMD_DIR_TODEVICE)
+
+struct PVSCSIRingReqDesc {
+ uint64_t context;
+ uint64_t dataAddr;
+ uint64_t dataLen;
+ uint64_t senseAddr;
+ uint32_t senseLen;
+ uint32_t flags;
+ uint8_t cdb[16];
+ uint8_t cdbLen;
+ uint8_t lun[8];
+ uint8_t tag;
+ uint8_t bus;
+ uint8_t target;
+ uint8_t vcpuHint;
+ uint8_t unused[59];
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingReqDesc PVSCSIRingReqDesc;
+
+/*
+ * Scatter-gather list management.
+ *
+ * As described above, when PVSCSI_FLAG_CMD_WITH_SG_LIST is set in the
+ * RingReqDesc.flags, then RingReqDesc.dataAddr is the PA of the first s/g
+ * table segment.
+ *
+ * - each segment of the s/g table contain a succession of struct
+ * PVSCSISGElement.
+ * - each segment is entirely contained on a single physical page of memory.
+ * - a "chain" s/g element has the flag PVSCSI_SGE_FLAG_CHAIN_ELEMENT set in
+ * PVSCSISGElement.flags and in this case:
+ * * addr is the PA of the next s/g segment,
+ * * length is undefined, assumed to be 0.
+ */
+
+struct PVSCSISGElement {
+ uint64_t addr;
+ uint32_t length;
+ uint32_t flags;
+} QEMU_PACKED;
+
+typedef struct PVSCSISGElement PVSCSISGElement;
+
+/*
+ * Completion descriptor.
+ *
+ * sizeof(RingCmpDesc) = 32
+ *
+ * - context: identifier of the command. The same thing that was specified
+ * under "context" as part of struct RingReqDesc at initiation time,
+ * - dataLen: number of bytes transferred for the actual i/o operation,
+ * - senseLen: number of bytes written into the sense buffer,
+ * - hostStatus: adapter status,
+ * - scsiStatus: device status,
+ * - pad should be zero.
+ */
+
+struct PVSCSIRingCmpDesc {
+ uint64_t context;
+ uint64_t dataLen;
+ uint32_t senseLen;
+ uint16_t hostStatus;
+ uint16_t scsiStatus;
+ uint32_t pad[2];
+} QEMU_PACKED;
+
+typedef struct PVSCSIRingCmpDesc PVSCSIRingCmpDesc;
+
+/*
+ * Interrupt status / IRQ bits.
+ */
+
+#define PVSCSI_INTR_CMPL_0 (1 << 0)
+#define PVSCSI_INTR_CMPL_1 (1 << 1)
+#define PVSCSI_INTR_CMPL_MASK MASK(2)
+
+#define PVSCSI_INTR_MSG_0 (1 << 2)
+#define PVSCSI_INTR_MSG_1 (1 << 3)
+#define PVSCSI_INTR_MSG_MASK (MASK(2) << 2)
+
+#define PVSCSI_INTR_ALL_SUPPORTED MASK(4)
+
+/*
+ * Number of MSI-X vectors supported.
+ */
+#define PVSCSI_MAX_INTRS 24
+
+/*
+ * Enumeration of supported MSI-X vectors
+ */
+#define PVSCSI_VECTOR_COMPLETION 0
+
+/*
+ * Misc constants for the rings.
+ */
+
+#define PVSCSI_MAX_NUM_PAGES_REQ_RING PVSCSI_SETUP_RINGS_MAX_NUM_PAGES
+#define PVSCSI_MAX_NUM_PAGES_CMP_RING PVSCSI_SETUP_RINGS_MAX_NUM_PAGES
+#define PVSCSI_MAX_NUM_PAGES_MSG_RING PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES
+
+#define PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE \
+ (VMW_PAGE_SIZE / sizeof(struct PVSCSIRingReqDesc))
+
+#define PVSCSI_MAX_NUM_CMP_ENTRIES_PER_PAGE \
+ (VMW_PAGE_SIZE / sizeof(PVSCSIRingCmpDesc))
+
+#define PVSCSI_MAX_NUM_MSG_ENTRIES_PER_PAGE \
+ (VMW_PAGE_SIZE / sizeof(PVSCSIRingMsgDesc))
+
+#define PVSCSI_MAX_REQ_QUEUE_DEPTH \
+ (PVSCSI_MAX_NUM_PAGES_REQ_RING * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE)
+
+#define PVSCSI_MEM_SPACE_COMMAND_NUM_PAGES 1
+#define PVSCSI_MEM_SPACE_INTR_STATUS_NUM_PAGES 1
+#define PVSCSI_MEM_SPACE_MISC_NUM_PAGES 2
+#define PVSCSI_MEM_SPACE_KICK_IO_NUM_PAGES 2
+#define PVSCSI_MEM_SPACE_MSIX_NUM_PAGES 2
+
+enum PVSCSIMemSpace {
+ PVSCSI_MEM_SPACE_COMMAND_PAGE = 0,
+ PVSCSI_MEM_SPACE_INTR_STATUS_PAGE = 1,
+ PVSCSI_MEM_SPACE_MISC_PAGE = 2,
+ PVSCSI_MEM_SPACE_KICK_IO_PAGE = 4,
+ PVSCSI_MEM_SPACE_MSIX_TABLE_PAGE = 6,
+ PVSCSI_MEM_SPACE_MSIX_PBA_PAGE = 7,
+};
+
+#define PVSCSI_MEM_SPACE_NUM_PAGES \
+ (PVSCSI_MEM_SPACE_COMMAND_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_INTR_STATUS_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_MISC_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_KICK_IO_NUM_PAGES + \
+ PVSCSI_MEM_SPACE_MSIX_NUM_PAGES)
+
+#define PVSCSI_MEM_SPACE_SIZE (PVSCSI_MEM_SPACE_NUM_PAGES * VMW_PAGE_SIZE)
+
+#endif /* VMW_PVSCSI_H */