335 lines
9.3 KiB
C
335 lines
9.3 KiB
C
// 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 "block.h" // struct drive_s
|
|
#include "blockcmd.h" // scsi_drive_setup
|
|
#include "config.h" // CONFIG_*
|
|
#include "malloc.h" // free
|
|
#include "memmap.h" // PAGE_SHIFT, virt_to_phys
|
|
#include "output.h" // dprintf
|
|
#include "pcidevice.h" // foreachpci
|
|
#include "pci_ids.h" // PCI_DEVICE_ID_VMWARE_PVSCSI
|
|
#include "pci_regs.h" // PCI_VENDOR_ID
|
|
#include "pvscsi.h" // pvscsi_setup
|
|
#include "stacks.h" // run_thread
|
|
#include "std/disk.h" // DISK_RET_SUCCESS
|
|
#include "string.h" // memset
|
|
#include "util.h" // usleep
|
|
#include "x86.h" // writel
|
|
|
|
#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;
|
|
void *iobase;
|
|
u8 target;
|
|
u8 lun;
|
|
struct pvscsi_ring_dsc_s *ring_dsc;
|
|
};
|
|
|
|
static void
|
|
pvscsi_write_cmd_desc(void *iobase, u32 cmd, const void *desc, size_t len)
|
|
{
|
|
const u32 *ptr = desc;
|
|
size_t i;
|
|
|
|
len /= sizeof(*ptr);
|
|
writel(iobase + PVSCSI_REG_OFFSET_COMMAND, cmd);
|
|
for (i = 0; i < len; i++)
|
|
writel(iobase + PVSCSI_REG_OFFSET_COMMAND_DATA, ptr[i]);
|
|
}
|
|
|
|
static void
|
|
pvscsi_kick_rw_io(void *iobase)
|
|
{
|
|
writel(iobase + PVSCSI_REG_OFFSET_KICK_RW_IO, 0);
|
|
}
|
|
|
|
static void
|
|
pvscsi_wait_intr_cmpl(void *iobase)
|
|
{
|
|
while (!(readl(iobase + PVSCSI_REG_OFFSET_INTR_STATUS) & PVSCSI_INTR_CMPL_MASK))
|
|
usleep(5);
|
|
writel(iobase + PVSCSI_REG_OFFSET_INTR_STATUS, PVSCSI_INTR_CMPL_MASK);
|
|
}
|
|
|
|
static void
|
|
pvscsi_init_rings(void *iobase, struct pvscsi_ring_dsc_s **ring_dsc)
|
|
{
|
|
struct PVSCSICmdDescSetupRings cmd = {0,};
|
|
|
|
struct pvscsi_ring_dsc_s *dsc = malloc_high(sizeof(*dsc));
|
|
if (!dsc) {
|
|
warn_noalloc();
|
|
return;
|
|
}
|
|
|
|
dsc->ring_state =
|
|
(struct PVSCSIRingsState *)memalign_high(PAGE_SIZE, PAGE_SIZE);
|
|
dsc->ring_reqs =
|
|
(struct PVSCSIRingReqDesc *)memalign_high(PAGE_SIZE, PAGE_SIZE);
|
|
dsc->ring_cmps =
|
|
(struct PVSCSIRingCmpDesc *)memalign_high(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 u32
|
|
pvscsi_get_rsp(struct PVSCSIRingsState *s,
|
|
struct PVSCSIRingCmpDesc *rsp)
|
|
{
|
|
u32 status = rsp->hostStatus;
|
|
s->cmpConsIdx = s->cmpConsIdx + 1;
|
|
return status;
|
|
}
|
|
|
|
int
|
|
pvscsi_process_op(struct disk_op_s *op)
|
|
{
|
|
if (!CONFIG_PVSCSI)
|
|
return DISK_RET_EBADTRACK;
|
|
struct pvscsi_lun_s *plun =
|
|
container_of(op->drive_fl, struct pvscsi_lun_s, drive);
|
|
struct pvscsi_ring_dsc_s *ring_dsc = plun->ring_dsc;
|
|
struct PVSCSIRingsState *s = ring_dsc->ring_state;
|
|
u32 req_entries = s->reqNumEntriesLog2;
|
|
u32 cmp_entries = s->cmpNumEntriesLog2;
|
|
struct PVSCSIRingReqDesc *req;
|
|
struct PVSCSIRingCmpDesc *rsp;
|
|
u32 status;
|
|
|
|
if (s->reqProdIdx - s->cmpConsIdx >= 1 << req_entries) {
|
|
dprintf(1, "pvscsi: ring full: reqProdIdx=%d cmpConsIdx=%d\n",
|
|
s->reqProdIdx, s->cmpConsIdx);
|
|
return DISK_RET_EBADTRACK;
|
|
}
|
|
|
|
req = ring_dsc->ring_reqs + (s->reqProdIdx & MASK(req_entries));
|
|
int blocksize = scsi_fill_cmd(op, req->cdb, 16);
|
|
if (blocksize < 0)
|
|
return default_process_op(op);
|
|
req->bus = 0;
|
|
req->target = plun->target;
|
|
memset(req->lun, 0, sizeof(req->lun));
|
|
req->lun[1] = plun->lun;
|
|
req->senseLen = 0;
|
|
req->senseAddr = 0;
|
|
req->cdbLen = 16;
|
|
req->vcpuHint = 0;
|
|
req->tag = SIMPLE_QUEUE_TAG;
|
|
req->flags = scsi_is_read(op) ?
|
|
PVSCSI_FLAG_CMD_DIR_TOHOST : PVSCSI_FLAG_CMD_DIR_TODEVICE;
|
|
req->dataLen = op->count * blocksize;
|
|
req->dataAddr = (u32)op->buf_fl;
|
|
s->reqProdIdx = s->reqProdIdx + 1;
|
|
|
|
pvscsi_kick_rw_io(plun->iobase);
|
|
pvscsi_wait_intr_cmpl(plun->iobase);
|
|
|
|
rsp = ring_dsc->ring_cmps + (s->cmpConsIdx & MASK(cmp_entries));
|
|
status = pvscsi_get_rsp(s, rsp);
|
|
|
|
return status == 0 ? DISK_RET_SUCCESS : DISK_RET_EBADTRACK;
|
|
}
|
|
|
|
static int
|
|
pvscsi_add_lun(struct pci_device *pci, void *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->target = target;
|
|
plun->lun = lun;
|
|
plun->iobase = iobase;
|
|
plun->ring_dsc = ring_dsc;
|
|
|
|
boot_lchs_find_scsi_device(pci, target, lun, &(plun->drive.lchs));
|
|
char *name = znprintf(MAXDESCSIZE, "pvscsi %pP %d:%d", pci, 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, void *iobase,
|
|
struct pvscsi_ring_dsc_s *ring_dsc, u8 target)
|
|
{
|
|
/* pvscsi has no more than a single lun per target */
|
|
pvscsi_add_lun(pci, iobase, ring_dsc, target, 0);
|
|
}
|
|
|
|
static void
|
|
init_pvscsi(void *data)
|
|
{
|
|
struct pci_device *pci = data;
|
|
void *iobase = pci_enable_membar(pci, PCI_BASE_ADDRESS_0);
|
|
if (!iobase)
|
|
return;
|
|
pci_enable_busmaster(pci);
|
|
|
|
dprintf(1, "found pvscsi at %pP, io @ %p\n", pci, iobase);
|
|
|
|
pvscsi_write_cmd_desc(iobase, PVSCSI_CMD_ADAPTER_RESET, NULL, 0);
|
|
|
|
struct pvscsi_ring_dsc_s *ring_dsc = NULL;
|
|
pvscsi_init_rings(iobase, &ring_dsc);
|
|
int i;
|
|
for (i = 0; i < 64; i++)
|
|
pvscsi_scan_target(pci, iobase, ring_dsc, i);
|
|
}
|
|
|
|
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;
|
|
run_thread(init_pvscsi, pci);
|
|
}
|
|
}
|