diff options
Diffstat (limited to 'hw/scsi')
-rw-r--r-- | hw/scsi/Kconfig | 60 | ||||
-rw-r--r-- | hw/scsi/emulation.c | 42 | ||||
-rw-r--r-- | hw/scsi/esp-pci.c | 564 | ||||
-rw-r--r-- | hw/scsi/esp.c | 1515 | ||||
-rw-r--r-- | hw/scsi/lsi53c895a.c | 2376 | ||||
-rw-r--r-- | hw/scsi/megasas.c | 2589 | ||||
-rw-r--r-- | hw/scsi/meson.build | 26 | ||||
-rw-r--r-- | hw/scsi/mfi.h | 1272 | ||||
-rw-r--r-- | hw/scsi/mpi.h | 1153 | ||||
-rw-r--r-- | hw/scsi/mptconfig.c | 904 | ||||
-rw-r--r-- | hw/scsi/mptendian.c | 205 | ||||
-rw-r--r-- | hw/scsi/mptsas.c | 1455 | ||||
-rw-r--r-- | hw/scsi/mptsas.h | 105 | ||||
-rw-r--r-- | hw/scsi/scsi-bus.c | 1887 | ||||
-rw-r--r-- | hw/scsi/scsi-disk.c | 3260 | ||||
-rw-r--r-- | hw/scsi/scsi-generic.c | 829 | ||||
-rw-r--r-- | hw/scsi/spapr_vscsi.c | 1301 | ||||
-rw-r--r-- | hw/scsi/srp.h | 247 | ||||
-rw-r--r-- | hw/scsi/trace-events | 357 | ||||
-rw-r--r-- | hw/scsi/trace.h | 1 | ||||
-rw-r--r-- | hw/scsi/vhost-scsi-common.c | 171 | ||||
-rw-r--r-- | hw/scsi/vhost-scsi.c | 349 | ||||
-rw-r--r-- | hw/scsi/vhost-user-scsi.c | 240 | ||||
-rw-r--r-- | hw/scsi/viosrp.h | 217 | ||||
-rw-r--r-- | hw/scsi/virtio-scsi-dataplane.c | 230 | ||||
-rw-r--r-- | hw/scsi/virtio-scsi.c | 1181 | ||||
-rw-r--r-- | hw/scsi/vmw_pvscsi.c | 1363 | ||||
-rw-r--r-- | hw/scsi/vmw_pvscsi.h | 434 |
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, ¬ifier->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, ¬ifier->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, ¬ifier->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, ¬ifier->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 */ |