summaryrefslogtreecommitdiff
path: root/roms/seabios/src/hw/pvscsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/seabios/src/hw/pvscsi.c')
-rw-r--r--roms/seabios/src/hw/pvscsi.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/roms/seabios/src/hw/pvscsi.c b/roms/seabios/src/hw/pvscsi.c
new file mode 100644
index 000000000..6911230cb
--- /dev/null
+++ b/roms/seabios/src/hw/pvscsi.c
@@ -0,0 +1,364 @@
+// QEMU VMWARE Paravirtualized SCSI boot support.
+//
+// Copyright (c) 2013 Ravello Systems LTD (http://ravellosystems.com)
+//
+// Authors:
+// Evgeny Budilovsky <evgeny.budilovsky@ravellosystems.com>
+//
+// This file may be distributed under the terms of the GNU LGPLv3 license.
+
+#include "biosvar.h" // GET_GLOBALFLAT
+#include "block.h" // struct drive_s
+#include "blockcmd.h" // scsi_drive_setup
+#include "config.h" // CONFIG_*
+#include "malloc.h" // free
+#include "output.h" // dprintf
+#include "pci.h" // foreachpci
+#include "pci_ids.h" // PCI_DEVICE_ID_VMWARE_PVSCSI
+#include "pci_regs.h" // PCI_VENDOR_ID
+#include "std/disk.h" // DISK_RET_SUCCESS
+#include "string.h" // memset
+#include "util.h" // usleep
+#include "pvscsi.h"
+#include "virtio-ring.h" // PAGE_SHIFT, virt_to_phys
+
+#define MASK(n) ((1 << (n)) - 1)
+
+#define SIMPLE_QUEUE_TAG 0x20
+
+#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)
+
+#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)
+
+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,
+};
+
+enum PVSCSICommands {
+ PVSCSI_CMD_FIRST = 0,
+ 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
+};
+
+#define PVSCSI_SETUP_RINGS_MAX_NUM_PAGES 32
+struct PVSCSICmdDescSetupRings {
+ u32 reqRingNumPages;
+ u32 cmpRingNumPages;
+ u64 ringsStatePPN;
+ u64 reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ u64 cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+} PACKED;
+
+struct PVSCSIRingCmpDesc {
+ u64 context;
+ u64 dataLen;
+ u32 senseLen;
+ u16 hostStatus;
+ u16 scsiStatus;
+ u32 pad[2];
+} PACKED;
+
+struct PVSCSIRingsState {
+ u32 reqProdIdx;
+ u32 reqConsIdx;
+ u32 reqNumEntriesLog2;
+
+ u32 cmpProdIdx;
+ u32 cmpConsIdx;
+ u32 cmpNumEntriesLog2;
+
+ u8 pad[104];
+
+ u32 msgProdIdx;
+ u32 msgConsIdx;
+ u32 msgNumEntriesLog2;
+} PACKED;
+
+struct PVSCSIRingReqDesc {
+ u64 context;
+ u64 dataAddr;
+ u64 dataLen;
+ u64 senseAddr;
+ u32 senseLen;
+ u32 flags;
+ u8 cdb[16];
+ u8 cdbLen;
+ u8 lun[8];
+ u8 tag;
+ u8 bus;
+ u8 target;
+ u8 vcpuHint;
+ u8 unused[59];
+} PACKED;
+
+struct pvscsi_ring_dsc_s {
+ struct PVSCSIRingsState *ring_state;
+ struct PVSCSIRingReqDesc *ring_reqs;
+ struct PVSCSIRingCmpDesc *ring_cmps;
+};
+
+struct pvscsi_lun_s {
+ struct drive_s drive;
+ struct pci_device *pci;
+ u32 iobase;
+ u8 target;
+ u8 lun;
+ struct pvscsi_ring_dsc_s *ring_dsc;
+};
+
+static void
+pvscsi_write_cmd_desc(u32 iobase, u32 cmd, const void *desc, size_t len)
+{
+ const u32 *ptr = desc;
+ size_t i;
+
+ len /= sizeof(*ptr);
+ pci_writel(iobase + PVSCSI_REG_OFFSET_COMMAND, cmd);
+ for (i = 0; i < len; i++)
+ pci_writel(iobase + PVSCSI_REG_OFFSET_COMMAND_DATA, ptr[i]);
+}
+
+static void
+pvscsi_kick_rw_io(u32 iobase)
+{
+ pci_writel(iobase + PVSCSI_REG_OFFSET_KICK_RW_IO, 0);
+}
+
+static void
+pvscsi_wait_intr_cmpl(u32 iobase)
+{
+ while (!(pci_readl(iobase + PVSCSI_REG_OFFSET_INTR_STATUS) & PVSCSI_INTR_CMPL_MASK))
+ usleep(5);
+ pci_writel(iobase + PVSCSI_REG_OFFSET_INTR_STATUS, PVSCSI_INTR_CMPL_MASK);
+
+}
+
+static void
+pvscsi_init_rings(u32 iobase, struct pvscsi_ring_dsc_s **ring_dsc)
+{
+ struct PVSCSICmdDescSetupRings cmd = {0,};
+
+ struct pvscsi_ring_dsc_s *dsc = memalign_low(sizeof(*dsc), PAGE_SIZE);
+ if (!dsc) {
+ warn_noalloc();
+ return;
+ }
+
+ dsc->ring_state =
+ (struct PVSCSIRingsState *)memalign_low(PAGE_SIZE, PAGE_SIZE);
+ dsc->ring_reqs =
+ (struct PVSCSIRingReqDesc *)memalign_low(PAGE_SIZE, PAGE_SIZE);
+ dsc->ring_cmps =
+ (struct PVSCSIRingCmpDesc *)memalign_low(PAGE_SIZE, PAGE_SIZE);
+ if (!dsc->ring_state || !dsc->ring_reqs || !dsc->ring_cmps) {
+ warn_noalloc();
+ return;
+ }
+ memset(dsc->ring_state, 0, PAGE_SIZE);
+ memset(dsc->ring_reqs, 0, PAGE_SIZE);
+ memset(dsc->ring_cmps, 0, PAGE_SIZE);
+
+ cmd.reqRingNumPages = 1;
+ cmd.cmpRingNumPages = 1;
+ cmd.ringsStatePPN = virt_to_phys(dsc->ring_state) >> PAGE_SHIFT;
+ cmd.reqRingPPNs[0] = virt_to_phys(dsc->ring_reqs) >> PAGE_SHIFT;
+ cmd.cmpRingPPNs[0] = virt_to_phys(dsc->ring_cmps) >> PAGE_SHIFT;
+
+ pvscsi_write_cmd_desc(iobase, PVSCSI_CMD_SETUP_RINGS,
+ &cmd, sizeof(cmd));
+ *ring_dsc = dsc;
+}
+
+static void pvscsi_fill_req(struct PVSCSIRingsState *s,
+ struct PVSCSIRingReqDesc *req,
+ u16 target, u16 lun, void *cdbcmd, u16 blocksize,
+ struct disk_op_s *op)
+{
+ SET_LOWFLAT(req->bus, 0);
+ SET_LOWFLAT(req->target, target);
+ memset(LOWFLAT2LOW(&req->lun[0]), 0, sizeof(req->lun));
+ SET_LOWFLAT(req->lun[1], lun);
+ SET_LOWFLAT(req->senseLen, 0);
+ SET_LOWFLAT(req->senseAddr, 0);
+ SET_LOWFLAT(req->cdbLen, 16);
+ SET_LOWFLAT(req->vcpuHint, 0);
+ memcpy(LOWFLAT2LOW(&req->cdb[0]), cdbcmd, 16);
+ SET_LOWFLAT(req->tag, SIMPLE_QUEUE_TAG);
+ SET_LOWFLAT(req->flags,
+ cdb_is_read(cdbcmd, blocksize) ?
+ PVSCSI_FLAG_CMD_DIR_TOHOST : PVSCSI_FLAG_CMD_DIR_TODEVICE);
+
+ SET_LOWFLAT(req->dataLen, op->count * blocksize);
+ SET_LOWFLAT(req->dataAddr, (u32)op->buf_fl);
+ SET_LOWFLAT(s->reqProdIdx, GET_LOWFLAT(s->reqProdIdx) + 1);
+
+}
+
+static u32
+pvscsi_get_rsp(struct PVSCSIRingsState *s,
+ struct PVSCSIRingCmpDesc *rsp)
+{
+ u32 status = GET_LOWFLAT(rsp->hostStatus);
+ SET_LOWFLAT(s->cmpConsIdx, GET_LOWFLAT(s->cmpConsIdx)+1);
+ return status;
+}
+
+static int
+pvscsi_cmd(struct pvscsi_lun_s *plun_gf, struct disk_op_s *op,
+ void *cdbcmd, u16 target, u16 lun, u16 blocksize)
+{
+ struct pvscsi_ring_dsc_s *ring_dsc = GET_GLOBALFLAT(plun_gf->ring_dsc);
+ struct PVSCSIRingsState *s = GET_LOWFLAT(ring_dsc->ring_state);
+ u32 req_entries = GET_LOWFLAT(s->reqNumEntriesLog2);
+ u32 cmp_entries = GET_LOWFLAT(s->cmpNumEntriesLog2);
+ struct PVSCSIRingReqDesc *req;
+ struct PVSCSIRingCmpDesc *rsp;
+ u32 status;
+
+ if (GET_LOWFLAT(s->reqProdIdx) - GET_LOWFLAT(s->cmpConsIdx) >= 1 << req_entries) {
+ dprintf(1, "pvscsi: ring full: reqProdIdx=%d cmpConsIdx=%d\n",
+ GET_LOWFLAT(s->reqProdIdx), GET_LOWFLAT(s->cmpConsIdx));
+ return DISK_RET_EBADTRACK;
+ }
+
+ req = GET_LOWFLAT(ring_dsc->ring_reqs) + (GET_LOWFLAT(s->reqProdIdx) & MASK(req_entries));
+ pvscsi_fill_req(s, req, target, lun, cdbcmd, blocksize, op);
+
+ pvscsi_kick_rw_io(GET_GLOBALFLAT(plun_gf->iobase));
+ pvscsi_wait_intr_cmpl(GET_GLOBALFLAT(plun_gf->iobase));
+
+ rsp = GET_LOWFLAT(ring_dsc->ring_cmps) + (GET_LOWFLAT(s->cmpConsIdx) & MASK(cmp_entries));
+ status = pvscsi_get_rsp(s, rsp);
+
+ return status == 0 ? DISK_RET_SUCCESS : DISK_RET_EBADTRACK;
+}
+
+int
+pvscsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
+{
+ if (!CONFIG_PVSCSI)
+ return DISK_RET_EBADTRACK;
+
+ struct pvscsi_lun_s *plun_gf =
+ container_of(op->drive_gf, struct pvscsi_lun_s, drive);
+
+ return pvscsi_cmd(plun_gf, op, cdbcmd,
+ GET_GLOBALFLAT(plun_gf->target),
+ GET_GLOBALFLAT(plun_gf->lun),
+ blocksize);
+
+}
+
+static int
+pvscsi_add_lun(struct pci_device *pci, u32 iobase,
+ struct pvscsi_ring_dsc_s *ring_dsc, u8 target, u8 lun)
+{
+ struct pvscsi_lun_s *plun = malloc_fseg(sizeof(*plun));
+ if (!plun) {
+ warn_noalloc();
+ return -1;
+ }
+ memset(plun, 0, sizeof(*plun));
+ plun->drive.type = DTYPE_PVSCSI;
+ plun->drive.cntl_id = pci->bdf;
+ plun->pci = pci;
+ plun->target = target;
+ plun->lun = lun;
+ plun->iobase = iobase;
+ plun->ring_dsc = ring_dsc;
+
+ char *name = znprintf(16, "pvscsi %02x:%02x.%x %d:%d",
+ pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf),
+ pci_bdf_to_fn(pci->bdf), target, lun);
+ int prio = bootprio_find_scsi_device(pci, target, lun);
+ int ret = scsi_drive_setup(&plun->drive, name, prio);
+ free(name);
+ if (ret)
+ goto fail;
+ return 0;
+
+fail:
+ free(plun);
+ return -1;
+}
+
+static void
+pvscsi_scan_target(struct pci_device *pci, u32 iobase,
+ struct pvscsi_ring_dsc_s *ring_dsc, u8 target)
+{
+ /* TODO: send REPORT LUNS. For now, only LUN 0 is recognized. */
+ pvscsi_add_lun(pci, iobase, ring_dsc, target, 0);
+}
+
+static void
+init_pvscsi(struct pci_device *pci)
+{
+ struct pvscsi_ring_dsc_s *ring_dsc = NULL;
+ int i;
+ u16 bdf = pci->bdf;
+ u32 iobase = pci_config_readl(pci->bdf, PCI_BASE_ADDRESS_0)
+ & PCI_BASE_ADDRESS_MEM_MASK;
+
+ pci_config_maskw(bdf, PCI_COMMAND, 0, PCI_COMMAND_MASTER);
+
+ dprintf(1, "found pvscsi at %02x:%02x.%x, io @ %x\n",
+ pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf),
+ pci_bdf_to_fn(bdf), iobase);
+
+ pvscsi_write_cmd_desc(iobase, PVSCSI_CMD_ADAPTER_RESET, NULL, 0);
+
+ pvscsi_init_rings(iobase, &ring_dsc);
+ for (i = 0; i < 7; i++)
+ pvscsi_scan_target(pci, iobase, ring_dsc, i);
+
+ return;
+
+}
+
+void
+pvscsi_setup(void)
+{
+ ASSERT32FLAT();
+ if (! CONFIG_PVSCSI)
+ return;
+
+ dprintf(3, "init pvscsi\n");
+
+ struct pci_device *pci;
+ foreachpci(pci) {
+ if (pci->vendor != PCI_VENDOR_ID_VMWARE
+ || pci->device != PCI_DEVICE_ID_VMWARE_PVSCSI)
+ continue;
+ init_pvscsi(pci);
+ }
+}