2009-09-30 18:53:45

by Alok Kataria

[permalink] [raw]
Subject: SCSI driver for VMware's virtual HBA - V5.

Hi James,

Given that their are no more modification requests, I think this driver
should be ready for prime time now. I have incorporated the remaining
few outstanding comments while rebasing the patch for 32-rc1.

Please consider this for the SCSI tree. Also let me know if you have any
feedback. Thanks.

VMware PVSCSI driver - v5.

Changelog (v5-v4)
- use bool for pvscsi _disable_msi, _disable_msix, _use_msg
- fix for 16 byte CDBs, max_cmd_len should be initialized to 16.
- bump the driver version.
- add <[email protected]> in the MAINTAINERS file.
- rebase to 2.6.32-rc1.

Changelog (v4-v3)
- use size_t type for a loop iterator.
- use IS_ALIGNED instead of ~PAGE_MASK, for alignment checks
- use scsi_dma_ map/unmap API's
- reduce the pvscsi_info buffer to 256 bytes
- code cleanup in pvscsi_shutdown_intr

Changelog (v3-v2)
- use kmalloc for allocating sg_lists. And pci_map it for device access.
- re-use pdev->revision for setting up adapter->rev.

Changelog (v2-v1)
- use PCI_VDEVICE instead of PCI_DEVICE.
- use list_first_entry
- use parenthesis for every sizeof usage
- get rid of all #ifdef for CONFIG_PCI_MSI
- use PVSCSI_MEM_SPACE_SIZE while checking for MMIO resource len.
- use kcalloc instead of kmalloc.
- replaced a couple of scmd_printk usage with dev_dbg
- use dev_info and pr_info at couple of places.
- add a comment to pvscsi_map_context
- Add entry in MAINTAINERS.

Patch applies on top of 2.6.32-rc1.

--

SCSI driver for VMware's virtual HBA.

From: Alok N Kataria <[email protected]>

This is a driver for VMware's paravirtualized SCSI device,
which should improve disk performance for guests running
under control of VMware hypervisors that support such devices.

Signed-off-by: Alok N Kataria <[email protected]>
---

MAINTAINERS | 8
drivers/scsi/Kconfig | 8
drivers/scsi/Makefile | 1
drivers/scsi/pvscsi.c | 1393 +++++++++++++++++++++++++++++++++++++++++++++++++
drivers/scsi/pvscsi.h | 397 ++++++++++++++
5 files changed, 1807 insertions(+), 0 deletions(-)
create mode 100644 drivers/scsi/pvscsi.c
create mode 100644 drivers/scsi/pvscsi.h


diff --git a/MAINTAINERS b/MAINTAINERS
index c450f3a..45de4b3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5608,6 +5608,14 @@ S: Maintained
F: drivers/vlynq/vlynq.c
F: include/linux/vlynq.h

+VMware PVSCSI driver
+M: Alok Kataria <[email protected]>
+M: VMware PV-Drivers <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/scsi/pvscsi.c
+F: drivers/scsi/pvscsi.h
+
VOLTAGE AND CURRENT REGULATOR FRAMEWORK
M: Liam Girdwood <[email protected]>
M: Mark Brown <[email protected]>
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index 82bb3b2..5a19ae0 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -620,6 +620,14 @@ config SCSI_FLASHPOINT
substantial, so users of MultiMaster Host Adapters may not
wish to include it.

+config VMWARE_PVSCSI
+ tristate "VMware PVSCSI driver support"
+ depends on PCI && SCSI && X86
+ help
+ This driver supports VMware's para virtualized SCSI HBA.
+ To compile this driver as a module, choose M here: the
+ module will be called pvscsi.
+
config LIBFC
tristate "LibFC module"
select SCSI_FC_ATTRS
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 61a94af..8ddad00 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -131,6 +131,7 @@ obj-$(CONFIG_PS3_ROM) += ps3rom.o
obj-$(CONFIG_SCSI_CXGB3_ISCSI) += libiscsi.o libiscsi_tcp.o cxgb3i/
obj-$(CONFIG_SCSI_BNX2_ISCSI) += libiscsi.o bnx2i/
obj-$(CONFIG_SCSI_PMCRAID) += pmcraid.o
+obj-$(CONFIG_VMWARE_PVSCSI) += pvscsi.o

obj-$(CONFIG_ARM) += arm/

diff --git a/drivers/scsi/pvscsi.c b/drivers/scsi/pvscsi.c
new file mode 100644
index 0000000..f0ba207
--- /dev/null
+++ b/drivers/scsi/pvscsi.c
@@ -0,0 +1,1393 @@
+/*
+ * Linux driver for VMware's para-virtualized SCSI HBA.
+ *
+ * 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: Alok N Kataria <[email protected]>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/pci.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+
+#include "pvscsi.h"
+
+#define PVSCSI_LINUX_DRIVER_DESC "VMware PVSCSI driver"
+
+MODULE_DESCRIPTION(PVSCSI_LINUX_DRIVER_DESC);
+MODULE_AUTHOR("VMware, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(PVSCSI_DRIVER_VERSION_STRING);
+
+#define PVSCSI_DEFAULT_NUM_PAGES_PER_RING 8
+#define PVSCSI_DEFAULT_NUM_PAGES_MSG_RING 1
+#define PVSCSI_DEFAULT_QUEUE_DEPTH 64
+#define SGL_SIZE PAGE_SIZE
+
+#define pvscsi_dev(adapter) (&(adapter->dev->dev))
+
+struct pvscsi_sg_list {
+ struct PVSCSISGElement sge[PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT];
+};
+
+struct pvscsi_ctx {
+ /*
+ * The index of the context in cmd_map serves as the context ID for a
+ * 1-to-1 mapping completions back to requests.
+ */
+ struct scsi_cmnd *cmd;
+ struct pvscsi_sg_list *sgl;
+ struct list_head list;
+ dma_addr_t dataPA;
+ dma_addr_t sensePA;
+ dma_addr_t sglPA;
+};
+
+struct pvscsi_adapter {
+ char *mmioBase;
+ unsigned int irq;
+ u8 rev;
+ bool use_msi;
+ bool use_msix;
+ bool use_msg;
+
+ spinlock_t hw_lock;
+
+ struct workqueue_struct *workqueue;
+ struct work_struct work;
+
+ struct PVSCSIRingReqDesc *req_ring;
+ unsigned req_pages;
+ unsigned req_depth;
+ dma_addr_t reqRingPA;
+
+ struct PVSCSIRingCmpDesc *cmp_ring;
+ unsigned cmp_pages;
+ dma_addr_t cmpRingPA;
+
+ struct PVSCSIRingMsgDesc *msg_ring;
+ unsigned msg_pages;
+ dma_addr_t msgRingPA;
+
+ struct PVSCSIRingsState *rings_state;
+ dma_addr_t ringStatePA;
+
+ struct pci_dev *dev;
+ struct Scsi_Host *host;
+
+ struct list_head cmd_pool;
+ struct pvscsi_ctx *cmd_map;
+};
+
+
+/* Command line parameters */
+static int pvscsi_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_PER_RING;
+static int pvscsi_msg_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_MSG_RING;
+static int pvscsi_cmd_per_lun = PVSCSI_DEFAULT_QUEUE_DEPTH;
+static bool pvscsi_disable_msi;
+static bool pvscsi_disable_msix;
+static bool pvscsi_use_msg = true;
+
+#define PVSCSI_RW (S_IRUSR | S_IWUSR)
+
+module_param_named(ring_pages, pvscsi_ring_pages, int, PVSCSI_RW);
+MODULE_PARM_DESC(ring_pages, "Number of pages per req/cmp ring - (default="
+ __stringify(PVSCSI_DEFAULT_NUM_PAGES_PER_RING) ")");
+
+module_param_named(msg_ring_pages, pvscsi_msg_ring_pages, int, PVSCSI_RW);
+MODULE_PARM_DESC(msg_ring_pages, "Number of pages for the msg ring - (default="
+ __stringify(PVSCSI_DEFAULT_NUM_PAGES_MSG_RING) ")");
+
+module_param_named(cmd_per_lun, pvscsi_cmd_per_lun, int, PVSCSI_RW);
+MODULE_PARM_DESC(cmd_per_lun, "Maximum commands per lun - (default="
+ __stringify(PVSCSI_MAX_REQ_QUEUE_DEPTH) ")");
+
+module_param_named(disable_msi, pvscsi_disable_msi, bool, PVSCSI_RW);
+MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
+
+module_param_named(disable_msix, pvscsi_disable_msix, bool, PVSCSI_RW);
+MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default=0)");
+
+module_param_named(use_msg, pvscsi_use_msg, bool, PVSCSI_RW);
+MODULE_PARM_DESC(use_msg, "Use msg ring when available - (default=1)");
+
+static const struct pci_device_id pvscsi_pci_tbl[] = {
+ { PCI_VDEVICE(VMWARE, PCI_DEVICE_ID_VMWARE_PVSCSI) },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, pvscsi_pci_tbl);
+
+static struct pvscsi_ctx *
+pvscsi_find_context(const struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
+{
+ struct pvscsi_ctx *ctx, *end;
+
+ end = &adapter->cmd_map[adapter->req_depth];
+ for (ctx = adapter->cmd_map; ctx < end; ctx++)
+ if (ctx->cmd == cmd)
+ return ctx;
+
+ return NULL;
+}
+
+static struct pvscsi_ctx *
+pvscsi_acquire_context(struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
+{
+ struct pvscsi_ctx *ctx;
+
+ if (list_empty(&adapter->cmd_pool))
+ return NULL;
+
+ ctx = list_first_entry(&adapter->cmd_pool, struct pvscsi_ctx, list);
+ ctx->cmd = cmd;
+ list_del(&ctx->list);
+
+ return ctx;
+}
+
+static void pvscsi_release_context(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx)
+{
+ ctx->cmd = NULL;
+ list_add(&ctx->list, &adapter->cmd_pool);
+}
+
+/*
+ * Map a pvscsi_ctx struct to a context ID field value; we map to a simple
+ * non-zero integer. ctx always points to an entry in cmd_map array, hence
+ * the return value is always >=1.
+ */
+static u64 pvscsi_map_context(const struct pvscsi_adapter *adapter,
+ const struct pvscsi_ctx *ctx)
+{
+ return ctx - adapter->cmd_map + 1;
+}
+
+static struct pvscsi_ctx *
+pvscsi_get_context(const struct pvscsi_adapter *adapter, u64 context)
+{
+ return &adapter->cmd_map[context - 1];
+}
+
+static void pvscsi_reg_write(const struct pvscsi_adapter *adapter,
+ u32 offset, u32 val)
+{
+ writel(val, adapter->mmioBase + offset);
+}
+
+static u32 pvscsi_reg_read(const struct pvscsi_adapter *adapter, u32 offset)
+{
+ return readl(adapter->mmioBase + offset);
+}
+
+static u32 pvscsi_read_intr_status(const struct pvscsi_adapter *adapter)
+{
+ return pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_INTR_STATUS);
+}
+
+static void pvscsi_write_intr_status(const struct pvscsi_adapter *adapter,
+ u32 val)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_STATUS, val);
+}
+
+static void pvscsi_unmask_intr(const struct pvscsi_adapter *adapter)
+{
+ u32 intr_bits;
+
+ intr_bits = PVSCSI_INTR_CMPL_MASK;
+ if (adapter->use_msg)
+ intr_bits |= PVSCSI_INTR_MSG_MASK;
+
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, intr_bits);
+}
+
+static void pvscsi_mask_intr(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, 0);
+}
+
+static void pvscsi_write_cmd_desc(const struct pvscsi_adapter *adapter,
+ u32 cmd, const void *desc, size_t len)
+{
+ const u32 *ptr = desc;
+ size_t i;
+
+ len /= sizeof(*ptr);
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND, cmd);
+ for (i = 0; i < len; i++)
+ pvscsi_reg_write(adapter,
+ PVSCSI_REG_OFFSET_COMMAND_DATA, ptr[i]);
+}
+
+static void pvscsi_abort_cmd(const struct pvscsi_adapter *adapter,
+ const struct pvscsi_ctx *ctx)
+{
+ struct PVSCSICmdDescAbortCmd cmd = { 0 };
+
+ cmd.target = ctx->cmd->device->id;
+ cmd.context = pvscsi_map_context(adapter, ctx);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ABORT_CMD, &cmd, sizeof(cmd));
+}
+
+static void pvscsi_kick_rw_io(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_RW_IO, 0);
+}
+
+static void pvscsi_process_request_ring(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_NON_RW_IO, 0);
+}
+
+static int scsi_is_rw(unsigned char op)
+{
+ return op == READ_6 || op == WRITE_6 ||
+ op == READ_10 || op == WRITE_10 ||
+ op == READ_12 || op == WRITE_12 ||
+ op == READ_16 || op == WRITE_16;
+}
+
+static void pvscsi_kick_io(const struct pvscsi_adapter *adapter,
+ unsigned char op)
+{
+ if (scsi_is_rw(op))
+ pvscsi_kick_rw_io(adapter);
+ else
+ pvscsi_process_request_ring(adapter);
+}
+
+static void ll_adapter_reset(const struct pvscsi_adapter *adapter)
+{
+ dev_dbg(pvscsi_dev(adapter), "Adapter Reset on %p\n", adapter);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ADAPTER_RESET, NULL, 0);
+}
+
+static void ll_bus_reset(const struct pvscsi_adapter *adapter)
+{
+ dev_dbg(pvscsi_dev(adapter), "Reseting bus on %p\n", adapter);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_BUS, NULL, 0);
+}
+
+static void ll_device_reset(const struct pvscsi_adapter *adapter, u32 target)
+{
+ struct PVSCSICmdDescResetDevice cmd = { 0 };
+
+ dev_dbg(pvscsi_dev(adapter), "Reseting device: target=%u\n", target);
+
+ cmd.target = target;
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_DEVICE,
+ &cmd, sizeof(cmd));
+}
+
+static void pvscsi_create_sg(struct pvscsi_ctx *ctx,
+ struct scatterlist *sg, unsigned count)
+{
+ unsigned i;
+ struct PVSCSISGElement *sge;
+
+ BUG_ON(count > PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT);
+
+ sge = &ctx->sgl->sge[0];
+ for (i = 0; i < count; i++, sg++) {
+ sge[i].addr = sg_dma_address(sg);
+ sge[i].length = sg_dma_len(sg);
+ sge[i].flags = 0;
+ }
+}
+
+/*
+ * Map all data buffers for a command into PCI space and
+ * setup the scatter/gather list if needed.
+ */
+static void pvscsi_map_buffers(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd,
+ struct PVSCSIRingReqDesc *e)
+{
+ unsigned count;
+ unsigned bufflen = scsi_bufflen(cmd);
+ struct scatterlist *sg;
+
+ e->dataLen = bufflen;
+ e->dataAddr = 0;
+ if (bufflen == 0)
+ return;
+
+ sg = scsi_sglist(cmd);
+ count = scsi_sg_count(cmd);
+ if (count != 0) {
+ int segs = scsi_dma_map(cmd);
+ if (segs > 1) {
+ pvscsi_create_sg(ctx, sg, segs);
+
+ e->flags |= PVSCSI_FLAG_CMD_WITH_SG_LIST;
+ ctx->sglPA = pci_map_single(adapter->dev, ctx->sgl,
+ SGL_SIZE, PCI_DMA_TODEVICE);
+ e->dataAddr = ctx->sglPA;
+ } else
+ e->dataAddr = sg_dma_address(sg);
+ } else {
+ /*
+ * In case there is no S/G list, scsi_sglist points
+ * directly to the buffer.
+ */
+ ctx->dataPA = pci_map_single(adapter->dev, sg, bufflen,
+ cmd->sc_data_direction);
+ e->dataAddr = ctx->dataPA;
+ }
+}
+
+static void pvscsi_unmap_buffers(const struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx)
+{
+ struct scsi_cmnd *cmd;
+ unsigned bufflen;
+
+ cmd = ctx->cmd;
+ bufflen = scsi_bufflen(cmd);
+
+ if (bufflen != 0) {
+ unsigned count = scsi_sg_count(cmd);
+
+ if (count != 0) {
+ scsi_dma_unmap(cmd);
+ if (ctx->sglPA) {
+ pci_unmap_single(adapter->dev, ctx->sglPA,
+ SGL_SIZE, PCI_DMA_TODEVICE);
+ ctx->sglPA = 0;
+ }
+ } else
+ pci_unmap_single(adapter->dev, ctx->dataPA, bufflen,
+ cmd->sc_data_direction);
+ }
+ if (cmd->sense_buffer)
+ pci_unmap_single(adapter->dev, ctx->sensePA,
+ SCSI_SENSE_BUFFERSIZE, PCI_DMA_FROMDEVICE);
+}
+
+static int __devinit pvscsi_allocate_rings(struct pvscsi_adapter *adapter)
+{
+ adapter->rings_state = pci_alloc_consistent(adapter->dev, PAGE_SIZE,
+ &adapter->ringStatePA);
+ if (!adapter->rings_state)
+ return -ENOMEM;
+
+ adapter->req_pages = min(PVSCSI_MAX_NUM_PAGES_REQ_RING,
+ pvscsi_ring_pages);
+ adapter->req_depth = adapter->req_pages
+ * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ adapter->req_ring = pci_alloc_consistent(adapter->dev,
+ adapter->req_pages * PAGE_SIZE,
+ &adapter->reqRingPA);
+ if (!adapter->req_ring)
+ return -ENOMEM;
+
+ adapter->cmp_pages = min(PVSCSI_MAX_NUM_PAGES_CMP_RING,
+ pvscsi_ring_pages);
+ adapter->cmp_ring = pci_alloc_consistent(adapter->dev,
+ adapter->cmp_pages * PAGE_SIZE,
+ &adapter->cmpRingPA);
+ if (!adapter->cmp_ring)
+ return -ENOMEM;
+
+ BUG_ON(!IS_ALIGNED(adapter->ringStatePA, PAGE_SIZE));
+ BUG_ON(!IS_ALIGNED(adapter->reqRingPA, PAGE_SIZE));
+ BUG_ON(!IS_ALIGNED(adapter->cmpRingPA, PAGE_SIZE));
+
+ if (!adapter->use_msg)
+ return 0;
+
+ adapter->msg_pages = min(PVSCSI_MAX_NUM_PAGES_MSG_RING,
+ pvscsi_msg_ring_pages);
+ adapter->msg_ring = pci_alloc_consistent(adapter->dev,
+ adapter->msg_pages * PAGE_SIZE,
+ &adapter->msgRingPA);
+ if (!adapter->msg_ring)
+ return -ENOMEM;
+ BUG_ON(!IS_ALIGNED(adapter->msgRingPA, PAGE_SIZE));
+
+ return 0;
+}
+
+static void pvscsi_setup_all_rings(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSICmdDescSetupRings cmd = { 0 };
+ dma_addr_t base;
+ unsigned i;
+
+ cmd.ringsStatePPN = adapter->ringStatePA >> PAGE_SHIFT;
+ cmd.reqRingNumPages = adapter->req_pages;
+ cmd.cmpRingNumPages = adapter->cmp_pages;
+
+ base = adapter->reqRingPA;
+ for (i = 0; i < adapter->req_pages; i++) {
+ cmd.reqRingPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+
+ base = adapter->cmpRingPA;
+ for (i = 0; i < adapter->cmp_pages; i++) {
+ cmd.cmpRingPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+
+ memset(adapter->rings_state, 0, PAGE_SIZE);
+ memset(adapter->req_ring, 0, adapter->req_pages * PAGE_SIZE);
+ memset(adapter->cmp_ring, 0, adapter->cmp_pages * PAGE_SIZE);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_RINGS,
+ &cmd, sizeof(cmd));
+
+ if (adapter->use_msg) {
+ struct PVSCSICmdDescSetupMsgRing cmd_msg = { 0 };
+
+ cmd_msg.numPages = adapter->msg_pages;
+
+ base = adapter->msgRingPA;
+ for (i = 0; i < adapter->msg_pages; i++) {
+ cmd_msg.ringPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+ memset(adapter->msg_ring, 0, adapter->msg_pages * PAGE_SIZE);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_MSG_RING,
+ &cmd_msg, sizeof(cmd_msg));
+ }
+}
+
+/*
+ * Pull a completion descriptor off and pass the completion back
+ * to the SCSI mid layer.
+ */
+static void pvscsi_complete_request(struct pvscsi_adapter *adapter,
+ const struct PVSCSIRingCmpDesc *e)
+{
+ struct pvscsi_ctx *ctx;
+ struct scsi_cmnd *cmd;
+ u32 btstat = e->hostStatus;
+ u32 sdstat = e->scsiStatus;
+
+ ctx = pvscsi_get_context(adapter, e->context);
+ cmd = ctx->cmd;
+ pvscsi_unmap_buffers(adapter, ctx);
+ pvscsi_release_context(adapter, ctx);
+ cmd->result = 0;
+
+ if (sdstat != SAM_STAT_GOOD &&
+ (btstat == BTSTAT_SUCCESS ||
+ btstat == BTSTAT_LINKED_COMMAND_COMPLETED ||
+ btstat == BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG)) {
+ cmd->result = (DID_OK << 16) | sdstat;
+ if (sdstat == SAM_STAT_CHECK_CONDITION && cmd->sense_buffer)
+ cmd->result |= (DRIVER_SENSE << 24);
+ } else
+ switch (btstat) {
+ case BTSTAT_SUCCESS:
+ case BTSTAT_LINKED_COMMAND_COMPLETED:
+ case BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG:
+ /* If everything went fine, let's move on.. */
+ cmd->result = (DID_OK << 16);
+ break;
+
+ case BTSTAT_DATARUN:
+ case BTSTAT_DATA_UNDERRUN:
+ /* Report residual data in underruns */
+ scsi_set_resid(cmd, scsi_bufflen(cmd) - e->dataLen);
+ cmd->result = (DID_ERROR << 16);
+ break;
+
+ case BTSTAT_SELTIMEO:
+ /* Our emulation returns this for non-connected devs */
+ cmd->result = (DID_BAD_TARGET << 16);
+ break;
+
+ case BTSTAT_LUNMISMATCH:
+ case BTSTAT_TAGREJECT:
+ case BTSTAT_BADMSG:
+ cmd->result = (DRIVER_INVALID << 24);
+ /* fall through */
+
+ case BTSTAT_HAHARDWARE:
+ case BTSTAT_INVPHASE:
+ case BTSTAT_HATIMEOUT:
+ case BTSTAT_NORESPONSE:
+ case BTSTAT_DISCONNECT:
+ case BTSTAT_HASOFTWARE:
+ case BTSTAT_BUSFREE:
+ case BTSTAT_SENSFAILED:
+ cmd->result |= (DID_ERROR << 16);
+ break;
+
+ case BTSTAT_SENTRST:
+ case BTSTAT_RECVRST:
+ case BTSTAT_BUSRESET:
+ cmd->result = (DID_RESET << 16);
+ break;
+
+ case BTSTAT_ABORTQUEUE:
+ cmd->result = (DID_ABORT << 16);
+ break;
+
+ case BTSTAT_SCSIPARITY:
+ cmd->result = (DID_PARITY << 16);
+ break;
+
+ default:
+ cmd->result = (DID_ERROR << 16);
+ scmd_printk(KERN_DEBUG, cmd,
+ "Unknown completion status: 0x%x\n",
+ btstat);
+ }
+
+ dev_dbg(&cmd->device->sdev_gendev,
+ "cmd=%p %x ctx=%p result=0x%x status=0x%x,%x\n",
+ cmd, cmd->cmnd[0], ctx, cmd->result, btstat, sdstat);
+
+ cmd->scsi_done(cmd);
+}
+
+/*
+ * barrier usage : Since the PVSCSI device is emulated, there could be cases
+ * where we may want to serialize some accesses between the driver and the
+ * emulation layer. We use compiler barriers instead of the more expensive
+ * memory barriers because PVSCSI is only supported on X86 which has strong
+ * memory access ordering.
+ */
+static void pvscsi_process_completion_ring(struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct PVSCSIRingCmpDesc *ring = adapter->cmp_ring;
+ u32 cmp_entries = s->cmpNumEntriesLog2;
+
+ while (s->cmpConsIdx != s->cmpProdIdx) {
+ struct PVSCSIRingCmpDesc *e = ring + (s->cmpConsIdx &
+ MASK(cmp_entries));
+ /*
+ * This barrier() ensures that *e is not dereferenced while
+ * the device emulation still writes data into the slot.
+ * Since the device emulation advances s->cmpProdIdx only after
+ * updating the slot we want to check it first.
+ */
+ barrier();
+ pvscsi_complete_request(adapter, e);
+ /*
+ * This barrier() ensures that compiler doesn't reorder write
+ * to s->cmpConsIdx before the read of (*e) inside
+ * pvscsi_complete_request. Otherwise, device emulation may
+ * overwrite *e before we had a chance to read it.
+ */
+ barrier();
+ s->cmpConsIdx++;
+ }
+}
+
+/*
+ * Translate a Linux SCSI request into a request ring entry.
+ */
+static int pvscsi_queue_ring(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd)
+{
+ struct PVSCSIRingsState *s;
+ struct PVSCSIRingReqDesc *e;
+ struct scsi_device *sdev;
+ u32 req_entries;
+
+ s = adapter->rings_state;
+ sdev = cmd->device;
+ req_entries = s->reqNumEntriesLog2;
+
+ /*
+ * If this condition holds, we might have room on the request ring, but
+ * we might not have room on the completion ring for the response.
+ * However, we have already ruled out this possibility - we would not
+ * have successfully allocated a context if it were true, since we only
+ * have one context per request entry. Check for it anyway, since it
+ * would be a serious bug.
+ */
+ if (s->reqProdIdx - s->cmpConsIdx >= 1 << req_entries) {
+ scmd_printk(KERN_ERR, cmd,
+ "pvscsi: ring full: reqProdIdx=%d cmpConsIdx=%d\n",
+ s->reqProdIdx, s->cmpConsIdx);
+ return -1;
+ }
+
+ e = adapter->req_ring + (s->reqProdIdx & MASK(req_entries));
+
+ e->bus = sdev->channel;
+ e->target = sdev->id;
+ memset(e->lun, 0, sizeof(e->lun));
+ e->lun[1] = sdev->lun;
+
+ if (cmd->sense_buffer) {
+ ctx->sensePA = pci_map_single(adapter->dev, cmd->sense_buffer,
+ SCSI_SENSE_BUFFERSIZE,
+ PCI_DMA_FROMDEVICE);
+ e->senseAddr = ctx->sensePA;
+ e->senseLen = SCSI_SENSE_BUFFERSIZE;
+ } else {
+ e->senseLen = 0;
+ e->senseAddr = 0;
+ }
+ e->cdbLen = cmd->cmd_len;
+ e->vcpuHint = smp_processor_id();
+ memcpy(e->cdb, cmd->cmnd, e->cdbLen);
+
+ e->tag = SIMPLE_QUEUE_TAG;
+ if (sdev->tagged_supported &&
+ (cmd->tag == HEAD_OF_QUEUE_TAG ||
+ cmd->tag == ORDERED_QUEUE_TAG))
+ e->tag = cmd->tag;
+
+ if (cmd->sc_data_direction == DMA_FROM_DEVICE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_TOHOST;
+ else if (cmd->sc_data_direction == DMA_TO_DEVICE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_TODEVICE;
+ else if (cmd->sc_data_direction == DMA_NONE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_NONE;
+ else
+ e->flags = 0;
+
+ pvscsi_map_buffers(adapter, ctx, cmd, e);
+
+ e->context = pvscsi_map_context(adapter, ctx);
+
+ barrier();
+
+ s->reqProdIdx++;
+
+ return 0;
+}
+
+static int pvscsi_queue(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ struct pvscsi_ctx *ctx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ ctx = pvscsi_acquire_context(adapter, cmd);
+ if (!ctx || pvscsi_queue_ring(adapter, ctx, cmd) != 0) {
+ if (ctx)
+ pvscsi_release_context(adapter, ctx);
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ return SCSI_MLQUEUE_HOST_BUSY;
+ }
+
+ cmd->scsi_done = done;
+
+ dev_dbg(&cmd->device->sdev_gendev,
+ "queued cmd %p, ctx %p, op=%x\n", cmd, ctx, cmd->cmnd[0]);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ pvscsi_kick_io(adapter, cmd->cmnd[0]);
+
+ return 0;
+}
+
+static int pvscsi_abort(struct scsi_cmnd *cmd)
+{
+ struct pvscsi_adapter *adapter = shost_priv(cmd->device->host);
+ struct pvscsi_ctx *ctx;
+ unsigned long flags;
+
+ scmd_printk(KERN_DEBUG, cmd, "task abort on host %u, %p\n",
+ adapter->host->host_no, cmd);
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ /*
+ * Poll the completion ring first - we might be trying to abort
+ * a command that is waiting to be dispatched in the completion ring.
+ */
+ pvscsi_process_completion_ring(adapter);
+
+ /*
+ * If there is no context for the command, it either already succeeded
+ * or else was never properly issued. Not our problem.
+ */
+ ctx = pvscsi_find_context(adapter, cmd);
+ if (!ctx) {
+ scmd_printk(KERN_DEBUG, cmd, "Failed to abort cmd %p\n", cmd);
+ goto out;
+ }
+
+ pvscsi_abort_cmd(adapter, ctx);
+
+ pvscsi_process_completion_ring(adapter);
+
+out:
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ return SUCCESS;
+}
+
+/*
+ * Abort all outstanding requests. This is only safe to use if the completion
+ * ring will never be walked again or the device has been reset, because it
+ * destroys the 1-1 mapping between context field passed to emulation and our
+ * request structure.
+ */
+static void pvscsi_reset_all(struct pvscsi_adapter *adapter)
+{
+ unsigned i;
+
+ for (i = 0; i < adapter->req_depth; i++) {
+ struct pvscsi_ctx *ctx = &adapter->cmd_map[i];
+ struct scsi_cmnd *cmd = ctx->cmd;
+ if (cmd) {
+ scmd_printk(KERN_ERR, cmd,
+ "Forced reset on cmd %p\n", cmd);
+ pvscsi_unmap_buffers(adapter, ctx);
+ pvscsi_release_context(adapter, ctx);
+ cmd->result = (DID_RESET << 16);
+ cmd->scsi_done(cmd);
+ }
+ }
+}
+
+static int pvscsi_host_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+ bool use_msg;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI Host reset\n");
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ use_msg = adapter->use_msg;
+
+ if (use_msg) {
+ adapter->use_msg = 0;
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ /*
+ * Now that we know that the ISR won't add more work on the
+ * workqueue we can safely flush any outstanding work.
+ */
+ flush_workqueue(adapter->workqueue);
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+ }
+
+ /*
+ * We're going to tear down the entire ring structure and set it back
+ * up, so stalling new requests until all completions are flushed and
+ * the rings are back in place.
+ */
+
+ pvscsi_process_request_ring(adapter);
+
+ ll_adapter_reset(adapter);
+
+ /*
+ * Now process any completions. Note we do this AFTER adapter reset,
+ * which is strange, but stops races where completions get posted
+ * between processing the ring and issuing the reset. The backend will
+ * not touch the ring memory after reset, so the immediately pre-reset
+ * completion ring state is still valid.
+ */
+ pvscsi_process_completion_ring(adapter);
+
+ pvscsi_reset_all(adapter);
+ adapter->use_msg = use_msg;
+ pvscsi_setup_all_rings(adapter);
+ pvscsi_unmask_intr(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static int pvscsi_bus_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI Bus reset\n");
+
+ /*
+ * We don't want to queue new requests for this bus after
+ * flushing all pending requests to emulation, since new
+ * requests could then sneak in during this bus reset phase,
+ * so take the lock now.
+ */
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_request_ring(adapter);
+ ll_bus_reset(adapter);
+ pvscsi_process_completion_ring(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static int pvscsi_device_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI device reset on scsi%u:%u\n",
+ host->host_no, cmd->device->id);
+
+ /*
+ * We don't want to queue new requests for this device after flushing
+ * all pending requests to emulation, since new requests could then
+ * sneak in during this device reset phase, so take the lock now.
+ */
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_request_ring(adapter);
+ ll_device_reset(adapter, cmd->device->id);
+ pvscsi_process_completion_ring(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static struct scsi_host_template pvscsi_template;
+
+static const char *pvscsi_info(struct Scsi_Host *host)
+{
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ static char buf[256];
+
+ sprintf(buf, "VMware PVSCSI storage adapter rev %d, req/cmp/msg rings: "
+ "%u/%u/%u pages, cmd_per_lun=%u", adapter->rev,
+ adapter->req_pages, adapter->cmp_pages, adapter->msg_pages,
+ pvscsi_template.cmd_per_lun);
+
+ return buf;
+}
+
+static struct scsi_host_template pvscsi_template = {
+ .module = THIS_MODULE,
+ .name = "VMware PVSCSI Host Adapter",
+ .proc_name = "pvscsi",
+ .info = pvscsi_info,
+ .queuecommand = pvscsi_queue,
+ .this_id = -1,
+ .sg_tablesize = PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT,
+ .dma_boundary = UINT_MAX,
+ .max_sectors = 0xffff,
+ .use_clustering = ENABLE_CLUSTERING,
+ .eh_abort_handler = pvscsi_abort,
+ .eh_device_reset_handler = pvscsi_device_reset,
+ .eh_bus_reset_handler = pvscsi_bus_reset,
+ .eh_host_reset_handler = pvscsi_host_reset,
+};
+
+static void pvscsi_process_msg(const struct pvscsi_adapter *adapter,
+ const struct PVSCSIRingMsgDesc *e)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct Scsi_Host *host = adapter->host;
+ struct scsi_device *sdev;
+
+ printk(KERN_INFO "pvscsi: msg type: 0x%x - MSG RING: %u/%u (%u) \n",
+ e->type, s->msgProdIdx, s->msgConsIdx, s->msgNumEntriesLog2);
+
+ BUILD_BUG_ON(PVSCSI_MSG_LAST != 2);
+
+ if (e->type == PVSCSI_MSG_DEV_ADDED) {
+ struct PVSCSIMsgDescDevStatusChanged *desc;
+ desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
+
+ printk(KERN_INFO "pvscsi: msg: device added at scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ if (!scsi_host_get(host))
+ return;
+
+ sdev = scsi_device_lookup(host, desc->bus, desc->target,
+ desc->lun[1]);
+ if (sdev) {
+ printk(KERN_INFO "pvscsi: device already exists\n");
+ scsi_device_put(sdev);
+ } else
+ scsi_add_device(adapter->host, desc->bus,
+ desc->target, desc->lun[1]);
+
+ scsi_host_put(host);
+ } else if (e->type == PVSCSI_MSG_DEV_REMOVED) {
+ struct PVSCSIMsgDescDevStatusChanged *desc;
+ desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
+
+ printk(KERN_INFO "pvscsi: msg: device removed at scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ if (!scsi_host_get(host))
+ return;
+
+ sdev = scsi_device_lookup(host, desc->bus, desc->target,
+ desc->lun[1]);
+ if (sdev) {
+ scsi_remove_device(sdev);
+ scsi_device_put(sdev);
+ } else
+ printk(KERN_INFO "pvscsi: failed to lookup scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ scsi_host_put(host);
+ }
+}
+
+static int pvscsi_msg_pending(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+
+ return s->msgProdIdx != s->msgConsIdx;
+}
+
+static void pvscsi_process_msg_ring(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct PVSCSIRingMsgDesc *ring = adapter->msg_ring;
+ u32 msg_entries = s->msgNumEntriesLog2;
+
+ while (pvscsi_msg_pending(adapter)) {
+ struct PVSCSIRingMsgDesc *e = ring + (s->msgConsIdx &
+ MASK(msg_entries));
+
+ barrier();
+ pvscsi_process_msg(adapter, e);
+ barrier();
+ s->msgConsIdx++;
+ }
+}
+
+static void pvscsi_msg_workqueue_handler(struct work_struct *data)
+{
+ struct pvscsi_adapter *adapter;
+
+ adapter = container_of(data, struct pvscsi_adapter, work);
+
+ pvscsi_process_msg_ring(adapter);
+}
+
+static int pvscsi_setup_msg_workqueue(struct pvscsi_adapter *adapter)
+{
+ char name[32];
+
+ if (!pvscsi_use_msg)
+ return 0;
+
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND,
+ PVSCSI_CMD_SETUP_MSG_RING);
+
+ if (pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_COMMAND_STATUS) == -1)
+ return 0;
+
+ snprintf(name, sizeof(name), "pvscsi_wq_%u", adapter->host->host_no);
+
+ adapter->workqueue = create_singlethread_workqueue(name);
+ if (!adapter->workqueue) {
+ printk(KERN_ERR "pvscsi: failed to create work queue\n");
+ return 0;
+ }
+ INIT_WORK(&adapter->work, pvscsi_msg_workqueue_handler);
+
+ return 1;
+}
+
+static irqreturn_t pvscsi_isr(int irq, void *devp)
+{
+ struct pvscsi_adapter *adapter = devp;
+ int handled;
+
+ if (adapter->use_msi || adapter->use_msix)
+ handled = true;
+ else {
+ u32 val = pvscsi_read_intr_status(adapter);
+ handled = (val & PVSCSI_INTR_ALL_SUPPORTED) != 0;
+ if (handled)
+ pvscsi_write_intr_status(devp, val);
+ }
+
+ if (handled) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_completion_ring(adapter);
+ if (adapter->use_msg && pvscsi_msg_pending(adapter))
+ queue_work(adapter->workqueue, &adapter->work);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ }
+
+ return IRQ_RETVAL(handled);
+}
+
+static void pvscsi_free_sgls(const struct pvscsi_adapter *adapter)
+{
+ struct pvscsi_ctx *ctx = adapter->cmd_map;
+ unsigned i;
+
+ for (i = 0; i < adapter->req_depth; ++i, ++ctx)
+ kfree(ctx->sgl);
+}
+
+static int pvscsi_setup_msix(const struct pvscsi_adapter *adapter, int *irq)
+{
+ struct msix_entry entry = { 0, PVSCSI_VECTOR_COMPLETION };
+ int ret;
+
+ ret = pci_enable_msix(adapter->dev, &entry, 1);
+ if (ret)
+ return ret;
+
+ *irq = entry.vector;
+
+ return 0;
+}
+
+static void pvscsi_shutdown_intr(struct pvscsi_adapter *adapter)
+{
+ if (adapter->irq) {
+ free_irq(adapter->irq, adapter);
+ adapter->irq = 0;
+ }
+ if (adapter->use_msi) {
+ pci_disable_msi(adapter->dev);
+ adapter->use_msi = 0;
+ } else if (adapter->use_msix) {
+ pci_disable_msix(adapter->dev);
+ adapter->use_msix = 0;
+ }
+}
+
+static void pvscsi_release_resources(struct pvscsi_adapter *adapter)
+{
+ pvscsi_shutdown_intr(adapter);
+
+ if (adapter->workqueue)
+ destroy_workqueue(adapter->workqueue);
+
+ if (adapter->mmioBase)
+ pci_iounmap(adapter->dev, adapter->mmioBase);
+
+ pci_release_regions(adapter->dev);
+
+ if (adapter->cmd_map) {
+ pvscsi_free_sgls(adapter);
+ kfree(adapter->cmd_map);
+ }
+
+ if (adapter->rings_state)
+ pci_free_consistent(adapter->dev, PAGE_SIZE,
+ adapter->rings_state, adapter->ringStatePA);
+
+ if (adapter->req_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->req_pages * PAGE_SIZE,
+ adapter->req_ring, adapter->reqRingPA);
+
+ if (adapter->cmp_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->cmp_pages * PAGE_SIZE,
+ adapter->cmp_ring, adapter->cmpRingPA);
+
+ if (adapter->msg_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->msg_pages * PAGE_SIZE,
+ adapter->msg_ring, adapter->msgRingPA);
+}
+
+/*
+ * Allocate scatter gather lists.
+ *
+ * These are statically allocated. Trying to be clever was not worth it.
+ *
+ * Dynamic allocation can fail, and we can't go deeep into the memory
+ * allocator, since we're a SCSI driver, and trying too hard to allocate
+ * memory might generate disk I/O. We also don't want to fail disk I/O
+ * in that case because we can't get an allocation - the I/O could be
+ * trying to swap out data to free memory. Since that is pathological,
+ * just use a statically allocated scatter list.
+ *
+ */
+static int __devinit pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
+{
+ struct pvscsi_ctx *ctx;
+ int i;
+
+ ctx = adapter->cmd_map;
+ BUILD_BUG_ON(sizeof(struct pvscsi_sg_list) > SGL_SIZE);
+
+ for (i = 0; i < adapter->req_depth; ++i, ++ctx) {
+ ctx->sgl = kmalloc(SGL_SIZE, GFP_KERNEL);
+ ctx->sglPA = 0;
+ BUG_ON(!IS_ALIGNED(((unsigned long)ctx->sgl), PAGE_SIZE));
+ if (!ctx->sgl) {
+ for (; i >= 0; --i, --ctx) {
+ kfree(ctx->sgl);
+ ctx->sgl = NULL;
+ }
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static int __devinit pvscsi_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct pvscsi_adapter *adapter;
+ struct Scsi_Host *host;
+ unsigned int i;
+ int error;
+
+ error = -ENODEV;
+
+ if (pci_enable_device(pdev))
+ return error;
+
+ if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) == 0 &&
+ pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) == 0) {
+ printk(KERN_INFO "pvscsi: using 64bit dma\n");
+ } else if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) == 0 &&
+ pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) == 0) {
+ printk(KERN_INFO "pvscsi: using 32bit dma\n");
+ } else {
+ printk(KERN_ERR "pvscsi: failed to set DMA mask\n");
+ goto out_disable_device;
+ }
+
+ pvscsi_template.can_queue =
+ min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
+ PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ pvscsi_template.cmd_per_lun =
+ min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);
+ host = scsi_host_alloc(&pvscsi_template, sizeof(struct pvscsi_adapter));
+ if (!host) {
+ printk(KERN_ERR "pvscsi: failed to allocate host\n");
+ goto out_disable_device;
+ }
+
+ adapter = shost_priv(host);
+ memset(adapter, 0, sizeof(*adapter));
+ adapter->dev = pdev;
+ adapter->host = host;
+
+ spin_lock_init(&adapter->hw_lock);
+
+ host->max_channel = 0;
+ host->max_id = 16;
+ host->max_lun = 1;
+ host->max_cmd_len = 16;
+
+ adapter->rev = pdev->revision;
+
+ if (pci_request_regions(pdev, "pvscsi")) {
+ printk(KERN_ERR "pvscsi: pci memory selection failed\n");
+ goto out_free_host;
+ }
+
+ for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
+ if ((pci_resource_flags(pdev, i) & PCI_BASE_ADDRESS_SPACE_IO))
+ continue;
+
+ if (pci_resource_len(pdev, i) < PVSCSI_MEM_SPACE_SIZE)
+ continue;
+
+ break;
+ }
+
+ if (i == DEVICE_COUNT_RESOURCE) {
+ printk(KERN_ERR "pvscsi: adapter has no suitable MMIO region\n");
+ goto out_release_resources;
+ }
+
+ adapter->mmioBase = pci_iomap(pdev, i, PVSCSI_MEM_SPACE_SIZE);
+
+ if (!adapter->mmioBase) {
+ printk(KERN_ERR "pvscsi: can't iomap for BAR %d memsize %lu\n",
+ i, PVSCSI_MEM_SPACE_SIZE);
+ goto out_release_resources;
+ }
+
+ pci_set_master(pdev);
+ pci_set_drvdata(pdev, host);
+
+ ll_adapter_reset(adapter);
+
+ adapter->use_msg = pvscsi_setup_msg_workqueue(adapter);
+
+ error = pvscsi_allocate_rings(adapter);
+ if (error) {
+ printk(KERN_ERR "pvscsi: unable to allocate ring memory\n");
+ goto out_release_resources;
+ }
+
+ /*
+ * From this point on we should reset the adapter if anything goes
+ * wrong.
+ */
+ pvscsi_setup_all_rings(adapter);
+
+ adapter->cmd_map = kcalloc(adapter->req_depth,
+ sizeof(struct pvscsi_ctx), GFP_KERNEL);
+ if (!adapter->cmd_map) {
+ printk(KERN_ERR "pvscsi: failed to allocate memory.\n");
+ error = -ENOMEM;
+ goto out_reset_adapter;
+ }
+
+ INIT_LIST_HEAD(&adapter->cmd_pool);
+ for (i = 0; i < adapter->req_depth; i++) {
+ struct pvscsi_ctx *ctx = adapter->cmd_map + i;
+ list_add(&ctx->list, &adapter->cmd_pool);
+ }
+
+ error = pvscsi_allocate_sg(adapter);
+ if (error) {
+ printk(KERN_ERR "pvscsi: unable to allocate s/g table\n");
+ goto out_reset_adapter;
+ }
+
+ if (!pvscsi_disable_msix &&
+ pvscsi_setup_msix(adapter, &adapter->irq) == 0) {
+ printk(KERN_INFO "pvscsi: using MSI-X\n");
+ adapter->use_msix = 1;
+ } else if (!pvscsi_disable_msi && pci_enable_msi(pdev) == 0) {
+ printk(KERN_INFO "pvscsi: using MSI\n");
+ adapter->use_msi = 1;
+ adapter->irq = pdev->irq;
+ } else {
+ printk(KERN_INFO "pvscsi: using INTx\n");
+ adapter->irq = pdev->irq;
+ }
+
+ error = request_irq(adapter->irq, pvscsi_isr, IRQF_SHARED,
+ "pvscsi", adapter);
+ if (error) {
+ printk(KERN_ERR "pvscsi: unable to request IRQ: %d\n", error);
+ adapter->irq = 0;
+ goto out_reset_adapter;
+ }
+
+ error = scsi_add_host(host, &pdev->dev);
+ if (error) {
+ printk(KERN_ERR "pvscsi: scsi_add_host failed: %d\n", error);
+ goto out_reset_adapter;
+ }
+
+ dev_info(&pdev->dev, "VMware PVSCSI rev %d host #%u\n",
+ adapter->rev, host->host_no);
+
+ pvscsi_unmask_intr(adapter);
+
+ scsi_scan_host(host);
+
+ return 0;
+
+out_reset_adapter:
+ ll_adapter_reset(adapter);
+out_release_resources:
+ pvscsi_release_resources(adapter);
+out_free_host:
+ scsi_host_put(host);
+out_disable_device:
+ pci_set_drvdata(pdev, NULL);
+ pci_disable_device(pdev);
+
+ return error;
+}
+
+static void __pvscsi_shutdown(struct pvscsi_adapter *adapter)
+{
+ pvscsi_mask_intr(adapter);
+
+ if (adapter->workqueue)
+ flush_workqueue(adapter->workqueue);
+
+ pvscsi_shutdown_intr(adapter);
+
+ pvscsi_process_request_ring(adapter);
+ pvscsi_process_completion_ring(adapter);
+ ll_adapter_reset(adapter);
+}
+
+static void pvscsi_shutdown(struct pci_dev *dev)
+{
+ struct Scsi_Host *host = pci_get_drvdata(dev);
+ struct pvscsi_adapter *adapter = shost_priv(host);
+
+ __pvscsi_shutdown(adapter);
+}
+
+static void pvscsi_remove(struct pci_dev *pdev)
+{
+ struct Scsi_Host *host = pci_get_drvdata(pdev);
+ struct pvscsi_adapter *adapter = shost_priv(host);
+
+ scsi_remove_host(host);
+
+ __pvscsi_shutdown(adapter);
+ pvscsi_release_resources(adapter);
+
+ scsi_host_put(host);
+
+ pci_set_drvdata(pdev, NULL);
+ pci_disable_device(pdev);
+}
+
+static struct pci_driver pvscsi_pci_driver = {
+ .name = "pvscsi",
+ .id_table = pvscsi_pci_tbl,
+ .probe = pvscsi_probe,
+ .remove = __devexit_p(pvscsi_remove),
+ .shutdown = pvscsi_shutdown,
+};
+
+static int __init pvscsi_init(void)
+{
+ pr_info("%s - version %s\n",
+ PVSCSI_LINUX_DRIVER_DESC, PVSCSI_DRIVER_VERSION_STRING);
+ return pci_register_driver(&pvscsi_pci_driver);
+}
+
+static void __exit pvscsi_exit(void)
+{
+ pci_unregister_driver(&pvscsi_pci_driver);
+}
+
+module_init(pvscsi_init);
+module_exit(pvscsi_exit);
diff --git a/drivers/scsi/pvscsi.h b/drivers/scsi/pvscsi.h
new file mode 100644
index 0000000..9753eee
--- /dev/null
+++ b/drivers/scsi/pvscsi.h
@@ -0,0 +1,397 @@
+/*
+ * 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: Alok N Kataria <[email protected]>
+ *
+ */
+
+#ifndef _PVSCSI_H_
+#define _PVSCSI_H_
+
+#include <linux/types.h>
+
+#define PVSCSI_DRIVER_VERSION_STRING "1.0.1.0-k"
+
+#define PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT 128
+
+#define MASK(n) ((1 << (n)) - 1) /* make an n-bit mask */
+
+#define PCI_VENDOR_ID_VMWARE 0x15AD
+#define PCI_DEVICE_ID_VMWARE_PVSCSI 0x07C0
+
+/*
+ * 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 */
+};
+
+/*
+ * Command descriptor for PVSCSI_CMD_RESET_DEVICE --
+ */
+
+struct PVSCSICmdDescResetDevice {
+ u32 target;
+ u8 lun[8];
+} __packed;
+
+/*
+ * Command descriptor for PVSCSI_CMD_ABORT_CMD --
+ *
+ * - currently does not support specifying the LUN.
+ * - _pad should be 0.
+ */
+
+struct PVSCSICmdDescAbortCmd {
+ u64 context;
+ u32 target;
+ u32 _pad;
+} __packed;
+
+/*
+ * 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 {
+ u32 reqRingNumPages;
+ u32 cmpRingNumPages;
+ u64 ringsStatePPN;
+ u64 reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ u64 cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+} __packed;
+
+/*
+ * 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(u32).
+ * - 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 {
+ u32 numPages;
+ u32 _pad;
+ u64 ringPPNs[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
+} __packed;
+
+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 {
+ u32 type;
+ u32 args[31];
+} __packed;
+
+struct PVSCSIMsgDescDevStatusChanged {
+ u32 type; /* PVSCSI_MSG_DEV _ADDED / _REMOVED */
+ u32 bus;
+ u32 target;
+ u8 lun[8];
+ u32 pad[27];
+} __packed;
+
+/*
+ * 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 {
+ u32 reqProdIdx;
+ u32 reqConsIdx;
+ u32 reqNumEntriesLog2;
+
+ u32 cmpProdIdx;
+ u32 cmpConsIdx;
+ u32 cmpNumEntriesLog2;
+
+ u8 _pad[104];
+
+ u32 msgProdIdx;
+ u32 msgConsIdx;
+ u32 msgNumEntriesLog2;
+} __packed;
+
+/*
+ * 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 u32.
+ * 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)
+
+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;
+
+/*
+ * 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 {
+ u64 addr;
+ u32 length;
+ u32 flags;
+} __packed;
+
+/*
+ * 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 {
+ u64 context;
+ u64 dataLen;
+ u32 senseLen;
+ u16 hostStatus;
+ u16 scsiStatus;
+ u32 _pad[2];
+} __packed;
+
+/*
+ * 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 \
+ (PAGE_SIZE / sizeof(struct PVSCSIRingReqDesc))
+
+#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 * PAGE_SIZE)
+
+#endif /* _PVSCSI_H_ */


2009-09-30 18:56:17

by Alok Kataria

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

Forgot to copy the virtualization list..

On Wed, 2009-09-30 at 11:53 -0700, Alok Kataria wrote:
> Hi James,
>
> Given that their are no more modification requests, I think this driver
> should be ready for prime time now. I have incorporated the remaining
> few outstanding comments while rebasing the patch for 32-rc1.
>
> Please consider this for the SCSI tree. Also let me know if you have any
> feedback. Thanks.
>
> VMware PVSCSI driver - v5.
>
> Changelog (v5-v4)
> - use bool for pvscsi _disable_msi, _disable_msix, _use_msg
> - fix for 16 byte CDBs, max_cmd_len should be initialized to 16.
> - bump the driver version.
> - add <[email protected]> in the MAINTAINERS file.
> - rebase to 2.6.32-rc1.
>
> Changelog (v4-v3)
> - use size_t type for a loop iterator.
> - use IS_ALIGNED instead of ~PAGE_MASK, for alignment checks
> - use scsi_dma_ map/unmap API's
> - reduce the pvscsi_info buffer to 256 bytes
> - code cleanup in pvscsi_shutdown_intr
>
> Changelog (v3-v2)
> - use kmalloc for allocating sg_lists. And pci_map it for device access.
> - re-use pdev->revision for setting up adapter->rev.
>
> Changelog (v2-v1)
> - use PCI_VDEVICE instead of PCI_DEVICE.
> - use list_first_entry
> - use parenthesis for every sizeof usage
> - get rid of all #ifdef for CONFIG_PCI_MSI
> - use PVSCSI_MEM_SPACE_SIZE while checking for MMIO resource len.
> - use kcalloc instead of kmalloc.
> - replaced a couple of scmd_printk usage with dev_dbg
> - use dev_info and pr_info at couple of places.
> - add a comment to pvscsi_map_context
> - Add entry in MAINTAINERS.
>
> Patch applies on top of 2.6.32-rc1.
>
> --
>
> SCSI driver for VMware's virtual HBA.
>
> From: Alok N Kataria <[email protected]>
>
> This is a driver for VMware's paravirtualized SCSI device,
> which should improve disk performance for guests running
> under control of VMware hypervisors that support such devices.
>
> Signed-off-by: Alok N Kataria <[email protected]>
> ---
>
> MAINTAINERS | 8
> drivers/scsi/Kconfig | 8
> drivers/scsi/Makefile | 1
> drivers/scsi/pvscsi.c | 1393 +++++++++++++++++++++++++++++++++++++++++++++++++
> drivers/scsi/pvscsi.h | 397 ++++++++++++++
> 5 files changed, 1807 insertions(+), 0 deletions(-)
> create mode 100644 drivers/scsi/pvscsi.c
> create mode 100644 drivers/scsi/pvscsi.h
>
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c450f3a..45de4b3 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5608,6 +5608,14 @@ S: Maintained
> F: drivers/vlynq/vlynq.c
> F: include/linux/vlynq.h
>
> +VMware PVSCSI driver
> +M: Alok Kataria <[email protected]>
> +M: VMware PV-Drivers <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: drivers/scsi/pvscsi.c
> +F: drivers/scsi/pvscsi.h
> +
> VOLTAGE AND CURRENT REGULATOR FRAMEWORK
> M: Liam Girdwood <[email protected]>
> M: Mark Brown <[email protected]>
> diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
> index 82bb3b2..5a19ae0 100644
> --- a/drivers/scsi/Kconfig
> +++ b/drivers/scsi/Kconfig
> @@ -620,6 +620,14 @@ config SCSI_FLASHPOINT
> substantial, so users of MultiMaster Host Adapters may not
> wish to include it.
>
> +config VMWARE_PVSCSI
> + tristate "VMware PVSCSI driver support"
> + depends on PCI && SCSI && X86
> + help
> + This driver supports VMware's para virtualized SCSI HBA.
> + To compile this driver as a module, choose M here: the
> + module will be called pvscsi.
> +
> config LIBFC
> tristate "LibFC module"
> select SCSI_FC_ATTRS
> diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
> index 61a94af..8ddad00 100644
> --- a/drivers/scsi/Makefile
> +++ b/drivers/scsi/Makefile
> @@ -131,6 +131,7 @@ obj-$(CONFIG_PS3_ROM) += ps3rom.o
> obj-$(CONFIG_SCSI_CXGB3_ISCSI) += libiscsi.o libiscsi_tcp.o cxgb3i/
> obj-$(CONFIG_SCSI_BNX2_ISCSI) += libiscsi.o bnx2i/
> obj-$(CONFIG_SCSI_PMCRAID) += pmcraid.o
> +obj-$(CONFIG_VMWARE_PVSCSI) += pvscsi.o
>
> obj-$(CONFIG_ARM) += arm/
>
> diff --git a/drivers/scsi/pvscsi.c b/drivers/scsi/pvscsi.c
> new file mode 100644
> index 0000000..f0ba207
> --- /dev/null
> +++ b/drivers/scsi/pvscsi.c
> @@ -0,0 +1,1393 @@
> +/*
> + * Linux driver for VMware's para-virtualized SCSI HBA.
> + *
> + * 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: Alok N Kataria <[email protected]>
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/types.h>
> +#include <linux/interrupt.h>
> +#include <linux/workqueue.h>
> +#include <linux/pci.h>
> +
> +#include <scsi/scsi.h>
> +#include <scsi/scsi_host.h>
> +#include <scsi/scsi_cmnd.h>
> +#include <scsi/scsi_device.h>
> +
> +#include "pvscsi.h"
> +
> +#define PVSCSI_LINUX_DRIVER_DESC "VMware PVSCSI driver"
> +
> +MODULE_DESCRIPTION(PVSCSI_LINUX_DRIVER_DESC);
> +MODULE_AUTHOR("VMware, Inc.");
> +MODULE_LICENSE("GPL");
> +MODULE_VERSION(PVSCSI_DRIVER_VERSION_STRING);
> +
> +#define PVSCSI_DEFAULT_NUM_PAGES_PER_RING 8
> +#define PVSCSI_DEFAULT_NUM_PAGES_MSG_RING 1
> +#define PVSCSI_DEFAULT_QUEUE_DEPTH 64
> +#define SGL_SIZE PAGE_SIZE
> +
> +#define pvscsi_dev(adapter) (&(adapter->dev->dev))
> +
> +struct pvscsi_sg_list {
> + struct PVSCSISGElement sge[PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT];
> +};
> +
> +struct pvscsi_ctx {
> + /*
> + * The index of the context in cmd_map serves as the context ID for a
> + * 1-to-1 mapping completions back to requests.
> + */
> + struct scsi_cmnd *cmd;
> + struct pvscsi_sg_list *sgl;
> + struct list_head list;
> + dma_addr_t dataPA;
> + dma_addr_t sensePA;
> + dma_addr_t sglPA;
> +};
> +
> +struct pvscsi_adapter {
> + char *mmioBase;
> + unsigned int irq;
> + u8 rev;
> + bool use_msi;
> + bool use_msix;
> + bool use_msg;
> +
> + spinlock_t hw_lock;
> +
> + struct workqueue_struct *workqueue;
> + struct work_struct work;
> +
> + struct PVSCSIRingReqDesc *req_ring;
> + unsigned req_pages;
> + unsigned req_depth;
> + dma_addr_t reqRingPA;
> +
> + struct PVSCSIRingCmpDesc *cmp_ring;
> + unsigned cmp_pages;
> + dma_addr_t cmpRingPA;
> +
> + struct PVSCSIRingMsgDesc *msg_ring;
> + unsigned msg_pages;
> + dma_addr_t msgRingPA;
> +
> + struct PVSCSIRingsState *rings_state;
> + dma_addr_t ringStatePA;
> +
> + struct pci_dev *dev;
> + struct Scsi_Host *host;
> +
> + struct list_head cmd_pool;
> + struct pvscsi_ctx *cmd_map;
> +};
> +
> +
> +/* Command line parameters */
> +static int pvscsi_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_PER_RING;
> +static int pvscsi_msg_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_MSG_RING;
> +static int pvscsi_cmd_per_lun = PVSCSI_DEFAULT_QUEUE_DEPTH;
> +static bool pvscsi_disable_msi;
> +static bool pvscsi_disable_msix;
> +static bool pvscsi_use_msg = true;
> +
> +#define PVSCSI_RW (S_IRUSR | S_IWUSR)
> +
> +module_param_named(ring_pages, pvscsi_ring_pages, int, PVSCSI_RW);
> +MODULE_PARM_DESC(ring_pages, "Number of pages per req/cmp ring - (default="
> + __stringify(PVSCSI_DEFAULT_NUM_PAGES_PER_RING) ")");
> +
> +module_param_named(msg_ring_pages, pvscsi_msg_ring_pages, int, PVSCSI_RW);
> +MODULE_PARM_DESC(msg_ring_pages, "Number of pages for the msg ring - (default="
> + __stringify(PVSCSI_DEFAULT_NUM_PAGES_MSG_RING) ")");
> +
> +module_param_named(cmd_per_lun, pvscsi_cmd_per_lun, int, PVSCSI_RW);
> +MODULE_PARM_DESC(cmd_per_lun, "Maximum commands per lun - (default="
> + __stringify(PVSCSI_MAX_REQ_QUEUE_DEPTH) ")");
> +
> +module_param_named(disable_msi, pvscsi_disable_msi, bool, PVSCSI_RW);
> +MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
> +
> +module_param_named(disable_msix, pvscsi_disable_msix, bool, PVSCSI_RW);
> +MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default=0)");
> +
> +module_param_named(use_msg, pvscsi_use_msg, bool, PVSCSI_RW);
> +MODULE_PARM_DESC(use_msg, "Use msg ring when available - (default=1)");
> +
> +static const struct pci_device_id pvscsi_pci_tbl[] = {
> + { PCI_VDEVICE(VMWARE, PCI_DEVICE_ID_VMWARE_PVSCSI) },
> + { 0 }
> +};
> +
> +MODULE_DEVICE_TABLE(pci, pvscsi_pci_tbl);
> +
> +static struct pvscsi_ctx *
> +pvscsi_find_context(const struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
> +{
> + struct pvscsi_ctx *ctx, *end;
> +
> + end = &adapter->cmd_map[adapter->req_depth];
> + for (ctx = adapter->cmd_map; ctx < end; ctx++)
> + if (ctx->cmd == cmd)
> + return ctx;
> +
> + return NULL;
> +}
> +
> +static struct pvscsi_ctx *
> +pvscsi_acquire_context(struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
> +{
> + struct pvscsi_ctx *ctx;
> +
> + if (list_empty(&adapter->cmd_pool))
> + return NULL;
> +
> + ctx = list_first_entry(&adapter->cmd_pool, struct pvscsi_ctx, list);
> + ctx->cmd = cmd;
> + list_del(&ctx->list);
> +
> + return ctx;
> +}
> +
> +static void pvscsi_release_context(struct pvscsi_adapter *adapter,
> + struct pvscsi_ctx *ctx)
> +{
> + ctx->cmd = NULL;
> + list_add(&ctx->list, &adapter->cmd_pool);
> +}
> +
> +/*
> + * Map a pvscsi_ctx struct to a context ID field value; we map to a simple
> + * non-zero integer. ctx always points to an entry in cmd_map array, hence
> + * the return value is always >=1.
> + */
> +static u64 pvscsi_map_context(const struct pvscsi_adapter *adapter,
> + const struct pvscsi_ctx *ctx)
> +{
> + return ctx - adapter->cmd_map + 1;
> +}
> +
> +static struct pvscsi_ctx *
> +pvscsi_get_context(const struct pvscsi_adapter *adapter, u64 context)
> +{
> + return &adapter->cmd_map[context - 1];
> +}
> +
> +static void pvscsi_reg_write(const struct pvscsi_adapter *adapter,
> + u32 offset, u32 val)
> +{
> + writel(val, adapter->mmioBase + offset);
> +}
> +
> +static u32 pvscsi_reg_read(const struct pvscsi_adapter *adapter, u32 offset)
> +{
> + return readl(adapter->mmioBase + offset);
> +}
> +
> +static u32 pvscsi_read_intr_status(const struct pvscsi_adapter *adapter)
> +{
> + return pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_INTR_STATUS);
> +}
> +
> +static void pvscsi_write_intr_status(const struct pvscsi_adapter *adapter,
> + u32 val)
> +{
> + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_STATUS, val);
> +}
> +
> +static void pvscsi_unmask_intr(const struct pvscsi_adapter *adapter)
> +{
> + u32 intr_bits;
> +
> + intr_bits = PVSCSI_INTR_CMPL_MASK;
> + if (adapter->use_msg)
> + intr_bits |= PVSCSI_INTR_MSG_MASK;
> +
> + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, intr_bits);
> +}
> +
> +static void pvscsi_mask_intr(const struct pvscsi_adapter *adapter)
> +{
> + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, 0);
> +}
> +
> +static void pvscsi_write_cmd_desc(const struct pvscsi_adapter *adapter,
> + u32 cmd, const void *desc, size_t len)
> +{
> + const u32 *ptr = desc;
> + size_t i;
> +
> + len /= sizeof(*ptr);
> + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND, cmd);
> + for (i = 0; i < len; i++)
> + pvscsi_reg_write(adapter,
> + PVSCSI_REG_OFFSET_COMMAND_DATA, ptr[i]);
> +}
> +
> +static void pvscsi_abort_cmd(const struct pvscsi_adapter *adapter,
> + const struct pvscsi_ctx *ctx)
> +{
> + struct PVSCSICmdDescAbortCmd cmd = { 0 };
> +
> + cmd.target = ctx->cmd->device->id;
> + cmd.context = pvscsi_map_context(adapter, ctx);
> +
> + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ABORT_CMD, &cmd, sizeof(cmd));
> +}
> +
> +static void pvscsi_kick_rw_io(const struct pvscsi_adapter *adapter)
> +{
> + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_RW_IO, 0);
> +}
> +
> +static void pvscsi_process_request_ring(const struct pvscsi_adapter *adapter)
> +{
> + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_NON_RW_IO, 0);
> +}
> +
> +static int scsi_is_rw(unsigned char op)
> +{
> + return op == READ_6 || op == WRITE_6 ||
> + op == READ_10 || op == WRITE_10 ||
> + op == READ_12 || op == WRITE_12 ||
> + op == READ_16 || op == WRITE_16;
> +}
> +
> +static void pvscsi_kick_io(const struct pvscsi_adapter *adapter,
> + unsigned char op)
> +{
> + if (scsi_is_rw(op))
> + pvscsi_kick_rw_io(adapter);
> + else
> + pvscsi_process_request_ring(adapter);
> +}
> +
> +static void ll_adapter_reset(const struct pvscsi_adapter *adapter)
> +{
> + dev_dbg(pvscsi_dev(adapter), "Adapter Reset on %p\n", adapter);
> +
> + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ADAPTER_RESET, NULL, 0);
> +}
> +
> +static void ll_bus_reset(const struct pvscsi_adapter *adapter)
> +{
> + dev_dbg(pvscsi_dev(adapter), "Reseting bus on %p\n", adapter);
> +
> + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_BUS, NULL, 0);
> +}
> +
> +static void ll_device_reset(const struct pvscsi_adapter *adapter, u32 target)
> +{
> + struct PVSCSICmdDescResetDevice cmd = { 0 };
> +
> + dev_dbg(pvscsi_dev(adapter), "Reseting device: target=%u\n", target);
> +
> + cmd.target = target;
> +
> + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_DEVICE,
> + &cmd, sizeof(cmd));
> +}
> +
> +static void pvscsi_create_sg(struct pvscsi_ctx *ctx,
> + struct scatterlist *sg, unsigned count)
> +{
> + unsigned i;
> + struct PVSCSISGElement *sge;
> +
> + BUG_ON(count > PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT);
> +
> + sge = &ctx->sgl->sge[0];
> + for (i = 0; i < count; i++, sg++) {
> + sge[i].addr = sg_dma_address(sg);
> + sge[i].length = sg_dma_len(sg);
> + sge[i].flags = 0;
> + }
> +}
> +
> +/*
> + * Map all data buffers for a command into PCI space and
> + * setup the scatter/gather list if needed.
> + */
> +static void pvscsi_map_buffers(struct pvscsi_adapter *adapter,
> + struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd,
> + struct PVSCSIRingReqDesc *e)
> +{
> + unsigned count;
> + unsigned bufflen = scsi_bufflen(cmd);
> + struct scatterlist *sg;
> +
> + e->dataLen = bufflen;
> + e->dataAddr = 0;
> + if (bufflen == 0)
> + return;
> +
> + sg = scsi_sglist(cmd);
> + count = scsi_sg_count(cmd);
> + if (count != 0) {
> + int segs = scsi_dma_map(cmd);
> + if (segs > 1) {
> + pvscsi_create_sg(ctx, sg, segs);
> +
> + e->flags |= PVSCSI_FLAG_CMD_WITH_SG_LIST;
> + ctx->sglPA = pci_map_single(adapter->dev, ctx->sgl,
> + SGL_SIZE, PCI_DMA_TODEVICE);
> + e->dataAddr = ctx->sglPA;
> + } else
> + e->dataAddr = sg_dma_address(sg);
> + } else {
> + /*
> + * In case there is no S/G list, scsi_sglist points
> + * directly to the buffer.
> + */
> + ctx->dataPA = pci_map_single(adapter->dev, sg, bufflen,
> + cmd->sc_data_direction);
> + e->dataAddr = ctx->dataPA;
> + }
> +}
> +
> +static void pvscsi_unmap_buffers(const struct pvscsi_adapter *adapter,
> + struct pvscsi_ctx *ctx)
> +{
> + struct scsi_cmnd *cmd;
> + unsigned bufflen;
> +
> + cmd = ctx->cmd;
> + bufflen = scsi_bufflen(cmd);
> +
> + if (bufflen != 0) {
> + unsigned count = scsi_sg_count(cmd);
> +
> + if (count != 0) {
> + scsi_dma_unmap(cmd);
> + if (ctx->sglPA) {
> + pci_unmap_single(adapter->dev, ctx->sglPA,
> + SGL_SIZE, PCI_DMA_TODEVICE);
> + ctx->sglPA = 0;
> + }
> + } else
> + pci_unmap_single(adapter->dev, ctx->dataPA, bufflen,
> + cmd->sc_data_direction);
> + }
> + if (cmd->sense_buffer)
> + pci_unmap_single(adapter->dev, ctx->sensePA,
> + SCSI_SENSE_BUFFERSIZE, PCI_DMA_FROMDEVICE);
> +}
> +
> +static int __devinit pvscsi_allocate_rings(struct pvscsi_adapter *adapter)
> +{
> + adapter->rings_state = pci_alloc_consistent(adapter->dev, PAGE_SIZE,
> + &adapter->ringStatePA);
> + if (!adapter->rings_state)
> + return -ENOMEM;
> +
> + adapter->req_pages = min(PVSCSI_MAX_NUM_PAGES_REQ_RING,
> + pvscsi_ring_pages);
> + adapter->req_depth = adapter->req_pages
> + * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
> + adapter->req_ring = pci_alloc_consistent(adapter->dev,
> + adapter->req_pages * PAGE_SIZE,
> + &adapter->reqRingPA);
> + if (!adapter->req_ring)
> + return -ENOMEM;
> +
> + adapter->cmp_pages = min(PVSCSI_MAX_NUM_PAGES_CMP_RING,
> + pvscsi_ring_pages);
> + adapter->cmp_ring = pci_alloc_consistent(adapter->dev,
> + adapter->cmp_pages * PAGE_SIZE,
> + &adapter->cmpRingPA);
> + if (!adapter->cmp_ring)
> + return -ENOMEM;
> +
> + BUG_ON(!IS_ALIGNED(adapter->ringStatePA, PAGE_SIZE));
> + BUG_ON(!IS_ALIGNED(adapter->reqRingPA, PAGE_SIZE));
> + BUG_ON(!IS_ALIGNED(adapter->cmpRingPA, PAGE_SIZE));
> +
> + if (!adapter->use_msg)
> + return 0;
> +
> + adapter->msg_pages = min(PVSCSI_MAX_NUM_PAGES_MSG_RING,
> + pvscsi_msg_ring_pages);
> + adapter->msg_ring = pci_alloc_consistent(adapter->dev,
> + adapter->msg_pages * PAGE_SIZE,
> + &adapter->msgRingPA);
> + if (!adapter->msg_ring)
> + return -ENOMEM;
> + BUG_ON(!IS_ALIGNED(adapter->msgRingPA, PAGE_SIZE));
> +
> + return 0;
> +}
> +
> +static void pvscsi_setup_all_rings(const struct pvscsi_adapter *adapter)
> +{
> + struct PVSCSICmdDescSetupRings cmd = { 0 };
> + dma_addr_t base;
> + unsigned i;
> +
> + cmd.ringsStatePPN = adapter->ringStatePA >> PAGE_SHIFT;
> + cmd.reqRingNumPages = adapter->req_pages;
> + cmd.cmpRingNumPages = adapter->cmp_pages;
> +
> + base = adapter->reqRingPA;
> + for (i = 0; i < adapter->req_pages; i++) {
> + cmd.reqRingPPNs[i] = base >> PAGE_SHIFT;
> + base += PAGE_SIZE;
> + }
> +
> + base = adapter->cmpRingPA;
> + for (i = 0; i < adapter->cmp_pages; i++) {
> + cmd.cmpRingPPNs[i] = base >> PAGE_SHIFT;
> + base += PAGE_SIZE;
> + }
> +
> + memset(adapter->rings_state, 0, PAGE_SIZE);
> + memset(adapter->req_ring, 0, adapter->req_pages * PAGE_SIZE);
> + memset(adapter->cmp_ring, 0, adapter->cmp_pages * PAGE_SIZE);
> +
> + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_RINGS,
> + &cmd, sizeof(cmd));
> +
> + if (adapter->use_msg) {
> + struct PVSCSICmdDescSetupMsgRing cmd_msg = { 0 };
> +
> + cmd_msg.numPages = adapter->msg_pages;
> +
> + base = adapter->msgRingPA;
> + for (i = 0; i < adapter->msg_pages; i++) {
> + cmd_msg.ringPPNs[i] = base >> PAGE_SHIFT;
> + base += PAGE_SIZE;
> + }
> + memset(adapter->msg_ring, 0, adapter->msg_pages * PAGE_SIZE);
> +
> + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_MSG_RING,
> + &cmd_msg, sizeof(cmd_msg));
> + }
> +}
> +
> +/*
> + * Pull a completion descriptor off and pass the completion back
> + * to the SCSI mid layer.
> + */
> +static void pvscsi_complete_request(struct pvscsi_adapter *adapter,
> + const struct PVSCSIRingCmpDesc *e)
> +{
> + struct pvscsi_ctx *ctx;
> + struct scsi_cmnd *cmd;
> + u32 btstat = e->hostStatus;
> + u32 sdstat = e->scsiStatus;
> +
> + ctx = pvscsi_get_context(adapter, e->context);
> + cmd = ctx->cmd;
> + pvscsi_unmap_buffers(adapter, ctx);
> + pvscsi_release_context(adapter, ctx);
> + cmd->result = 0;
> +
> + if (sdstat != SAM_STAT_GOOD &&
> + (btstat == BTSTAT_SUCCESS ||
> + btstat == BTSTAT_LINKED_COMMAND_COMPLETED ||
> + btstat == BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG)) {
> + cmd->result = (DID_OK << 16) | sdstat;
> + if (sdstat == SAM_STAT_CHECK_CONDITION && cmd->sense_buffer)
> + cmd->result |= (DRIVER_SENSE << 24);
> + } else
> + switch (btstat) {
> + case BTSTAT_SUCCESS:
> + case BTSTAT_LINKED_COMMAND_COMPLETED:
> + case BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG:
> + /* If everything went fine, let's move on.. */
> + cmd->result = (DID_OK << 16);
> + break;
> +
> + case BTSTAT_DATARUN:
> + case BTSTAT_DATA_UNDERRUN:
> + /* Report residual data in underruns */
> + scsi_set_resid(cmd, scsi_bufflen(cmd) - e->dataLen);
> + cmd->result = (DID_ERROR << 16);
> + break;
> +
> + case BTSTAT_SELTIMEO:
> + /* Our emulation returns this for non-connected devs */
> + cmd->result = (DID_BAD_TARGET << 16);
> + break;
> +
> + case BTSTAT_LUNMISMATCH:
> + case BTSTAT_TAGREJECT:
> + case BTSTAT_BADMSG:
> + cmd->result = (DRIVER_INVALID << 24);
> + /* fall through */
> +
> + case BTSTAT_HAHARDWARE:
> + case BTSTAT_INVPHASE:
> + case BTSTAT_HATIMEOUT:
> + case BTSTAT_NORESPONSE:
> + case BTSTAT_DISCONNECT:
> + case BTSTAT_HASOFTWARE:
> + case BTSTAT_BUSFREE:
> + case BTSTAT_SENSFAILED:
> + cmd->result |= (DID_ERROR << 16);
> + break;
> +
> + case BTSTAT_SENTRST:
> + case BTSTAT_RECVRST:
> + case BTSTAT_BUSRESET:
> + cmd->result = (DID_RESET << 16);
> + break;
> +
> + case BTSTAT_ABORTQUEUE:
> + cmd->result = (DID_ABORT << 16);
> + break;
> +
> + case BTSTAT_SCSIPARITY:
> + cmd->result = (DID_PARITY << 16);
> + break;
> +
> + default:
> + cmd->result = (DID_ERROR << 16);
> + scmd_printk(KERN_DEBUG, cmd,
> + "Unknown completion status: 0x%x\n",
> + btstat);
> + }
> +
> + dev_dbg(&cmd->device->sdev_gendev,
> + "cmd=%p %x ctx=%p result=0x%x status=0x%x,%x\n",
> + cmd, cmd->cmnd[0], ctx, cmd->result, btstat, sdstat);
> +
> + cmd->scsi_done(cmd);
> +}
> +
> +/*
> + * barrier usage : Since the PVSCSI device is emulated, there could be cases
> + * where we may want to serialize some accesses between the driver and the
> + * emulation layer. We use compiler barriers instead of the more expensive
> + * memory barriers because PVSCSI is only supported on X86 which has strong
> + * memory access ordering.
> + */
> +static void pvscsi_process_completion_ring(struct pvscsi_adapter *adapter)
> +{
> + struct PVSCSIRingsState *s = adapter->rings_state;
> + struct PVSCSIRingCmpDesc *ring = adapter->cmp_ring;
> + u32 cmp_entries = s->cmpNumEntriesLog2;
> +
> + while (s->cmpConsIdx != s->cmpProdIdx) {
> + struct PVSCSIRingCmpDesc *e = ring + (s->cmpConsIdx &
> + MASK(cmp_entries));
> + /*
> + * This barrier() ensures that *e is not dereferenced while
> + * the device emulation still writes data into the slot.
> + * Since the device emulation advances s->cmpProdIdx only after
> + * updating the slot we want to check it first.
> + */
> + barrier();
> + pvscsi_complete_request(adapter, e);
> + /*
> + * This barrier() ensures that compiler doesn't reorder write
> + * to s->cmpConsIdx before the read of (*e) inside
> + * pvscsi_complete_request. Otherwise, device emulation may
> + * overwrite *e before we had a chance to read it.
> + */
> + barrier();
> + s->cmpConsIdx++;
> + }
> +}
> +
> +/*
> + * Translate a Linux SCSI request into a request ring entry.
> + */
> +static int pvscsi_queue_ring(struct pvscsi_adapter *adapter,
> + struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd)
> +{
> + struct PVSCSIRingsState *s;
> + struct PVSCSIRingReqDesc *e;
> + struct scsi_device *sdev;
> + u32 req_entries;
> +
> + s = adapter->rings_state;
> + sdev = cmd->device;
> + req_entries = s->reqNumEntriesLog2;
> +
> + /*
> + * If this condition holds, we might have room on the request ring, but
> + * we might not have room on the completion ring for the response.
> + * However, we have already ruled out this possibility - we would not
> + * have successfully allocated a context if it were true, since we only
> + * have one context per request entry. Check for it anyway, since it
> + * would be a serious bug.
> + */
> + if (s->reqProdIdx - s->cmpConsIdx >= 1 << req_entries) {
> + scmd_printk(KERN_ERR, cmd,
> + "pvscsi: ring full: reqProdIdx=%d cmpConsIdx=%d\n",
> + s->reqProdIdx, s->cmpConsIdx);
> + return -1;
> + }
> +
> + e = adapter->req_ring + (s->reqProdIdx & MASK(req_entries));
> +
> + e->bus = sdev->channel;
> + e->target = sdev->id;
> + memset(e->lun, 0, sizeof(e->lun));
> + e->lun[1] = sdev->lun;
> +
> + if (cmd->sense_buffer) {
> + ctx->sensePA = pci_map_single(adapter->dev, cmd->sense_buffer,
> + SCSI_SENSE_BUFFERSIZE,
> + PCI_DMA_FROMDEVICE);
> + e->senseAddr = ctx->sensePA;
> + e->senseLen = SCSI_SENSE_BUFFERSIZE;
> + } else {
> + e->senseLen = 0;
> + e->senseAddr = 0;
> + }
> + e->cdbLen = cmd->cmd_len;
> + e->vcpuHint = smp_processor_id();
> + memcpy(e->cdb, cmd->cmnd, e->cdbLen);
> +
> + e->tag = SIMPLE_QUEUE_TAG;
> + if (sdev->tagged_supported &&
> + (cmd->tag == HEAD_OF_QUEUE_TAG ||
> + cmd->tag == ORDERED_QUEUE_TAG))
> + e->tag = cmd->tag;
> +
> + if (cmd->sc_data_direction == DMA_FROM_DEVICE)
> + e->flags = PVSCSI_FLAG_CMD_DIR_TOHOST;
> + else if (cmd->sc_data_direction == DMA_TO_DEVICE)
> + e->flags = PVSCSI_FLAG_CMD_DIR_TODEVICE;
> + else if (cmd->sc_data_direction == DMA_NONE)
> + e->flags = PVSCSI_FLAG_CMD_DIR_NONE;
> + else
> + e->flags = 0;
> +
> + pvscsi_map_buffers(adapter, ctx, cmd, e);
> +
> + e->context = pvscsi_map_context(adapter, ctx);
> +
> + barrier();
> +
> + s->reqProdIdx++;
> +
> + return 0;
> +}
> +
> +static int pvscsi_queue(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
> +{
> + struct Scsi_Host *host = cmd->device->host;
> + struct pvscsi_adapter *adapter = shost_priv(host);
> + struct pvscsi_ctx *ctx;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&adapter->hw_lock, flags);
> +
> + ctx = pvscsi_acquire_context(adapter, cmd);
> + if (!ctx || pvscsi_queue_ring(adapter, ctx, cmd) != 0) {
> + if (ctx)
> + pvscsi_release_context(adapter, ctx);
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> + return SCSI_MLQUEUE_HOST_BUSY;
> + }
> +
> + cmd->scsi_done = done;
> +
> + dev_dbg(&cmd->device->sdev_gendev,
> + "queued cmd %p, ctx %p, op=%x\n", cmd, ctx, cmd->cmnd[0]);
> +
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> +
> + pvscsi_kick_io(adapter, cmd->cmnd[0]);
> +
> + return 0;
> +}
> +
> +static int pvscsi_abort(struct scsi_cmnd *cmd)
> +{
> + struct pvscsi_adapter *adapter = shost_priv(cmd->device->host);
> + struct pvscsi_ctx *ctx;
> + unsigned long flags;
> +
> + scmd_printk(KERN_DEBUG, cmd, "task abort on host %u, %p\n",
> + adapter->host->host_no, cmd);
> +
> + spin_lock_irqsave(&adapter->hw_lock, flags);
> +
> + /*
> + * Poll the completion ring first - we might be trying to abort
> + * a command that is waiting to be dispatched in the completion ring.
> + */
> + pvscsi_process_completion_ring(adapter);
> +
> + /*
> + * If there is no context for the command, it either already succeeded
> + * or else was never properly issued. Not our problem.
> + */
> + ctx = pvscsi_find_context(adapter, cmd);
> + if (!ctx) {
> + scmd_printk(KERN_DEBUG, cmd, "Failed to abort cmd %p\n", cmd);
> + goto out;
> + }
> +
> + pvscsi_abort_cmd(adapter, ctx);
> +
> + pvscsi_process_completion_ring(adapter);
> +
> +out:
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> + return SUCCESS;
> +}
> +
> +/*
> + * Abort all outstanding requests. This is only safe to use if the completion
> + * ring will never be walked again or the device has been reset, because it
> + * destroys the 1-1 mapping between context field passed to emulation and our
> + * request structure.
> + */
> +static void pvscsi_reset_all(struct pvscsi_adapter *adapter)
> +{
> + unsigned i;
> +
> + for (i = 0; i < adapter->req_depth; i++) {
> + struct pvscsi_ctx *ctx = &adapter->cmd_map[i];
> + struct scsi_cmnd *cmd = ctx->cmd;
> + if (cmd) {
> + scmd_printk(KERN_ERR, cmd,
> + "Forced reset on cmd %p\n", cmd);
> + pvscsi_unmap_buffers(adapter, ctx);
> + pvscsi_release_context(adapter, ctx);
> + cmd->result = (DID_RESET << 16);
> + cmd->scsi_done(cmd);
> + }
> + }
> +}
> +
> +static int pvscsi_host_reset(struct scsi_cmnd *cmd)
> +{
> + struct Scsi_Host *host = cmd->device->host;
> + struct pvscsi_adapter *adapter = shost_priv(host);
> + unsigned long flags;
> + bool use_msg;
> +
> + scmd_printk(KERN_INFO, cmd, "SCSI Host reset\n");
> +
> + spin_lock_irqsave(&adapter->hw_lock, flags);
> +
> + use_msg = adapter->use_msg;
> +
> + if (use_msg) {
> + adapter->use_msg = 0;
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> +
> + /*
> + * Now that we know that the ISR won't add more work on the
> + * workqueue we can safely flush any outstanding work.
> + */
> + flush_workqueue(adapter->workqueue);
> + spin_lock_irqsave(&adapter->hw_lock, flags);
> + }
> +
> + /*
> + * We're going to tear down the entire ring structure and set it back
> + * up, so stalling new requests until all completions are flushed and
> + * the rings are back in place.
> + */
> +
> + pvscsi_process_request_ring(adapter);
> +
> + ll_adapter_reset(adapter);
> +
> + /*
> + * Now process any completions. Note we do this AFTER adapter reset,
> + * which is strange, but stops races where completions get posted
> + * between processing the ring and issuing the reset. The backend will
> + * not touch the ring memory after reset, so the immediately pre-reset
> + * completion ring state is still valid.
> + */
> + pvscsi_process_completion_ring(adapter);
> +
> + pvscsi_reset_all(adapter);
> + adapter->use_msg = use_msg;
> + pvscsi_setup_all_rings(adapter);
> + pvscsi_unmask_intr(adapter);
> +
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> +
> + return SUCCESS;
> +}
> +
> +static int pvscsi_bus_reset(struct scsi_cmnd *cmd)
> +{
> + struct Scsi_Host *host = cmd->device->host;
> + struct pvscsi_adapter *adapter = shost_priv(host);
> + unsigned long flags;
> +
> + scmd_printk(KERN_INFO, cmd, "SCSI Bus reset\n");
> +
> + /*
> + * We don't want to queue new requests for this bus after
> + * flushing all pending requests to emulation, since new
> + * requests could then sneak in during this bus reset phase,
> + * so take the lock now.
> + */
> + spin_lock_irqsave(&adapter->hw_lock, flags);
> +
> + pvscsi_process_request_ring(adapter);
> + ll_bus_reset(adapter);
> + pvscsi_process_completion_ring(adapter);
> +
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> +
> + return SUCCESS;
> +}
> +
> +static int pvscsi_device_reset(struct scsi_cmnd *cmd)
> +{
> + struct Scsi_Host *host = cmd->device->host;
> + struct pvscsi_adapter *adapter = shost_priv(host);
> + unsigned long flags;
> +
> + scmd_printk(KERN_INFO, cmd, "SCSI device reset on scsi%u:%u\n",
> + host->host_no, cmd->device->id);
> +
> + /*
> + * We don't want to queue new requests for this device after flushing
> + * all pending requests to emulation, since new requests could then
> + * sneak in during this device reset phase, so take the lock now.
> + */
> + spin_lock_irqsave(&adapter->hw_lock, flags);
> +
> + pvscsi_process_request_ring(adapter);
> + ll_device_reset(adapter, cmd->device->id);
> + pvscsi_process_completion_ring(adapter);
> +
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> +
> + return SUCCESS;
> +}
> +
> +static struct scsi_host_template pvscsi_template;
> +
> +static const char *pvscsi_info(struct Scsi_Host *host)
> +{
> + struct pvscsi_adapter *adapter = shost_priv(host);
> + static char buf[256];
> +
> + sprintf(buf, "VMware PVSCSI storage adapter rev %d, req/cmp/msg rings: "
> + "%u/%u/%u pages, cmd_per_lun=%u", adapter->rev,
> + adapter->req_pages, adapter->cmp_pages, adapter->msg_pages,
> + pvscsi_template.cmd_per_lun);
> +
> + return buf;
> +}
> +
> +static struct scsi_host_template pvscsi_template = {
> + .module = THIS_MODULE,
> + .name = "VMware PVSCSI Host Adapter",
> + .proc_name = "pvscsi",
> + .info = pvscsi_info,
> + .queuecommand = pvscsi_queue,
> + .this_id = -1,
> + .sg_tablesize = PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT,
> + .dma_boundary = UINT_MAX,
> + .max_sectors = 0xffff,
> + .use_clustering = ENABLE_CLUSTERING,
> + .eh_abort_handler = pvscsi_abort,
> + .eh_device_reset_handler = pvscsi_device_reset,
> + .eh_bus_reset_handler = pvscsi_bus_reset,
> + .eh_host_reset_handler = pvscsi_host_reset,
> +};
> +
> +static void pvscsi_process_msg(const struct pvscsi_adapter *adapter,
> + const struct PVSCSIRingMsgDesc *e)
> +{
> + struct PVSCSIRingsState *s = adapter->rings_state;
> + struct Scsi_Host *host = adapter->host;
> + struct scsi_device *sdev;
> +
> + printk(KERN_INFO "pvscsi: msg type: 0x%x - MSG RING: %u/%u (%u) \n",
> + e->type, s->msgProdIdx, s->msgConsIdx, s->msgNumEntriesLog2);
> +
> + BUILD_BUG_ON(PVSCSI_MSG_LAST != 2);
> +
> + if (e->type == PVSCSI_MSG_DEV_ADDED) {
> + struct PVSCSIMsgDescDevStatusChanged *desc;
> + desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
> +
> + printk(KERN_INFO "pvscsi: msg: device added at scsi%u:%u:%u\n",
> + desc->bus, desc->target, desc->lun[1]);
> +
> + if (!scsi_host_get(host))
> + return;
> +
> + sdev = scsi_device_lookup(host, desc->bus, desc->target,
> + desc->lun[1]);
> + if (sdev) {
> + printk(KERN_INFO "pvscsi: device already exists\n");
> + scsi_device_put(sdev);
> + } else
> + scsi_add_device(adapter->host, desc->bus,
> + desc->target, desc->lun[1]);
> +
> + scsi_host_put(host);
> + } else if (e->type == PVSCSI_MSG_DEV_REMOVED) {
> + struct PVSCSIMsgDescDevStatusChanged *desc;
> + desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
> +
> + printk(KERN_INFO "pvscsi: msg: device removed at scsi%u:%u:%u\n",
> + desc->bus, desc->target, desc->lun[1]);
> +
> + if (!scsi_host_get(host))
> + return;
> +
> + sdev = scsi_device_lookup(host, desc->bus, desc->target,
> + desc->lun[1]);
> + if (sdev) {
> + scsi_remove_device(sdev);
> + scsi_device_put(sdev);
> + } else
> + printk(KERN_INFO "pvscsi: failed to lookup scsi%u:%u:%u\n",
> + desc->bus, desc->target, desc->lun[1]);
> +
> + scsi_host_put(host);
> + }
> +}
> +
> +static int pvscsi_msg_pending(const struct pvscsi_adapter *adapter)
> +{
> + struct PVSCSIRingsState *s = adapter->rings_state;
> +
> + return s->msgProdIdx != s->msgConsIdx;
> +}
> +
> +static void pvscsi_process_msg_ring(const struct pvscsi_adapter *adapter)
> +{
> + struct PVSCSIRingsState *s = adapter->rings_state;
> + struct PVSCSIRingMsgDesc *ring = adapter->msg_ring;
> + u32 msg_entries = s->msgNumEntriesLog2;
> +
> + while (pvscsi_msg_pending(adapter)) {
> + struct PVSCSIRingMsgDesc *e = ring + (s->msgConsIdx &
> + MASK(msg_entries));
> +
> + barrier();
> + pvscsi_process_msg(adapter, e);
> + barrier();
> + s->msgConsIdx++;
> + }
> +}
> +
> +static void pvscsi_msg_workqueue_handler(struct work_struct *data)
> +{
> + struct pvscsi_adapter *adapter;
> +
> + adapter = container_of(data, struct pvscsi_adapter, work);
> +
> + pvscsi_process_msg_ring(adapter);
> +}
> +
> +static int pvscsi_setup_msg_workqueue(struct pvscsi_adapter *adapter)
> +{
> + char name[32];
> +
> + if (!pvscsi_use_msg)
> + return 0;
> +
> + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND,
> + PVSCSI_CMD_SETUP_MSG_RING);
> +
> + if (pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_COMMAND_STATUS) == -1)
> + return 0;
> +
> + snprintf(name, sizeof(name), "pvscsi_wq_%u", adapter->host->host_no);
> +
> + adapter->workqueue = create_singlethread_workqueue(name);
> + if (!adapter->workqueue) {
> + printk(KERN_ERR "pvscsi: failed to create work queue\n");
> + return 0;
> + }
> + INIT_WORK(&adapter->work, pvscsi_msg_workqueue_handler);
> +
> + return 1;
> +}
> +
> +static irqreturn_t pvscsi_isr(int irq, void *devp)
> +{
> + struct pvscsi_adapter *adapter = devp;
> + int handled;
> +
> + if (adapter->use_msi || adapter->use_msix)
> + handled = true;
> + else {
> + u32 val = pvscsi_read_intr_status(adapter);
> + handled = (val & PVSCSI_INTR_ALL_SUPPORTED) != 0;
> + if (handled)
> + pvscsi_write_intr_status(devp, val);
> + }
> +
> + if (handled) {
> + unsigned long flags;
> +
> + spin_lock_irqsave(&adapter->hw_lock, flags);
> +
> + pvscsi_process_completion_ring(adapter);
> + if (adapter->use_msg && pvscsi_msg_pending(adapter))
> + queue_work(adapter->workqueue, &adapter->work);
> +
> + spin_unlock_irqrestore(&adapter->hw_lock, flags);
> + }
> +
> + return IRQ_RETVAL(handled);
> +}
> +
> +static void pvscsi_free_sgls(const struct pvscsi_adapter *adapter)
> +{
> + struct pvscsi_ctx *ctx = adapter->cmd_map;
> + unsigned i;
> +
> + for (i = 0; i < adapter->req_depth; ++i, ++ctx)
> + kfree(ctx->sgl);
> +}
> +
> +static int pvscsi_setup_msix(const struct pvscsi_adapter *adapter, int *irq)
> +{
> + struct msix_entry entry = { 0, PVSCSI_VECTOR_COMPLETION };
> + int ret;
> +
> + ret = pci_enable_msix(adapter->dev, &entry, 1);
> + if (ret)
> + return ret;
> +
> + *irq = entry.vector;
> +
> + return 0;
> +}
> +
> +static void pvscsi_shutdown_intr(struct pvscsi_adapter *adapter)
> +{
> + if (adapter->irq) {
> + free_irq(adapter->irq, adapter);
> + adapter->irq = 0;
> + }
> + if (adapter->use_msi) {
> + pci_disable_msi(adapter->dev);
> + adapter->use_msi = 0;
> + } else if (adapter->use_msix) {
> + pci_disable_msix(adapter->dev);
> + adapter->use_msix = 0;
> + }
> +}
> +
> +static void pvscsi_release_resources(struct pvscsi_adapter *adapter)
> +{
> + pvscsi_shutdown_intr(adapter);
> +
> + if (adapter->workqueue)
> + destroy_workqueue(adapter->workqueue);
> +
> + if (adapter->mmioBase)
> + pci_iounmap(adapter->dev, adapter->mmioBase);
> +
> + pci_release_regions(adapter->dev);
> +
> + if (adapter->cmd_map) {
> + pvscsi_free_sgls(adapter);
> + kfree(adapter->cmd_map);
> + }
> +
> + if (adapter->rings_state)
> + pci_free_consistent(adapter->dev, PAGE_SIZE,
> + adapter->rings_state, adapter->ringStatePA);
> +
> + if (adapter->req_ring)
> + pci_free_consistent(adapter->dev,
> + adapter->req_pages * PAGE_SIZE,
> + adapter->req_ring, adapter->reqRingPA);
> +
> + if (adapter->cmp_ring)
> + pci_free_consistent(adapter->dev,
> + adapter->cmp_pages * PAGE_SIZE,
> + adapter->cmp_ring, adapter->cmpRingPA);
> +
> + if (adapter->msg_ring)
> + pci_free_consistent(adapter->dev,
> + adapter->msg_pages * PAGE_SIZE,
> + adapter->msg_ring, adapter->msgRingPA);
> +}
> +
> +/*
> + * Allocate scatter gather lists.
> + *
> + * These are statically allocated. Trying to be clever was not worth it.
> + *
> + * Dynamic allocation can fail, and we can't go deeep into the memory
> + * allocator, since we're a SCSI driver, and trying too hard to allocate
> + * memory might generate disk I/O. We also don't want to fail disk I/O
> + * in that case because we can't get an allocation - the I/O could be
> + * trying to swap out data to free memory. Since that is pathological,
> + * just use a statically allocated scatter list.
> + *
> + */
> +static int __devinit pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
> +{
> + struct pvscsi_ctx *ctx;
> + int i;
> +
> + ctx = adapter->cmd_map;
> + BUILD_BUG_ON(sizeof(struct pvscsi_sg_list) > SGL_SIZE);
> +
> + for (i = 0; i < adapter->req_depth; ++i, ++ctx) {
> + ctx->sgl = kmalloc(SGL_SIZE, GFP_KERNEL);
> + ctx->sglPA = 0;
> + BUG_ON(!IS_ALIGNED(((unsigned long)ctx->sgl), PAGE_SIZE));
> + if (!ctx->sgl) {
> + for (; i >= 0; --i, --ctx) {
> + kfree(ctx->sgl);
> + ctx->sgl = NULL;
> + }
> + return -ENOMEM;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __devinit pvscsi_probe(struct pci_dev *pdev,
> + const struct pci_device_id *id)
> +{
> + struct pvscsi_adapter *adapter;
> + struct Scsi_Host *host;
> + unsigned int i;
> + int error;
> +
> + error = -ENODEV;
> +
> + if (pci_enable_device(pdev))
> + return error;
> +
> + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) == 0 &&
> + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) == 0) {
> + printk(KERN_INFO "pvscsi: using 64bit dma\n");
> + } else if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) == 0 &&
> + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) == 0) {
> + printk(KERN_INFO "pvscsi: using 32bit dma\n");
> + } else {
> + printk(KERN_ERR "pvscsi: failed to set DMA mask\n");
> + goto out_disable_device;
> + }
> +
> + pvscsi_template.can_queue =
> + min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
> + PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
> + pvscsi_template.cmd_per_lun =
> + min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);
> + host = scsi_host_alloc(&pvscsi_template, sizeof(struct pvscsi_adapter));
> + if (!host) {
> + printk(KERN_ERR "pvscsi: failed to allocate host\n");
> + goto out_disable_device;
> + }
> +
> + adapter = shost_priv(host);
> + memset(adapter, 0, sizeof(*adapter));
> + adapter->dev = pdev;
> + adapter->host = host;
> +
> + spin_lock_init(&adapter->hw_lock);
> +
> + host->max_channel = 0;
> + host->max_id = 16;
> + host->max_lun = 1;
> + host->max_cmd_len = 16;
> +
> + adapter->rev = pdev->revision;
> +
> + if (pci_request_regions(pdev, "pvscsi")) {
> + printk(KERN_ERR "pvscsi: pci memory selection failed\n");
> + goto out_free_host;
> + }
> +
> + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
> + if ((pci_resource_flags(pdev, i) & PCI_BASE_ADDRESS_SPACE_IO))
> + continue;
> +
> + if (pci_resource_len(pdev, i) < PVSCSI_MEM_SPACE_SIZE)
> + continue;
> +
> + break;
> + }
> +
> + if (i == DEVICE_COUNT_RESOURCE) {
> + printk(KERN_ERR "pvscsi: adapter has no suitable MMIO region\n");
> + goto out_release_resources;
> + }
> +
> + adapter->mmioBase = pci_iomap(pdev, i, PVSCSI_MEM_SPACE_SIZE);
> +
> + if (!adapter->mmioBase) {
> + printk(KERN_ERR "pvscsi: can't iomap for BAR %d memsize %lu\n",
> + i, PVSCSI_MEM_SPACE_SIZE);
> + goto out_release_resources;
> + }
> +
> + pci_set_master(pdev);
> + pci_set_drvdata(pdev, host);
> +
> + ll_adapter_reset(adapter);
> +
> + adapter->use_msg = pvscsi_setup_msg_workqueue(adapter);
> +
> + error = pvscsi_allocate_rings(adapter);
> + if (error) {
> + printk(KERN_ERR "pvscsi: unable to allocate ring memory\n");
> + goto out_release_resources;
> + }
> +
> + /*
> + * From this point on we should reset the adapter if anything goes
> + * wrong.
> + */
> + pvscsi_setup_all_rings(adapter);
> +
> + adapter->cmd_map = kcalloc(adapter->req_depth,
> + sizeof(struct pvscsi_ctx), GFP_KERNEL);
> + if (!adapter->cmd_map) {
> + printk(KERN_ERR "pvscsi: failed to allocate memory.\n");
> + error = -ENOMEM;
> + goto out_reset_adapter;
> + }
> +
> + INIT_LIST_HEAD(&adapter->cmd_pool);
> + for (i = 0; i < adapter->req_depth; i++) {
> + struct pvscsi_ctx *ctx = adapter->cmd_map + i;
> + list_add(&ctx->list, &adapter->cmd_pool);
> + }
> +
> + error = pvscsi_allocate_sg(adapter);
> + if (error) {
> + printk(KERN_ERR "pvscsi: unable to allocate s/g table\n");
> + goto out_reset_adapter;
> + }
> +
> + if (!pvscsi_disable_msix &&
> + pvscsi_setup_msix(adapter, &adapter->irq) == 0) {
> + printk(KERN_INFO "pvscsi: using MSI-X\n");
> + adapter->use_msix = 1;
> + } else if (!pvscsi_disable_msi && pci_enable_msi(pdev) == 0) {
> + printk(KERN_INFO "pvscsi: using MSI\n");
> + adapter->use_msi = 1;
> + adapter->irq = pdev->irq;
> + } else {
> + printk(KERN_INFO "pvscsi: using INTx\n");
> + adapter->irq = pdev->irq;
> + }
> +
> + error = request_irq(adapter->irq, pvscsi_isr, IRQF_SHARED,
> + "pvscsi", adapter);
> + if (error) {
> + printk(KERN_ERR "pvscsi: unable to request IRQ: %d\n", error);
> + adapter->irq = 0;
> + goto out_reset_adapter;
> + }
> +
> + error = scsi_add_host(host, &pdev->dev);
> + if (error) {
> + printk(KERN_ERR "pvscsi: scsi_add_host failed: %d\n", error);
> + goto out_reset_adapter;
> + }
> +
> + dev_info(&pdev->dev, "VMware PVSCSI rev %d host #%u\n",
> + adapter->rev, host->host_no);
> +
> + pvscsi_unmask_intr(adapter);
> +
> + scsi_scan_host(host);
> +
> + return 0;
> +
> +out_reset_adapter:
> + ll_adapter_reset(adapter);
> +out_release_resources:
> + pvscsi_release_resources(adapter);
> +out_free_host:
> + scsi_host_put(host);
> +out_disable_device:
> + pci_set_drvdata(pdev, NULL);
> + pci_disable_device(pdev);
> +
> + return error;
> +}
> +
> +static void __pvscsi_shutdown(struct pvscsi_adapter *adapter)
> +{
> + pvscsi_mask_intr(adapter);
> +
> + if (adapter->workqueue)
> + flush_workqueue(adapter->workqueue);
> +
> + pvscsi_shutdown_intr(adapter);
> +
> + pvscsi_process_request_ring(adapter);
> + pvscsi_process_completion_ring(adapter);
> + ll_adapter_reset(adapter);
> +}
> +
> +static void pvscsi_shutdown(struct pci_dev *dev)
> +{
> + struct Scsi_Host *host = pci_get_drvdata(dev);
> + struct pvscsi_adapter *adapter = shost_priv(host);
> +
> + __pvscsi_shutdown(adapter);
> +}
> +
> +static void pvscsi_remove(struct pci_dev *pdev)
> +{
> + struct Scsi_Host *host = pci_get_drvdata(pdev);
> + struct pvscsi_adapter *adapter = shost_priv(host);
> +
> + scsi_remove_host(host);
> +
> + __pvscsi_shutdown(adapter);
> + pvscsi_release_resources(adapter);
> +
> + scsi_host_put(host);
> +
> + pci_set_drvdata(pdev, NULL);
> + pci_disable_device(pdev);
> +}
> +
> +static struct pci_driver pvscsi_pci_driver = {
> + .name = "pvscsi",
> + .id_table = pvscsi_pci_tbl,
> + .probe = pvscsi_probe,
> + .remove = __devexit_p(pvscsi_remove),
> + .shutdown = pvscsi_shutdown,
> +};
> +
> +static int __init pvscsi_init(void)
> +{
> + pr_info("%s - version %s\n",
> + PVSCSI_LINUX_DRIVER_DESC, PVSCSI_DRIVER_VERSION_STRING);
> + return pci_register_driver(&pvscsi_pci_driver);
> +}
> +
> +static void __exit pvscsi_exit(void)
> +{
> + pci_unregister_driver(&pvscsi_pci_driver);
> +}
> +
> +module_init(pvscsi_init);
> +module_exit(pvscsi_exit);
> diff --git a/drivers/scsi/pvscsi.h b/drivers/scsi/pvscsi.h
> new file mode 100644
> index 0000000..9753eee
> --- /dev/null
> +++ b/drivers/scsi/pvscsi.h
> @@ -0,0 +1,397 @@
> +/*
> + * 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: Alok N Kataria <[email protected]>
> + *
> + */
> +
> +#ifndef _PVSCSI_H_
> +#define _PVSCSI_H_
> +
> +#include <linux/types.h>
> +
> +#define PVSCSI_DRIVER_VERSION_STRING "1.0.1.0-k"
> +
> +#define PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT 128
> +
> +#define MASK(n) ((1 << (n)) - 1) /* make an n-bit mask */
> +
> +#define PCI_VENDOR_ID_VMWARE 0x15AD
> +#define PCI_DEVICE_ID_VMWARE_PVSCSI 0x07C0
> +
> +/*
> + * 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 */
> +};
> +
> +/*
> + * Command descriptor for PVSCSI_CMD_RESET_DEVICE --
> + */
> +
> +struct PVSCSICmdDescResetDevice {
> + u32 target;
> + u8 lun[8];
> +} __packed;
> +
> +/*
> + * Command descriptor for PVSCSI_CMD_ABORT_CMD --
> + *
> + * - currently does not support specifying the LUN.
> + * - _pad should be 0.
> + */
> +
> +struct PVSCSICmdDescAbortCmd {
> + u64 context;
> + u32 target;
> + u32 _pad;
> +} __packed;
> +
> +/*
> + * 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 {
> + u32 reqRingNumPages;
> + u32 cmpRingNumPages;
> + u64 ringsStatePPN;
> + u64 reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
> + u64 cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
> +} __packed;
> +
> +/*
> + * 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(u32).
> + * - 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 {
> + u32 numPages;
> + u32 _pad;
> + u64 ringPPNs[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
> +} __packed;
> +
> +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 {
> + u32 type;
> + u32 args[31];
> +} __packed;
> +
> +struct PVSCSIMsgDescDevStatusChanged {
> + u32 type; /* PVSCSI_MSG_DEV _ADDED / _REMOVED */
> + u32 bus;
> + u32 target;
> + u8 lun[8];
> + u32 pad[27];
> +} __packed;
> +
> +/*
> + * 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 {
> + u32 reqProdIdx;
> + u32 reqConsIdx;
> + u32 reqNumEntriesLog2;
> +
> + u32 cmpProdIdx;
> + u32 cmpConsIdx;
> + u32 cmpNumEntriesLog2;
> +
> + u8 _pad[104];
> +
> + u32 msgProdIdx;
> + u32 msgConsIdx;
> + u32 msgNumEntriesLog2;
> +} __packed;
> +
> +/*
> + * 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 u32.
> + * 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)
> +
> +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;
> +
> +/*
> + * 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 {
> + u64 addr;
> + u32 length;
> + u32 flags;
> +} __packed;
> +
> +/*
> + * 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 {
> + u64 context;
> + u64 dataLen;
> + u32 senseLen;
> + u16 hostStatus;
> + u16 scsiStatus;
> + u32 _pad[2];
> +} __packed;
> +
> +/*
> + * 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 \
> + (PAGE_SIZE / sizeof(struct PVSCSIRingReqDesc))
> +
> +#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 * PAGE_SIZE)
> +
> +#endif /* _PVSCSI_H_ */
>

2009-10-02 00:49:18

by Chris Wright

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

* Alok Kataria ([email protected]) wrote:
> VMware PVSCSI driver - v5.

Were you still planning on renaming the driver to something less generic
(like vmw_pvscsi)?

thanks,
-chris

2009-10-02 01:43:26

by James Bottomley

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

On Thu, 2009-10-01 at 17:47 -0700, Chris Wright wrote:
> * Alok Kataria ([email protected]) wrote:
> > VMware PVSCSI driver - v5.
>
> Were you still planning on renaming the driver to something less generic
> (like vmw_pvscsi)?

Actually it's not a paravirt driver either, so something like
vmwarevscsi would be fine.

James

2009-10-02 04:37:54

by Alok Kataria

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.


On Thu, 2009-10-01 at 18:43 -0700, James Bottomley wrote:
> On Thu, 2009-10-01 at 17:47 -0700, Chris Wright wrote:
> > * Alok Kataria ([email protected]) wrote:
> > > VMware PVSCSI driver - v5.
> >
> > Were you still planning on renaming the driver to something less generic
> > (like vmw_pvscsi)?
>

Yeah I will rename it. Though, I will skip renaming all the function
names, they have a pvscsi_ prefix right now. Since they are defined
static it should be okay to keep them as is, I think.

Yeah, I will make sure that the proc file name and other printk prefixes
are modified with the new module name.

> Actually it's not a paravirt driver either, so something like
> vmwarevscsi would be fine.

I would prefer keeping pvscsi in the module name just so that existing
users are not confused, the adapter that the backend exports is named as
pvscsi too. So something like vmw_pvscsi should be good enough to be
non-generic at the same time being close to our existing module name.

Thanks,
Alok

2009-10-06 00:31:13

by Alok Kataria

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.


> Were you still planning on renaming the driver to something less generic
> (like vmw_pvscsi)?


Yep, below is a patch with the rename, the driver is now called
vmw_pvscsi. Do let me know if you have any other comments. Thanks.

VMware PVSCSI driver - v5.

Changelog (v5-v4)
- use bool for pvscsi _disable_msi, _disable_msix, _use_msg
- fix for 16 byte CDBs, max_cmd_len should be initialized to 16.
- bump the driver version.
- add <[email protected]> in the MAINTAINERS file.
- rebase to 2.6.32-rc3.
- rename the driver to vmw_pvscsi

Changelog (v4-v3)
- use size_t type for a loop iterator.
- use IS_ALIGNED instead of ~PAGE_MASK, for alignment checks
- use scsi_dma_ map/unmap API's
- reduce the pvscsi_info buffer to 256 bytes
- code cleanup in pvscsi_shutdown_intr

Changelog (v3-v2)
- use kmalloc for allocating sg_lists. And pci_map it for device access.
- re-use pdev->revision for setting up adapter->rev.

Changelog (v2-v1)
- use PCI_VDEVICE instead of PCI_DEVICE.
- use list_first_entry
- use parenthesis for every sizeof usage
- get rid of all #ifdef for CONFIG_PCI_MSI
- use PVSCSI_MEM_SPACE_SIZE while checking for MMIO resource len.
- use kcalloc instead of kmalloc.
- replaced a couple of scmd_printk usage with dev_dbg
- use dev_info and pr_info at couple of places.
- add a comment to pvscsi_map_context
- Add entry in MAINTAINERS.

Patch applies on top of 2.6.32-rc3.

--
SCSI driver for VMware's virtual HBA.

From: Alok N Kataria <[email protected]>

This is a driver for VMware's paravirtualized SCSI device,
which should improve disk performance for guests running
under control of VMware hypervisors that support such devices.

Signed-off-by: Alok N Kataria <[email protected]>

---

MAINTAINERS | 8
drivers/scsi/Kconfig | 8
drivers/scsi/Makefile | 1
drivers/scsi/vmw_pvscsi.c | 1401 +++++++++++++++++++++++++++++++++++++++++++++
drivers/scsi/vmw_pvscsi.h | 397 +++++++++++++
5 files changed, 1815 insertions(+), 0 deletions(-)
create mode 100644 drivers/scsi/vmw_pvscsi.c
create mode 100644 drivers/scsi/vmw_pvscsi.h


diff --git a/MAINTAINERS b/MAINTAINERS
index c450f3a..3ce4502 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5608,6 +5608,14 @@ S: Maintained
F: drivers/vlynq/vlynq.c
F: include/linux/vlynq.h

+VMware PVSCSI driver
+M: Alok Kataria <[email protected]>
+M: VMware PV-Drivers <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/scsi/vmw_pvscsi.c
+F: drivers/scsi/vmw_pvscsi.h
+
VOLTAGE AND CURRENT REGULATOR FRAMEWORK
M: Liam Girdwood <[email protected]>
M: Mark Brown <[email protected]>
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index 82bb3b2..ebfddff 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -620,6 +620,14 @@ config SCSI_FLASHPOINT
substantial, so users of MultiMaster Host Adapters may not
wish to include it.

+config VMWARE_PVSCSI
+ tristate "VMware PVSCSI driver support"
+ depends on PCI && SCSI && X86
+ help
+ This driver supports VMware's para virtualized SCSI HBA.
+ To compile this driver as a module, choose M here: the
+ module will be called vmw_pvscsi.
+
config LIBFC
tristate "LibFC module"
select SCSI_FC_ATTRS
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 61a94af..af41d8e 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -131,6 +131,7 @@ obj-$(CONFIG_PS3_ROM) += ps3rom.o
obj-$(CONFIG_SCSI_CXGB3_ISCSI) += libiscsi.o libiscsi_tcp.o cxgb3i/
obj-$(CONFIG_SCSI_BNX2_ISCSI) += libiscsi.o bnx2i/
obj-$(CONFIG_SCSI_PMCRAID) += pmcraid.o
+obj-$(CONFIG_VMWARE_PVSCSI) += vmw_pvscsi.o

obj-$(CONFIG_ARM) += arm/

diff --git a/drivers/scsi/vmw_pvscsi.c b/drivers/scsi/vmw_pvscsi.c
new file mode 100644
index 0000000..eabb432
--- /dev/null
+++ b/drivers/scsi/vmw_pvscsi.c
@@ -0,0 +1,1401 @@
+/*
+ * Linux driver for VMware's para-virtualized SCSI HBA.
+ *
+ * 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: Alok N Kataria <[email protected]>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/pci.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+
+#include "vmw_pvscsi.h"
+
+#define PVSCSI_LINUX_DRIVER_DESC "VMware PVSCSI driver"
+
+MODULE_DESCRIPTION(PVSCSI_LINUX_DRIVER_DESC);
+MODULE_AUTHOR("VMware, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(PVSCSI_DRIVER_VERSION_STRING);
+
+#define PVSCSI_DEFAULT_NUM_PAGES_PER_RING 8
+#define PVSCSI_DEFAULT_NUM_PAGES_MSG_RING 1
+#define PVSCSI_DEFAULT_QUEUE_DEPTH 64
+#define SGL_SIZE PAGE_SIZE
+
+#define pvscsi_dev(adapter) (&(adapter->dev->dev))
+
+struct pvscsi_sg_list {
+ struct PVSCSISGElement sge[PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT];
+};
+
+struct pvscsi_ctx {
+ /*
+ * The index of the context in cmd_map serves as the context ID for a
+ * 1-to-1 mapping completions back to requests.
+ */
+ struct scsi_cmnd *cmd;
+ struct pvscsi_sg_list *sgl;
+ struct list_head list;
+ dma_addr_t dataPA;
+ dma_addr_t sensePA;
+ dma_addr_t sglPA;
+};
+
+struct pvscsi_adapter {
+ char *mmioBase;
+ unsigned int irq;
+ u8 rev;
+ bool use_msi;
+ bool use_msix;
+ bool use_msg;
+
+ spinlock_t hw_lock;
+
+ struct workqueue_struct *workqueue;
+ struct work_struct work;
+
+ struct PVSCSIRingReqDesc *req_ring;
+ unsigned req_pages;
+ unsigned req_depth;
+ dma_addr_t reqRingPA;
+
+ struct PVSCSIRingCmpDesc *cmp_ring;
+ unsigned cmp_pages;
+ dma_addr_t cmpRingPA;
+
+ struct PVSCSIRingMsgDesc *msg_ring;
+ unsigned msg_pages;
+ dma_addr_t msgRingPA;
+
+ struct PVSCSIRingsState *rings_state;
+ dma_addr_t ringStatePA;
+
+ struct pci_dev *dev;
+ struct Scsi_Host *host;
+
+ struct list_head cmd_pool;
+ struct pvscsi_ctx *cmd_map;
+};
+
+
+/* Command line parameters */
+static int pvscsi_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_PER_RING;
+static int pvscsi_msg_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_MSG_RING;
+static int pvscsi_cmd_per_lun = PVSCSI_DEFAULT_QUEUE_DEPTH;
+static bool pvscsi_disable_msi;
+static bool pvscsi_disable_msix;
+static bool pvscsi_use_msg = true;
+
+#define PVSCSI_RW (S_IRUSR | S_IWUSR)
+
+module_param_named(ring_pages, pvscsi_ring_pages, int, PVSCSI_RW);
+MODULE_PARM_DESC(ring_pages, "Number of pages per req/cmp ring - (default="
+ __stringify(PVSCSI_DEFAULT_NUM_PAGES_PER_RING) ")");
+
+module_param_named(msg_ring_pages, pvscsi_msg_ring_pages, int, PVSCSI_RW);
+MODULE_PARM_DESC(msg_ring_pages, "Number of pages for the msg ring - (default="
+ __stringify(PVSCSI_DEFAULT_NUM_PAGES_MSG_RING) ")");
+
+module_param_named(cmd_per_lun, pvscsi_cmd_per_lun, int, PVSCSI_RW);
+MODULE_PARM_DESC(cmd_per_lun, "Maximum commands per lun - (default="
+ __stringify(PVSCSI_MAX_REQ_QUEUE_DEPTH) ")");
+
+module_param_named(disable_msi, pvscsi_disable_msi, bool, PVSCSI_RW);
+MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
+
+module_param_named(disable_msix, pvscsi_disable_msix, bool, PVSCSI_RW);
+MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default=0)");
+
+module_param_named(use_msg, pvscsi_use_msg, bool, PVSCSI_RW);
+MODULE_PARM_DESC(use_msg, "Use msg ring when available - (default=1)");
+
+static const struct pci_device_id pvscsi_pci_tbl[] = {
+ { PCI_VDEVICE(VMWARE, PCI_DEVICE_ID_VMWARE_PVSCSI) },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, pvscsi_pci_tbl);
+
+static struct pvscsi_ctx *
+pvscsi_find_context(const struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
+{
+ struct pvscsi_ctx *ctx, *end;
+
+ end = &adapter->cmd_map[adapter->req_depth];
+ for (ctx = adapter->cmd_map; ctx < end; ctx++)
+ if (ctx->cmd == cmd)
+ return ctx;
+
+ return NULL;
+}
+
+static struct pvscsi_ctx *
+pvscsi_acquire_context(struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
+{
+ struct pvscsi_ctx *ctx;
+
+ if (list_empty(&adapter->cmd_pool))
+ return NULL;
+
+ ctx = list_first_entry(&adapter->cmd_pool, struct pvscsi_ctx, list);
+ ctx->cmd = cmd;
+ list_del(&ctx->list);
+
+ return ctx;
+}
+
+static void pvscsi_release_context(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx)
+{
+ ctx->cmd = NULL;
+ list_add(&ctx->list, &adapter->cmd_pool);
+}
+
+/*
+ * Map a pvscsi_ctx struct to a context ID field value; we map to a simple
+ * non-zero integer. ctx always points to an entry in cmd_map array, hence
+ * the return value is always >=1.
+ */
+static u64 pvscsi_map_context(const struct pvscsi_adapter *adapter,
+ const struct pvscsi_ctx *ctx)
+{
+ return ctx - adapter->cmd_map + 1;
+}
+
+static struct pvscsi_ctx *
+pvscsi_get_context(const struct pvscsi_adapter *adapter, u64 context)
+{
+ return &adapter->cmd_map[context - 1];
+}
+
+static void pvscsi_reg_write(const struct pvscsi_adapter *adapter,
+ u32 offset, u32 val)
+{
+ writel(val, adapter->mmioBase + offset);
+}
+
+static u32 pvscsi_reg_read(const struct pvscsi_adapter *adapter, u32 offset)
+{
+ return readl(adapter->mmioBase + offset);
+}
+
+static u32 pvscsi_read_intr_status(const struct pvscsi_adapter *adapter)
+{
+ return pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_INTR_STATUS);
+}
+
+static void pvscsi_write_intr_status(const struct pvscsi_adapter *adapter,
+ u32 val)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_STATUS, val);
+}
+
+static void pvscsi_unmask_intr(const struct pvscsi_adapter *adapter)
+{
+ u32 intr_bits;
+
+ intr_bits = PVSCSI_INTR_CMPL_MASK;
+ if (adapter->use_msg)
+ intr_bits |= PVSCSI_INTR_MSG_MASK;
+
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, intr_bits);
+}
+
+static void pvscsi_mask_intr(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, 0);
+}
+
+static void pvscsi_write_cmd_desc(const struct pvscsi_adapter *adapter,
+ u32 cmd, const void *desc, size_t len)
+{
+ const u32 *ptr = desc;
+ size_t i;
+
+ len /= sizeof(*ptr);
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND, cmd);
+ for (i = 0; i < len; i++)
+ pvscsi_reg_write(adapter,
+ PVSCSI_REG_OFFSET_COMMAND_DATA, ptr[i]);
+}
+
+static void pvscsi_abort_cmd(const struct pvscsi_adapter *adapter,
+ const struct pvscsi_ctx *ctx)
+{
+ struct PVSCSICmdDescAbortCmd cmd = { 0 };
+
+ cmd.target = ctx->cmd->device->id;
+ cmd.context = pvscsi_map_context(adapter, ctx);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ABORT_CMD, &cmd, sizeof(cmd));
+}
+
+static void pvscsi_kick_rw_io(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_RW_IO, 0);
+}
+
+static void pvscsi_process_request_ring(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_NON_RW_IO, 0);
+}
+
+static int scsi_is_rw(unsigned char op)
+{
+ return op == READ_6 || op == WRITE_6 ||
+ op == READ_10 || op == WRITE_10 ||
+ op == READ_12 || op == WRITE_12 ||
+ op == READ_16 || op == WRITE_16;
+}
+
+static void pvscsi_kick_io(const struct pvscsi_adapter *adapter,
+ unsigned char op)
+{
+ if (scsi_is_rw(op))
+ pvscsi_kick_rw_io(adapter);
+ else
+ pvscsi_process_request_ring(adapter);
+}
+
+static void ll_adapter_reset(const struct pvscsi_adapter *adapter)
+{
+ dev_dbg(pvscsi_dev(adapter), "Adapter Reset on %p\n", adapter);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ADAPTER_RESET, NULL, 0);
+}
+
+static void ll_bus_reset(const struct pvscsi_adapter *adapter)
+{
+ dev_dbg(pvscsi_dev(adapter), "Reseting bus on %p\n", adapter);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_BUS, NULL, 0);
+}
+
+static void ll_device_reset(const struct pvscsi_adapter *adapter, u32 target)
+{
+ struct PVSCSICmdDescResetDevice cmd = { 0 };
+
+ dev_dbg(pvscsi_dev(adapter), "Reseting device: target=%u\n", target);
+
+ cmd.target = target;
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_DEVICE,
+ &cmd, sizeof(cmd));
+}
+
+static void pvscsi_create_sg(struct pvscsi_ctx *ctx,
+ struct scatterlist *sg, unsigned count)
+{
+ unsigned i;
+ struct PVSCSISGElement *sge;
+
+ BUG_ON(count > PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT);
+
+ sge = &ctx->sgl->sge[0];
+ for (i = 0; i < count; i++, sg++) {
+ sge[i].addr = sg_dma_address(sg);
+ sge[i].length = sg_dma_len(sg);
+ sge[i].flags = 0;
+ }
+}
+
+/*
+ * Map all data buffers for a command into PCI space and
+ * setup the scatter/gather list if needed.
+ */
+static void pvscsi_map_buffers(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd,
+ struct PVSCSIRingReqDesc *e)
+{
+ unsigned count;
+ unsigned bufflen = scsi_bufflen(cmd);
+ struct scatterlist *sg;
+
+ e->dataLen = bufflen;
+ e->dataAddr = 0;
+ if (bufflen == 0)
+ return;
+
+ sg = scsi_sglist(cmd);
+ count = scsi_sg_count(cmd);
+ if (count != 0) {
+ int segs = scsi_dma_map(cmd);
+ if (segs > 1) {
+ pvscsi_create_sg(ctx, sg, segs);
+
+ e->flags |= PVSCSI_FLAG_CMD_WITH_SG_LIST;
+ ctx->sglPA = pci_map_single(adapter->dev, ctx->sgl,
+ SGL_SIZE, PCI_DMA_TODEVICE);
+ e->dataAddr = ctx->sglPA;
+ } else
+ e->dataAddr = sg_dma_address(sg);
+ } else {
+ /*
+ * In case there is no S/G list, scsi_sglist points
+ * directly to the buffer.
+ */
+ ctx->dataPA = pci_map_single(adapter->dev, sg, bufflen,
+ cmd->sc_data_direction);
+ e->dataAddr = ctx->dataPA;
+ }
+}
+
+static void pvscsi_unmap_buffers(const struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx)
+{
+ struct scsi_cmnd *cmd;
+ unsigned bufflen;
+
+ cmd = ctx->cmd;
+ bufflen = scsi_bufflen(cmd);
+
+ if (bufflen != 0) {
+ unsigned count = scsi_sg_count(cmd);
+
+ if (count != 0) {
+ scsi_dma_unmap(cmd);
+ if (ctx->sglPA) {
+ pci_unmap_single(adapter->dev, ctx->sglPA,
+ SGL_SIZE, PCI_DMA_TODEVICE);
+ ctx->sglPA = 0;
+ }
+ } else
+ pci_unmap_single(adapter->dev, ctx->dataPA, bufflen,
+ cmd->sc_data_direction);
+ }
+ if (cmd->sense_buffer)
+ pci_unmap_single(adapter->dev, ctx->sensePA,
+ SCSI_SENSE_BUFFERSIZE, PCI_DMA_FROMDEVICE);
+}
+
+static int __devinit pvscsi_allocate_rings(struct pvscsi_adapter *adapter)
+{
+ adapter->rings_state = pci_alloc_consistent(adapter->dev, PAGE_SIZE,
+ &adapter->ringStatePA);
+ if (!adapter->rings_state)
+ return -ENOMEM;
+
+ adapter->req_pages = min(PVSCSI_MAX_NUM_PAGES_REQ_RING,
+ pvscsi_ring_pages);
+ adapter->req_depth = adapter->req_pages
+ * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ adapter->req_ring = pci_alloc_consistent(adapter->dev,
+ adapter->req_pages * PAGE_SIZE,
+ &adapter->reqRingPA);
+ if (!adapter->req_ring)
+ return -ENOMEM;
+
+ adapter->cmp_pages = min(PVSCSI_MAX_NUM_PAGES_CMP_RING,
+ pvscsi_ring_pages);
+ adapter->cmp_ring = pci_alloc_consistent(adapter->dev,
+ adapter->cmp_pages * PAGE_SIZE,
+ &adapter->cmpRingPA);
+ if (!adapter->cmp_ring)
+ return -ENOMEM;
+
+ BUG_ON(!IS_ALIGNED(adapter->ringStatePA, PAGE_SIZE));
+ BUG_ON(!IS_ALIGNED(adapter->reqRingPA, PAGE_SIZE));
+ BUG_ON(!IS_ALIGNED(adapter->cmpRingPA, PAGE_SIZE));
+
+ if (!adapter->use_msg)
+ return 0;
+
+ adapter->msg_pages = min(PVSCSI_MAX_NUM_PAGES_MSG_RING,
+ pvscsi_msg_ring_pages);
+ adapter->msg_ring = pci_alloc_consistent(adapter->dev,
+ adapter->msg_pages * PAGE_SIZE,
+ &adapter->msgRingPA);
+ if (!adapter->msg_ring)
+ return -ENOMEM;
+ BUG_ON(!IS_ALIGNED(adapter->msgRingPA, PAGE_SIZE));
+
+ return 0;
+}
+
+static void pvscsi_setup_all_rings(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSICmdDescSetupRings cmd = { 0 };
+ dma_addr_t base;
+ unsigned i;
+
+ cmd.ringsStatePPN = adapter->ringStatePA >> PAGE_SHIFT;
+ cmd.reqRingNumPages = adapter->req_pages;
+ cmd.cmpRingNumPages = adapter->cmp_pages;
+
+ base = adapter->reqRingPA;
+ for (i = 0; i < adapter->req_pages; i++) {
+ cmd.reqRingPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+
+ base = adapter->cmpRingPA;
+ for (i = 0; i < adapter->cmp_pages; i++) {
+ cmd.cmpRingPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+
+ memset(adapter->rings_state, 0, PAGE_SIZE);
+ memset(adapter->req_ring, 0, adapter->req_pages * PAGE_SIZE);
+ memset(adapter->cmp_ring, 0, adapter->cmp_pages * PAGE_SIZE);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_RINGS,
+ &cmd, sizeof(cmd));
+
+ if (adapter->use_msg) {
+ struct PVSCSICmdDescSetupMsgRing cmd_msg = { 0 };
+
+ cmd_msg.numPages = adapter->msg_pages;
+
+ base = adapter->msgRingPA;
+ for (i = 0; i < adapter->msg_pages; i++) {
+ cmd_msg.ringPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+ memset(adapter->msg_ring, 0, adapter->msg_pages * PAGE_SIZE);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_MSG_RING,
+ &cmd_msg, sizeof(cmd_msg));
+ }
+}
+
+/*
+ * Pull a completion descriptor off and pass the completion back
+ * to the SCSI mid layer.
+ */
+static void pvscsi_complete_request(struct pvscsi_adapter *adapter,
+ const struct PVSCSIRingCmpDesc *e)
+{
+ struct pvscsi_ctx *ctx;
+ struct scsi_cmnd *cmd;
+ u32 btstat = e->hostStatus;
+ u32 sdstat = e->scsiStatus;
+
+ ctx = pvscsi_get_context(adapter, e->context);
+ cmd = ctx->cmd;
+ pvscsi_unmap_buffers(adapter, ctx);
+ pvscsi_release_context(adapter, ctx);
+ cmd->result = 0;
+
+ if (sdstat != SAM_STAT_GOOD &&
+ (btstat == BTSTAT_SUCCESS ||
+ btstat == BTSTAT_LINKED_COMMAND_COMPLETED ||
+ btstat == BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG)) {
+ cmd->result = (DID_OK << 16) | sdstat;
+ if (sdstat == SAM_STAT_CHECK_CONDITION && cmd->sense_buffer)
+ cmd->result |= (DRIVER_SENSE << 24);
+ } else
+ switch (btstat) {
+ case BTSTAT_SUCCESS:
+ case BTSTAT_LINKED_COMMAND_COMPLETED:
+ case BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG:
+ /* If everything went fine, let's move on.. */
+ cmd->result = (DID_OK << 16);
+ break;
+
+ case BTSTAT_DATARUN:
+ case BTSTAT_DATA_UNDERRUN:
+ /* Report residual data in underruns */
+ scsi_set_resid(cmd, scsi_bufflen(cmd) - e->dataLen);
+ cmd->result = (DID_ERROR << 16);
+ break;
+
+ case BTSTAT_SELTIMEO:
+ /* Our emulation returns this for non-connected devs */
+ cmd->result = (DID_BAD_TARGET << 16);
+ break;
+
+ case BTSTAT_LUNMISMATCH:
+ case BTSTAT_TAGREJECT:
+ case BTSTAT_BADMSG:
+ cmd->result = (DRIVER_INVALID << 24);
+ /* fall through */
+
+ case BTSTAT_HAHARDWARE:
+ case BTSTAT_INVPHASE:
+ case BTSTAT_HATIMEOUT:
+ case BTSTAT_NORESPONSE:
+ case BTSTAT_DISCONNECT:
+ case BTSTAT_HASOFTWARE:
+ case BTSTAT_BUSFREE:
+ case BTSTAT_SENSFAILED:
+ cmd->result |= (DID_ERROR << 16);
+ break;
+
+ case BTSTAT_SENTRST:
+ case BTSTAT_RECVRST:
+ case BTSTAT_BUSRESET:
+ cmd->result = (DID_RESET << 16);
+ break;
+
+ case BTSTAT_ABORTQUEUE:
+ cmd->result = (DID_ABORT << 16);
+ break;
+
+ case BTSTAT_SCSIPARITY:
+ cmd->result = (DID_PARITY << 16);
+ break;
+
+ default:
+ cmd->result = (DID_ERROR << 16);
+ scmd_printk(KERN_DEBUG, cmd,
+ "Unknown completion status: 0x%x\n",
+ btstat);
+ }
+
+ dev_dbg(&cmd->device->sdev_gendev,
+ "cmd=%p %x ctx=%p result=0x%x status=0x%x,%x\n",
+ cmd, cmd->cmnd[0], ctx, cmd->result, btstat, sdstat);
+
+ cmd->scsi_done(cmd);
+}
+
+/*
+ * barrier usage : Since the PVSCSI device is emulated, there could be cases
+ * where we may want to serialize some accesses between the driver and the
+ * emulation layer. We use compiler barriers instead of the more expensive
+ * memory barriers because PVSCSI is only supported on X86 which has strong
+ * memory access ordering.
+ */
+static void pvscsi_process_completion_ring(struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct PVSCSIRingCmpDesc *ring = adapter->cmp_ring;
+ u32 cmp_entries = s->cmpNumEntriesLog2;
+
+ while (s->cmpConsIdx != s->cmpProdIdx) {
+ struct PVSCSIRingCmpDesc *e = ring + (s->cmpConsIdx &
+ MASK(cmp_entries));
+ /*
+ * This barrier() ensures that *e is not dereferenced while
+ * the device emulation still writes data into the slot.
+ * Since the device emulation advances s->cmpProdIdx only after
+ * updating the slot we want to check it first.
+ */
+ barrier();
+ pvscsi_complete_request(adapter, e);
+ /*
+ * This barrier() ensures that compiler doesn't reorder write
+ * to s->cmpConsIdx before the read of (*e) inside
+ * pvscsi_complete_request. Otherwise, device emulation may
+ * overwrite *e before we had a chance to read it.
+ */
+ barrier();
+ s->cmpConsIdx++;
+ }
+}
+
+/*
+ * Translate a Linux SCSI request into a request ring entry.
+ */
+static int pvscsi_queue_ring(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd)
+{
+ struct PVSCSIRingsState *s;
+ struct PVSCSIRingReqDesc *e;
+ struct scsi_device *sdev;
+ u32 req_entries;
+
+ s = adapter->rings_state;
+ sdev = cmd->device;
+ req_entries = s->reqNumEntriesLog2;
+
+ /*
+ * If this condition holds, we might have room on the request ring, but
+ * we might not have room on the completion ring for the response.
+ * However, we have already ruled out this possibility - we would not
+ * have successfully allocated a context if it were true, since we only
+ * have one context per request entry. Check for it anyway, since it
+ * would be a serious bug.
+ */
+ if (s->reqProdIdx - s->cmpConsIdx >= 1 << req_entries) {
+ scmd_printk(KERN_ERR, cmd, "vmw_pvscsi: "
+ "ring full: reqProdIdx=%d cmpConsIdx=%d\n",
+ s->reqProdIdx, s->cmpConsIdx);
+ return -1;
+ }
+
+ e = adapter->req_ring + (s->reqProdIdx & MASK(req_entries));
+
+ e->bus = sdev->channel;
+ e->target = sdev->id;
+ memset(e->lun, 0, sizeof(e->lun));
+ e->lun[1] = sdev->lun;
+
+ if (cmd->sense_buffer) {
+ ctx->sensePA = pci_map_single(adapter->dev, cmd->sense_buffer,
+ SCSI_SENSE_BUFFERSIZE,
+ PCI_DMA_FROMDEVICE);
+ e->senseAddr = ctx->sensePA;
+ e->senseLen = SCSI_SENSE_BUFFERSIZE;
+ } else {
+ e->senseLen = 0;
+ e->senseAddr = 0;
+ }
+ e->cdbLen = cmd->cmd_len;
+ e->vcpuHint = smp_processor_id();
+ memcpy(e->cdb, cmd->cmnd, e->cdbLen);
+
+ e->tag = SIMPLE_QUEUE_TAG;
+ if (sdev->tagged_supported &&
+ (cmd->tag == HEAD_OF_QUEUE_TAG ||
+ cmd->tag == ORDERED_QUEUE_TAG))
+ e->tag = cmd->tag;
+
+ if (cmd->sc_data_direction == DMA_FROM_DEVICE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_TOHOST;
+ else if (cmd->sc_data_direction == DMA_TO_DEVICE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_TODEVICE;
+ else if (cmd->sc_data_direction == DMA_NONE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_NONE;
+ else
+ e->flags = 0;
+
+ pvscsi_map_buffers(adapter, ctx, cmd, e);
+
+ e->context = pvscsi_map_context(adapter, ctx);
+
+ barrier();
+
+ s->reqProdIdx++;
+
+ return 0;
+}
+
+static int pvscsi_queue(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ struct pvscsi_ctx *ctx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ ctx = pvscsi_acquire_context(adapter, cmd);
+ if (!ctx || pvscsi_queue_ring(adapter, ctx, cmd) != 0) {
+ if (ctx)
+ pvscsi_release_context(adapter, ctx);
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ return SCSI_MLQUEUE_HOST_BUSY;
+ }
+
+ cmd->scsi_done = done;
+
+ dev_dbg(&cmd->device->sdev_gendev,
+ "queued cmd %p, ctx %p, op=%x\n", cmd, ctx, cmd->cmnd[0]);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ pvscsi_kick_io(adapter, cmd->cmnd[0]);
+
+ return 0;
+}
+
+static int pvscsi_abort(struct scsi_cmnd *cmd)
+{
+ struct pvscsi_adapter *adapter = shost_priv(cmd->device->host);
+ struct pvscsi_ctx *ctx;
+ unsigned long flags;
+
+ scmd_printk(KERN_DEBUG, cmd, "task abort on host %u, %p\n",
+ adapter->host->host_no, cmd);
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ /*
+ * Poll the completion ring first - we might be trying to abort
+ * a command that is waiting to be dispatched in the completion ring.
+ */
+ pvscsi_process_completion_ring(adapter);
+
+ /*
+ * If there is no context for the command, it either already succeeded
+ * or else was never properly issued. Not our problem.
+ */
+ ctx = pvscsi_find_context(adapter, cmd);
+ if (!ctx) {
+ scmd_printk(KERN_DEBUG, cmd, "Failed to abort cmd %p\n", cmd);
+ goto out;
+ }
+
+ pvscsi_abort_cmd(adapter, ctx);
+
+ pvscsi_process_completion_ring(adapter);
+
+out:
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ return SUCCESS;
+}
+
+/*
+ * Abort all outstanding requests. This is only safe to use if the completion
+ * ring will never be walked again or the device has been reset, because it
+ * destroys the 1-1 mapping between context field passed to emulation and our
+ * request structure.
+ */
+static void pvscsi_reset_all(struct pvscsi_adapter *adapter)
+{
+ unsigned i;
+
+ for (i = 0; i < adapter->req_depth; i++) {
+ struct pvscsi_ctx *ctx = &adapter->cmd_map[i];
+ struct scsi_cmnd *cmd = ctx->cmd;
+ if (cmd) {
+ scmd_printk(KERN_ERR, cmd,
+ "Forced reset on cmd %p\n", cmd);
+ pvscsi_unmap_buffers(adapter, ctx);
+ pvscsi_release_context(adapter, ctx);
+ cmd->result = (DID_RESET << 16);
+ cmd->scsi_done(cmd);
+ }
+ }
+}
+
+static int pvscsi_host_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+ bool use_msg;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI Host reset\n");
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ use_msg = adapter->use_msg;
+
+ if (use_msg) {
+ adapter->use_msg = 0;
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ /*
+ * Now that we know that the ISR won't add more work on the
+ * workqueue we can safely flush any outstanding work.
+ */
+ flush_workqueue(adapter->workqueue);
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+ }
+
+ /*
+ * We're going to tear down the entire ring structure and set it back
+ * up, so stalling new requests until all completions are flushed and
+ * the rings are back in place.
+ */
+
+ pvscsi_process_request_ring(adapter);
+
+ ll_adapter_reset(adapter);
+
+ /*
+ * Now process any completions. Note we do this AFTER adapter reset,
+ * which is strange, but stops races where completions get posted
+ * between processing the ring and issuing the reset. The backend will
+ * not touch the ring memory after reset, so the immediately pre-reset
+ * completion ring state is still valid.
+ */
+ pvscsi_process_completion_ring(adapter);
+
+ pvscsi_reset_all(adapter);
+ adapter->use_msg = use_msg;
+ pvscsi_setup_all_rings(adapter);
+ pvscsi_unmask_intr(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static int pvscsi_bus_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI Bus reset\n");
+
+ /*
+ * We don't want to queue new requests for this bus after
+ * flushing all pending requests to emulation, since new
+ * requests could then sneak in during this bus reset phase,
+ * so take the lock now.
+ */
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_request_ring(adapter);
+ ll_bus_reset(adapter);
+ pvscsi_process_completion_ring(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static int pvscsi_device_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI device reset on scsi%u:%u\n",
+ host->host_no, cmd->device->id);
+
+ /*
+ * We don't want to queue new requests for this device after flushing
+ * all pending requests to emulation, since new requests could then
+ * sneak in during this device reset phase, so take the lock now.
+ */
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_request_ring(adapter);
+ ll_device_reset(adapter, cmd->device->id);
+ pvscsi_process_completion_ring(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static struct scsi_host_template pvscsi_template;
+
+static const char *pvscsi_info(struct Scsi_Host *host)
+{
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ static char buf[256];
+
+ sprintf(buf, "VMware PVSCSI storage adapter rev %d, req/cmp/msg rings: "
+ "%u/%u/%u pages, cmd_per_lun=%u", adapter->rev,
+ adapter->req_pages, adapter->cmp_pages, adapter->msg_pages,
+ pvscsi_template.cmd_per_lun);
+
+ return buf;
+}
+
+static struct scsi_host_template pvscsi_template = {
+ .module = THIS_MODULE,
+ .name = "VMware PVSCSI Host Adapter",
+ .proc_name = "vmw_pvscsi",
+ .info = pvscsi_info,
+ .queuecommand = pvscsi_queue,
+ .this_id = -1,
+ .sg_tablesize = PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT,
+ .dma_boundary = UINT_MAX,
+ .max_sectors = 0xffff,
+ .use_clustering = ENABLE_CLUSTERING,
+ .eh_abort_handler = pvscsi_abort,
+ .eh_device_reset_handler = pvscsi_device_reset,
+ .eh_bus_reset_handler = pvscsi_bus_reset,
+ .eh_host_reset_handler = pvscsi_host_reset,
+};
+
+static void pvscsi_process_msg(const struct pvscsi_adapter *adapter,
+ const struct PVSCSIRingMsgDesc *e)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct Scsi_Host *host = adapter->host;
+ struct scsi_device *sdev;
+
+ printk(KERN_INFO "vmw_pvscsi: msg type: 0x%x - MSG RING: %u/%u (%u) \n",
+ e->type, s->msgProdIdx, s->msgConsIdx, s->msgNumEntriesLog2);
+
+ BUILD_BUG_ON(PVSCSI_MSG_LAST != 2);
+
+ if (e->type == PVSCSI_MSG_DEV_ADDED) {
+ struct PVSCSIMsgDescDevStatusChanged *desc;
+ desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
+
+ printk(KERN_INFO
+ "vmw_pvscsi: msg: device added at scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ if (!scsi_host_get(host))
+ return;
+
+ sdev = scsi_device_lookup(host, desc->bus, desc->target,
+ desc->lun[1]);
+ if (sdev) {
+ printk(KERN_INFO "vmw_pvscsi: device already exists\n");
+ scsi_device_put(sdev);
+ } else
+ scsi_add_device(adapter->host, desc->bus,
+ desc->target, desc->lun[1]);
+
+ scsi_host_put(host);
+ } else if (e->type == PVSCSI_MSG_DEV_REMOVED) {
+ struct PVSCSIMsgDescDevStatusChanged *desc;
+ desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
+
+ printk(KERN_INFO
+ "vmw_pvscsi: msg: device removed at scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ if (!scsi_host_get(host))
+ return;
+
+ sdev = scsi_device_lookup(host, desc->bus, desc->target,
+ desc->lun[1]);
+ if (sdev) {
+ scsi_remove_device(sdev);
+ scsi_device_put(sdev);
+ } else
+ printk(KERN_INFO
+ "vmw_pvscsi: failed to lookup scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ scsi_host_put(host);
+ }
+}
+
+static int pvscsi_msg_pending(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+
+ return s->msgProdIdx != s->msgConsIdx;
+}
+
+static void pvscsi_process_msg_ring(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct PVSCSIRingMsgDesc *ring = adapter->msg_ring;
+ u32 msg_entries = s->msgNumEntriesLog2;
+
+ while (pvscsi_msg_pending(adapter)) {
+ struct PVSCSIRingMsgDesc *e = ring + (s->msgConsIdx &
+ MASK(msg_entries));
+
+ barrier();
+ pvscsi_process_msg(adapter, e);
+ barrier();
+ s->msgConsIdx++;
+ }
+}
+
+static void pvscsi_msg_workqueue_handler(struct work_struct *data)
+{
+ struct pvscsi_adapter *adapter;
+
+ adapter = container_of(data, struct pvscsi_adapter, work);
+
+ pvscsi_process_msg_ring(adapter);
+}
+
+static int pvscsi_setup_msg_workqueue(struct pvscsi_adapter *adapter)
+{
+ char name[32];
+
+ if (!pvscsi_use_msg)
+ return 0;
+
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND,
+ PVSCSI_CMD_SETUP_MSG_RING);
+
+ if (pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_COMMAND_STATUS) == -1)
+ return 0;
+
+ snprintf(name, sizeof(name),
+ "vmw_pvscsi_wq_%u", adapter->host->host_no);
+
+ adapter->workqueue = create_singlethread_workqueue(name);
+ if (!adapter->workqueue) {
+ printk(KERN_ERR "vmw_pvscsi: failed to create work queue\n");
+ return 0;
+ }
+ INIT_WORK(&adapter->work, pvscsi_msg_workqueue_handler);
+
+ return 1;
+}
+
+static irqreturn_t pvscsi_isr(int irq, void *devp)
+{
+ struct pvscsi_adapter *adapter = devp;
+ int handled;
+
+ if (adapter->use_msi || adapter->use_msix)
+ handled = true;
+ else {
+ u32 val = pvscsi_read_intr_status(adapter);
+ handled = (val & PVSCSI_INTR_ALL_SUPPORTED) != 0;
+ if (handled)
+ pvscsi_write_intr_status(devp, val);
+ }
+
+ if (handled) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_completion_ring(adapter);
+ if (adapter->use_msg && pvscsi_msg_pending(adapter))
+ queue_work(adapter->workqueue, &adapter->work);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ }
+
+ return IRQ_RETVAL(handled);
+}
+
+static void pvscsi_free_sgls(const struct pvscsi_adapter *adapter)
+{
+ struct pvscsi_ctx *ctx = adapter->cmd_map;
+ unsigned i;
+
+ for (i = 0; i < adapter->req_depth; ++i, ++ctx)
+ kfree(ctx->sgl);
+}
+
+static int pvscsi_setup_msix(const struct pvscsi_adapter *adapter, int *irq)
+{
+ struct msix_entry entry = { 0, PVSCSI_VECTOR_COMPLETION };
+ int ret;
+
+ ret = pci_enable_msix(adapter->dev, &entry, 1);
+ if (ret)
+ return ret;
+
+ *irq = entry.vector;
+
+ return 0;
+}
+
+static void pvscsi_shutdown_intr(struct pvscsi_adapter *adapter)
+{
+ if (adapter->irq) {
+ free_irq(adapter->irq, adapter);
+ adapter->irq = 0;
+ }
+ if (adapter->use_msi) {
+ pci_disable_msi(adapter->dev);
+ adapter->use_msi = 0;
+ } else if (adapter->use_msix) {
+ pci_disable_msix(adapter->dev);
+ adapter->use_msix = 0;
+ }
+}
+
+static void pvscsi_release_resources(struct pvscsi_adapter *adapter)
+{
+ pvscsi_shutdown_intr(adapter);
+
+ if (adapter->workqueue)
+ destroy_workqueue(adapter->workqueue);
+
+ if (adapter->mmioBase)
+ pci_iounmap(adapter->dev, adapter->mmioBase);
+
+ pci_release_regions(adapter->dev);
+
+ if (adapter->cmd_map) {
+ pvscsi_free_sgls(adapter);
+ kfree(adapter->cmd_map);
+ }
+
+ if (adapter->rings_state)
+ pci_free_consistent(adapter->dev, PAGE_SIZE,
+ adapter->rings_state, adapter->ringStatePA);
+
+ if (adapter->req_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->req_pages * PAGE_SIZE,
+ adapter->req_ring, adapter->reqRingPA);
+
+ if (adapter->cmp_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->cmp_pages * PAGE_SIZE,
+ adapter->cmp_ring, adapter->cmpRingPA);
+
+ if (adapter->msg_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->msg_pages * PAGE_SIZE,
+ adapter->msg_ring, adapter->msgRingPA);
+}
+
+/*
+ * Allocate scatter gather lists.
+ *
+ * These are statically allocated. Trying to be clever was not worth it.
+ *
+ * Dynamic allocation can fail, and we can't go deeep into the memory
+ * allocator, since we're a SCSI driver, and trying too hard to allocate
+ * memory might generate disk I/O. We also don't want to fail disk I/O
+ * in that case because we can't get an allocation - the I/O could be
+ * trying to swap out data to free memory. Since that is pathological,
+ * just use a statically allocated scatter list.
+ *
+ */
+static int __devinit pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
+{
+ struct pvscsi_ctx *ctx;
+ int i;
+
+ ctx = adapter->cmd_map;
+ BUILD_BUG_ON(sizeof(struct pvscsi_sg_list) > SGL_SIZE);
+
+ for (i = 0; i < adapter->req_depth; ++i, ++ctx) {
+ ctx->sgl = kmalloc(SGL_SIZE, GFP_KERNEL);
+ ctx->sglPA = 0;
+ BUG_ON(!IS_ALIGNED(((unsigned long)ctx->sgl), PAGE_SIZE));
+ if (!ctx->sgl) {
+ for (; i >= 0; --i, --ctx) {
+ kfree(ctx->sgl);
+ ctx->sgl = NULL;
+ }
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static int __devinit pvscsi_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct pvscsi_adapter *adapter;
+ struct Scsi_Host *host;
+ unsigned int i;
+ int error;
+
+ error = -ENODEV;
+
+ if (pci_enable_device(pdev))
+ return error;
+
+ if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) == 0 &&
+ pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using 64bit dma\n");
+ } else if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) == 0 &&
+ pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using 32bit dma\n");
+ } else {
+ printk(KERN_ERR "vmw_pvscsi: failed to set DMA mask\n");
+ goto out_disable_device;
+ }
+
+ pvscsi_template.can_queue =
+ min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
+ PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ pvscsi_template.cmd_per_lun =
+ min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);
+ host = scsi_host_alloc(&pvscsi_template, sizeof(struct pvscsi_adapter));
+ if (!host) {
+ printk(KERN_ERR "vmw_pvscsi: failed to allocate host\n");
+ goto out_disable_device;
+ }
+
+ adapter = shost_priv(host);
+ memset(adapter, 0, sizeof(*adapter));
+ adapter->dev = pdev;
+ adapter->host = host;
+
+ spin_lock_init(&adapter->hw_lock);
+
+ host->max_channel = 0;
+ host->max_id = 16;
+ host->max_lun = 1;
+ host->max_cmd_len = 16;
+
+ adapter->rev = pdev->revision;
+
+ if (pci_request_regions(pdev, "vmw_pvscsi")) {
+ printk(KERN_ERR "vmw_pvscsi: pci memory selection failed\n");
+ goto out_free_host;
+ }
+
+ for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
+ if ((pci_resource_flags(pdev, i) & PCI_BASE_ADDRESS_SPACE_IO))
+ continue;
+
+ if (pci_resource_len(pdev, i) < PVSCSI_MEM_SPACE_SIZE)
+ continue;
+
+ break;
+ }
+
+ if (i == DEVICE_COUNT_RESOURCE) {
+ printk(KERN_ERR
+ "vmw_pvscsi: adapter has no suitable MMIO region\n");
+ goto out_release_resources;
+ }
+
+ adapter->mmioBase = pci_iomap(pdev, i, PVSCSI_MEM_SPACE_SIZE);
+
+ if (!adapter->mmioBase) {
+ printk(KERN_ERR
+ "vmw_pvscsi: can't iomap for BAR %d memsize %lu\n",
+ i, PVSCSI_MEM_SPACE_SIZE);
+ goto out_release_resources;
+ }
+
+ pci_set_master(pdev);
+ pci_set_drvdata(pdev, host);
+
+ ll_adapter_reset(adapter);
+
+ adapter->use_msg = pvscsi_setup_msg_workqueue(adapter);
+
+ error = pvscsi_allocate_rings(adapter);
+ if (error) {
+ printk(KERN_ERR "vmw_pvscsi: unable to allocate ring memory\n");
+ goto out_release_resources;
+ }
+
+ /*
+ * From this point on we should reset the adapter if anything goes
+ * wrong.
+ */
+ pvscsi_setup_all_rings(adapter);
+
+ adapter->cmd_map = kcalloc(adapter->req_depth,
+ sizeof(struct pvscsi_ctx), GFP_KERNEL);
+ if (!adapter->cmd_map) {
+ printk(KERN_ERR "vmw_pvscsi: failed to allocate memory.\n");
+ error = -ENOMEM;
+ goto out_reset_adapter;
+ }
+
+ INIT_LIST_HEAD(&adapter->cmd_pool);
+ for (i = 0; i < adapter->req_depth; i++) {
+ struct pvscsi_ctx *ctx = adapter->cmd_map + i;
+ list_add(&ctx->list, &adapter->cmd_pool);
+ }
+
+ error = pvscsi_allocate_sg(adapter);
+ if (error) {
+ printk(KERN_ERR "vmw_pvscsi: unable to allocate s/g table\n");
+ goto out_reset_adapter;
+ }
+
+ if (!pvscsi_disable_msix &&
+ pvscsi_setup_msix(adapter, &adapter->irq) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using MSI-X\n");
+ adapter->use_msix = 1;
+ } else if (!pvscsi_disable_msi && pci_enable_msi(pdev) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using MSI\n");
+ adapter->use_msi = 1;
+ adapter->irq = pdev->irq;
+ } else {
+ printk(KERN_INFO "vmw_pvscsi: using INTx\n");
+ adapter->irq = pdev->irq;
+ }
+
+ error = request_irq(adapter->irq, pvscsi_isr, IRQF_SHARED,
+ "vmw_pvscsi", adapter);
+ if (error) {
+ printk(KERN_ERR
+ "vmw_pvscsi: unable to request IRQ: %d\n", error);
+ adapter->irq = 0;
+ goto out_reset_adapter;
+ }
+
+ error = scsi_add_host(host, &pdev->dev);
+ if (error) {
+ printk(KERN_ERR
+ "vmw_pvscsi: scsi_add_host failed: %d\n", error);
+ goto out_reset_adapter;
+ }
+
+ dev_info(&pdev->dev, "VMware PVSCSI rev %d host #%u\n",
+ adapter->rev, host->host_no);
+
+ pvscsi_unmask_intr(adapter);
+
+ scsi_scan_host(host);
+
+ return 0;
+
+out_reset_adapter:
+ ll_adapter_reset(adapter);
+out_release_resources:
+ pvscsi_release_resources(adapter);
+out_free_host:
+ scsi_host_put(host);
+out_disable_device:
+ pci_set_drvdata(pdev, NULL);
+ pci_disable_device(pdev);
+
+ return error;
+}
+
+static void __pvscsi_shutdown(struct pvscsi_adapter *adapter)
+{
+ pvscsi_mask_intr(adapter);
+
+ if (adapter->workqueue)
+ flush_workqueue(adapter->workqueue);
+
+ pvscsi_shutdown_intr(adapter);
+
+ pvscsi_process_request_ring(adapter);
+ pvscsi_process_completion_ring(adapter);
+ ll_adapter_reset(adapter);
+}
+
+static void pvscsi_shutdown(struct pci_dev *dev)
+{
+ struct Scsi_Host *host = pci_get_drvdata(dev);
+ struct pvscsi_adapter *adapter = shost_priv(host);
+
+ __pvscsi_shutdown(adapter);
+}
+
+static void pvscsi_remove(struct pci_dev *pdev)
+{
+ struct Scsi_Host *host = pci_get_drvdata(pdev);
+ struct pvscsi_adapter *adapter = shost_priv(host);
+
+ scsi_remove_host(host);
+
+ __pvscsi_shutdown(adapter);
+ pvscsi_release_resources(adapter);
+
+ scsi_host_put(host);
+
+ pci_set_drvdata(pdev, NULL);
+ pci_disable_device(pdev);
+}
+
+static struct pci_driver pvscsi_pci_driver = {
+ .name = "vmw_pvscsi",
+ .id_table = pvscsi_pci_tbl,
+ .probe = pvscsi_probe,
+ .remove = __devexit_p(pvscsi_remove),
+ .shutdown = pvscsi_shutdown,
+};
+
+static int __init pvscsi_init(void)
+{
+ pr_info("%s - version %s\n",
+ PVSCSI_LINUX_DRIVER_DESC, PVSCSI_DRIVER_VERSION_STRING);
+ return pci_register_driver(&pvscsi_pci_driver);
+}
+
+static void __exit pvscsi_exit(void)
+{
+ pci_unregister_driver(&pvscsi_pci_driver);
+}
+
+module_init(pvscsi_init);
+module_exit(pvscsi_exit);
diff --git a/drivers/scsi/vmw_pvscsi.h b/drivers/scsi/vmw_pvscsi.h
new file mode 100644
index 0000000..62e36e7
--- /dev/null
+++ b/drivers/scsi/vmw_pvscsi.h
@@ -0,0 +1,397 @@
+/*
+ * 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: Alok N Kataria <[email protected]>
+ *
+ */
+
+#ifndef _VMW_PVSCSI_H_
+#define _VMW_PVSCSI_H_
+
+#include <linux/types.h>
+
+#define PVSCSI_DRIVER_VERSION_STRING "1.0.1.0-k"
+
+#define PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT 128
+
+#define MASK(n) ((1 << (n)) - 1) /* make an n-bit mask */
+
+#define PCI_VENDOR_ID_VMWARE 0x15AD
+#define PCI_DEVICE_ID_VMWARE_PVSCSI 0x07C0
+
+/*
+ * 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 */
+};
+
+/*
+ * Command descriptor for PVSCSI_CMD_RESET_DEVICE --
+ */
+
+struct PVSCSICmdDescResetDevice {
+ u32 target;
+ u8 lun[8];
+} __packed;
+
+/*
+ * Command descriptor for PVSCSI_CMD_ABORT_CMD --
+ *
+ * - currently does not support specifying the LUN.
+ * - _pad should be 0.
+ */
+
+struct PVSCSICmdDescAbortCmd {
+ u64 context;
+ u32 target;
+ u32 _pad;
+} __packed;
+
+/*
+ * 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 {
+ u32 reqRingNumPages;
+ u32 cmpRingNumPages;
+ u64 ringsStatePPN;
+ u64 reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ u64 cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+} __packed;
+
+/*
+ * 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(u32).
+ * - 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 {
+ u32 numPages;
+ u32 _pad;
+ u64 ringPPNs[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
+} __packed;
+
+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 {
+ u32 type;
+ u32 args[31];
+} __packed;
+
+struct PVSCSIMsgDescDevStatusChanged {
+ u32 type; /* PVSCSI_MSG_DEV _ADDED / _REMOVED */
+ u32 bus;
+ u32 target;
+ u8 lun[8];
+ u32 pad[27];
+} __packed;
+
+/*
+ * 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 {
+ u32 reqProdIdx;
+ u32 reqConsIdx;
+ u32 reqNumEntriesLog2;
+
+ u32 cmpProdIdx;
+ u32 cmpConsIdx;
+ u32 cmpNumEntriesLog2;
+
+ u8 _pad[104];
+
+ u32 msgProdIdx;
+ u32 msgConsIdx;
+ u32 msgNumEntriesLog2;
+} __packed;
+
+/*
+ * 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 u32.
+ * 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)
+
+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;
+
+/*
+ * 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 {
+ u64 addr;
+ u32 length;
+ u32 flags;
+} __packed;
+
+/*
+ * 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 {
+ u64 context;
+ u64 dataLen;
+ u32 senseLen;
+ u16 hostStatus;
+ u16 scsiStatus;
+ u32 _pad[2];
+} __packed;
+
+/*
+ * 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 \
+ (PAGE_SIZE / sizeof(struct PVSCSIRingReqDesc))
+
+#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 * PAGE_SIZE)
+
+#endif /* _VMW_PVSCSI_H_ */

2009-10-13 05:40:05

by Chris Wright

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

mostly just nits

* Alok Kataria ([email protected]) wrote:
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>

shouldn't be needed

> +#include <linux/types.h>

shouldn't be needed

> +#include <linux/interrupt.h>
> +#include <linux/workqueue.h>
> +#include <linux/pci.h>
> +
> +#include <scsi/scsi.h>
> +#include <scsi/scsi_host.h>
> +#include <scsi/scsi_cmnd.h>
> +#include <scsi/scsi_device.h>
> +
> +#include "vmw_pvscsi.h"
> +
> +#define PVSCSI_LINUX_DRIVER_DESC "VMware PVSCSI driver"
> +
> +MODULE_DESCRIPTION(PVSCSI_LINUX_DRIVER_DESC);
> +MODULE_AUTHOR("VMware, Inc.");
> +MODULE_LICENSE("GPL");
> +MODULE_VERSION(PVSCSI_DRIVER_VERSION_STRING);
> +
> +#define PVSCSI_DEFAULT_NUM_PAGES_PER_RING 8
> +#define PVSCSI_DEFAULT_NUM_PAGES_MSG_RING 1
> +#define PVSCSI_DEFAULT_QUEUE_DEPTH 64
> +#define SGL_SIZE PAGE_SIZE
> +
> +#define pvscsi_dev(adapter) (&(adapter->dev->dev))

easy to make it static inline and get some type checking for free

> +struct pvscsi_sg_list {
> + struct PVSCSISGElement sge[PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT];
> +};
> +
> +struct pvscsi_ctx {
> + /*
> + * The index of the context in cmd_map serves as the context ID for a
> + * 1-to-1 mapping completions back to requests.
> + */
> + struct scsi_cmnd *cmd;
> + struct pvscsi_sg_list *sgl;
> + struct list_head list;
> + dma_addr_t dataPA;
> + dma_addr_t sensePA;
> + dma_addr_t sglPA;
> +};
> +
> +struct pvscsi_adapter {
> + char *mmioBase;
> + unsigned int irq;
> + u8 rev;
> + bool use_msi;
> + bool use_msix;
> + bool use_msg;
> +
> + spinlock_t hw_lock;
> +
> + struct workqueue_struct *workqueue;
> + struct work_struct work;
> +
> + struct PVSCSIRingReqDesc *req_ring;
> + unsigned req_pages;
> + unsigned req_depth;
> + dma_addr_t reqRingPA;
> +
> + struct PVSCSIRingCmpDesc *cmp_ring;
> + unsigned cmp_pages;
> + dma_addr_t cmpRingPA;
> +
> + struct PVSCSIRingMsgDesc *msg_ring;
> + unsigned msg_pages;
> + dma_addr_t msgRingPA;
> +
> + struct PVSCSIRingsState *rings_state;
> + dma_addr_t ringStatePA;
> +
> + struct pci_dev *dev;
> + struct Scsi_Host *host;
> +
> + struct list_head cmd_pool;
> + struct pvscsi_ctx *cmd_map;
> +};
> +
> +
> +/* Command line parameters */
> +static int pvscsi_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_PER_RING;
> +static int pvscsi_msg_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_MSG_RING;
> +static int pvscsi_cmd_per_lun = PVSCSI_DEFAULT_QUEUE_DEPTH;
> +static bool pvscsi_disable_msi;
> +static bool pvscsi_disable_msix;
> +static bool pvscsi_use_msg = true;
> +
> +#define PVSCSI_RW (S_IRUSR | S_IWUSR)
> +
> +module_param_named(ring_pages, pvscsi_ring_pages, int, PVSCSI_RW);
> +MODULE_PARM_DESC(ring_pages, "Number of pages per req/cmp ring - (default="
> + __stringify(PVSCSI_DEFAULT_NUM_PAGES_PER_RING) ")");
> +
> +module_param_named(msg_ring_pages, pvscsi_msg_ring_pages, int, PVSCSI_RW);
> +MODULE_PARM_DESC(msg_ring_pages, "Number of pages for the msg ring - (default="
> + __stringify(PVSCSI_DEFAULT_NUM_PAGES_MSG_RING) ")");
> +
> +module_param_named(cmd_per_lun, pvscsi_cmd_per_lun, int, PVSCSI_RW);
> +MODULE_PARM_DESC(cmd_per_lun, "Maximum commands per lun - (default="
> + __stringify(PVSCSI_MAX_REQ_QUEUE_DEPTH) ")");
> +
> +module_param_named(disable_msi, pvscsi_disable_msi, bool, PVSCSI_RW);
> +MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
> +
> +module_param_named(disable_msix, pvscsi_disable_msix, bool, PVSCSI_RW);
> +MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default=0)");
> +
> +module_param_named(use_msg, pvscsi_use_msg, bool, PVSCSI_RW);
> +MODULE_PARM_DESC(use_msg, "Use msg ring when available - (default=1)");
> +
> +static const struct pci_device_id pvscsi_pci_tbl[] = {
> + { PCI_VDEVICE(VMWARE, PCI_DEVICE_ID_VMWARE_PVSCSI) },
> + { 0 }
> +};
> +
> +MODULE_DEVICE_TABLE(pci, pvscsi_pci_tbl);
> +
> +static struct pvscsi_ctx *
> +pvscsi_find_context(const struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
> +{
> + struct pvscsi_ctx *ctx, *end;
> +
> + end = &adapter->cmd_map[adapter->req_depth];
> + for (ctx = adapter->cmd_map; ctx < end; ctx++)
> + if (ctx->cmd == cmd)
> + return ctx;
> +
> + return NULL;
> +}
> +
> +static struct pvscsi_ctx *
> +pvscsi_acquire_context(struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
> +{
> + struct pvscsi_ctx *ctx;
> +
> + if (list_empty(&adapter->cmd_pool))
> + return NULL;
> +
> + ctx = list_first_entry(&adapter->cmd_pool, struct pvscsi_ctx, list);
> + ctx->cmd = cmd;
> + list_del(&ctx->list);
> +
> + return ctx;
> +}
> +
> +static void pvscsi_release_context(struct pvscsi_adapter *adapter,
> + struct pvscsi_ctx *ctx)
> +{
> + ctx->cmd = NULL;
> + list_add(&ctx->list, &adapter->cmd_pool);
> +}

These list manipulations are protected by hw_lock? Looks like all cases
are covered.

<snip>
> +/*
> + * Allocate scatter gather lists.
> + *
> + * These are statically allocated. Trying to be clever was not worth it.
> + *
> + * Dynamic allocation can fail, and we can't go deeep into the memory
> + * allocator, since we're a SCSI driver, and trying too hard to allocate
> + * memory might generate disk I/O. We also don't want to fail disk I/O
> + * in that case because we can't get an allocation - the I/O could be
> + * trying to swap out data to free memory. Since that is pathological,
> + * just use a statically allocated scatter list.
> + *
> + */
> +static int __devinit pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
> +{
> + struct pvscsi_ctx *ctx;
> + int i;
> +
> + ctx = adapter->cmd_map;
> + BUILD_BUG_ON(sizeof(struct pvscsi_sg_list) > SGL_SIZE);
> +
> + for (i = 0; i < adapter->req_depth; ++i, ++ctx) {
> + ctx->sgl = kmalloc(SGL_SIZE, GFP_KERNEL);
> + ctx->sglPA = 0;
> + BUG_ON(!IS_ALIGNED(((unsigned long)ctx->sgl), PAGE_SIZE));

Why not simply allocate a page? Seems different allocator or debugging
options could trigger this.

> + if (!ctx->sgl) {
> + for (; i >= 0; --i, --ctx) {
> + kfree(ctx->sgl);
> + ctx->sgl = NULL;
> + }
> + return -ENOMEM;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int __devinit pvscsi_probe(struct pci_dev *pdev,
> + const struct pci_device_id *id)
> +{
> + struct pvscsi_adapter *adapter;
> + struct Scsi_Host *host;
> + unsigned int i;
> + int error;
> +
> + error = -ENODEV;
> +
> + if (pci_enable_device(pdev))
> + return error;

looks mmio only, pci_enable_device_mem()

> +
> + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) == 0 &&
> + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) == 0) {
> + printk(KERN_INFO "vmw_pvscsi: using 64bit dma\n");
> + } else if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) == 0 &&
> + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) == 0) {
> + printk(KERN_INFO "vmw_pvscsi: using 32bit dma\n");
> + } else {
> + printk(KERN_ERR "vmw_pvscsi: failed to set DMA mask\n");
> + goto out_disable_device;
> + }
> +
> + pvscsi_template.can_queue =
> + min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
> + PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
> + pvscsi_template.cmd_per_lun =
> + min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);

When/how are these tunables used? Are they still useful?

> + host = scsi_host_alloc(&pvscsi_template, sizeof(struct pvscsi_adapter));
> + if (!host) {
> + printk(KERN_ERR "vmw_pvscsi: failed to allocate host\n");
> + goto out_disable_device;
> + }
> +
> + adapter = shost_priv(host);
> + memset(adapter, 0, sizeof(*adapter));
> + adapter->dev = pdev;
> + adapter->host = host;
> +
> + spin_lock_init(&adapter->hw_lock);
> +
> + host->max_channel = 0;
> + host->max_id = 16;
> + host->max_lun = 1;
> + host->max_cmd_len = 16;
> +
> + adapter->rev = pdev->revision;
> +
> + if (pci_request_regions(pdev, "vmw_pvscsi")) {
> + printk(KERN_ERR "vmw_pvscsi: pci memory selection failed\n");
> + goto out_free_host;
> + }
> +
> + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
> + if ((pci_resource_flags(pdev, i) & PCI_BASE_ADDRESS_SPACE_IO))
> + continue;
> +
> + if (pci_resource_len(pdev, i) < PVSCSI_MEM_SPACE_SIZE)
> + continue;
> +
> + break;
> + }
> +
> + if (i == DEVICE_COUNT_RESOURCE) {
> + printk(KERN_ERR
> + "vmw_pvscsi: adapter has no suitable MMIO region\n");
> + goto out_release_resources;
> + }

Could simplify and just do pci_request_selected_regions.

> + adapter->mmioBase = pci_iomap(pdev, i, PVSCSI_MEM_SPACE_SIZE);
> +
> + if (!adapter->mmioBase) {
> + printk(KERN_ERR
> + "vmw_pvscsi: can't iomap for BAR %d memsize %lu\n",
> + i, PVSCSI_MEM_SPACE_SIZE);
> + goto out_release_resources;
> + }
> +
> + pci_set_master(pdev);
> + pci_set_drvdata(pdev, host);
> +
> + ll_adapter_reset(adapter);
> +
> + adapter->use_msg = pvscsi_setup_msg_workqueue(adapter);
> +
> + error = pvscsi_allocate_rings(adapter);
> + if (error) {
> + printk(KERN_ERR "vmw_pvscsi: unable to allocate ring memory\n");
> + goto out_release_resources;
> + }
> +
> + /*
> + * From this point on we should reset the adapter if anything goes
> + * wrong.
> + */
> + pvscsi_setup_all_rings(adapter);
> +
> + adapter->cmd_map = kcalloc(adapter->req_depth,
> + sizeof(struct pvscsi_ctx), GFP_KERNEL);
> + if (!adapter->cmd_map) {
> + printk(KERN_ERR "vmw_pvscsi: failed to allocate memory.\n");
> + error = -ENOMEM;
> + goto out_reset_adapter;
> + }
> +
> + INIT_LIST_HEAD(&adapter->cmd_pool);
> + for (i = 0; i < adapter->req_depth; i++) {
> + struct pvscsi_ctx *ctx = adapter->cmd_map + i;
> + list_add(&ctx->list, &adapter->cmd_pool);
> + }
> +
> + error = pvscsi_allocate_sg(adapter);
> + if (error) {
> + printk(KERN_ERR "vmw_pvscsi: unable to allocate s/g table\n");
> + goto out_reset_adapter;
> + }
> +
> + if (!pvscsi_disable_msix &&
> + pvscsi_setup_msix(adapter, &adapter->irq) == 0) {
> + printk(KERN_INFO "vmw_pvscsi: using MSI-X\n");
> + adapter->use_msix = 1;
> + } else if (!pvscsi_disable_msi && pci_enable_msi(pdev) == 0) {
> + printk(KERN_INFO "vmw_pvscsi: using MSI\n");
> + adapter->use_msi = 1;
> + adapter->irq = pdev->irq;
> + } else {
> + printk(KERN_INFO "vmw_pvscsi: using INTx\n");
> + adapter->irq = pdev->irq;
> + }
> +
> + error = request_irq(adapter->irq, pvscsi_isr, IRQF_SHARED,
> + "vmw_pvscsi", adapter);

Typically IRQF_SHARED w/ INTx, not MSI and MSI-X.

2009-10-13 14:36:48

by James Bottomley

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

On Mon, 2009-10-05 at 17:30 -0700, Alok Kataria wrote:
> > Were you still planning on renaming the driver to something less generic
> > (like vmw_pvscsi)?
>
>
> Yep, below is a patch with the rename, the driver is now called
> vmw_pvscsi. Do let me know if you have any other comments. Thanks.

I'm still curious to know what the 'p' in the name stands for. Surely
this is a fully virtual driver, not a paravirtual one?

James

2009-10-13 16:46:10

by Jeremy Fitzhardinge

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

On 10/13/09 07:35, James Bottomley wrote:
>> Yep, below is a patch with the rename, the driver is now called
>> vmw_pvscsi. Do let me know if you have any other comments. Thanks.
>>
> I'm still curious to know what the 'p' in the name stands for. Surely
> this is a fully virtual driver, not a paravirtual one?
>

I would assume that "pv" does stand for paravirtual. The terminology is
a bit screwy: "fully virtualized" means that the device looks like a
real hardware device that is being emulated. "Paravirtualized" means
that it is a completely virtual device which isn't an emulation of
anything; the guests know this this and talk to it accordingly.

J

2009-10-13 17:19:28

by Alok Kataria

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

Hi,

On Tue, 2009-10-13 at 09:45 -0700, Jeremy Fitzhardinge wrote:
> On 10/13/09 07:35, James Bottomley wrote:
> >> Yep, below is a patch with the rename, the driver is now called
> >> vmw_pvscsi. Do let me know if you have any other comments. Thanks.
> >>
> > I'm still curious to know what the 'p' in the name stands for. Surely
> > this is a fully virtual driver, not a paravirtual one?
> >

There is a very thin line of difference between para-virtualized and
fully virtualized in this case, and it depends on how we are looking at
it. This device uses the PCI bus for communication rather than
implementing a virtualized bus of some sought and hence is more like any
other PCI device. Though it's not like we are emulating an existing
device, VMware implemented its own device backend which has a particular
ABI, hence this is not a fully virtualized solution either.
The reason we chose to name the backend as pvscsi is since the solution
is para-virtual in nature.

As I said earlier, we would prefer to keep pvscsi in the name since the
backend storage adapter is called as PVSCSI and it will be better to
keep the confusion down to minimum if the driver is named something
similar.

Finally, I will like to add that these are just names I don't think we
need to be too picky about them, the driver is prefixed with "vmw",
hence people do know that the driver is VMware specific.

Thanks,
Alok

>
> I would assume that "pv" does stand for paravirtual. The terminology is
> a bit screwy: "fully virtualized" means that the device looks like a
> real hardware device that is being emulated. "Paravirtualized" means
> that it is a completely virtual device which isn't an emulation of
> anything; the guests know this this and talk to it accordingly.





> J
>

2009-10-13 21:28:54

by Alok Kataria

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

Hi Chris,

Thanks for taking a look.

On Mon, 2009-10-12 at 22:37 -0700, Chris Wright wrote:
> mostly just nits
>
> * Alok Kataria ([email protected]) wrote:
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/moduleparam.h>
>
> shouldn't be needed
>
> > +#include <linux/types.h>
>
> shouldn't be needed

Removed.
>
> > +#include <linux/interrupt.h>
> > +#include <linux/workqueue.h>
> > +#include <linux/pci.h>
> > +
> > +#include <scsi/scsi.h>
> > +#include <scsi/scsi_host.h>
> > +#include <scsi/scsi_cmnd.h>
> > +#include <scsi/scsi_device.h>
> > +
> > +#include "vmw_pvscsi.h"
> > +
> > +#define PVSCSI_LINUX_DRIVER_DESC "VMware PVSCSI driver"
> > +
> > +MODULE_DESCRIPTION(PVSCSI_LINUX_DRIVER_DESC);
> > +MODULE_AUTHOR("VMware, Inc.");
> > +MODULE_LICENSE("GPL");
> > +MODULE_VERSION(PVSCSI_DRIVER_VERSION_STRING);
> > +
> > +#define PVSCSI_DEFAULT_NUM_PAGES_PER_RING 8
> > +#define PVSCSI_DEFAULT_NUM_PAGES_MSG_RING 1
> > +#define PVSCSI_DEFAULT_QUEUE_DEPTH 64
> > +#define SGL_SIZE PAGE_SIZE
> > +
> > +#define pvscsi_dev(adapter) (&(adapter->dev->dev))
>
> easy to make it static inline and get some type checking for free

Done.

>
> > +
> > +static struct pvscsi_ctx *
> > +pvscsi_acquire_context(struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
> > +{
> > + struct pvscsi_ctx *ctx;
> > +
> > + if (list_empty(&adapter->cmd_pool))
> > + return NULL;
> > +
> > + ctx = list_first_entry(&adapter->cmd_pool, struct pvscsi_ctx, list);
> > + ctx->cmd = cmd;
> > + list_del(&ctx->list);
> > +
> > + return ctx;
> > +}
> > +
> > +static void pvscsi_release_context(struct pvscsi_adapter *adapter,
> > + struct pvscsi_ctx *ctx)
> > +{
> > + ctx->cmd = NULL;
> > + list_add(&ctx->list, &adapter->cmd_pool);
> > +}
>
> These list manipulations are protected by hw_lock? Looks like all cases
> are covered.
>

Yep.

> <snip>
> > +/*
> > + * Allocate scatter gather lists.
> > + *
> > + * These are statically allocated. Trying to be clever was not worth it.
> > + *
> > + * Dynamic allocation can fail, and we can't go deeep into the memory
> > + * allocator, since we're a SCSI driver, and trying too hard to allocate
> > + * memory might generate disk I/O. We also don't want to fail disk I/O
> > + * in that case because we can't get an allocation - the I/O could be
> > + * trying to swap out data to free memory. Since that is pathological,
> > + * just use a statically allocated scatter list.
> > + *
> > + */
> > +static int __devinit pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
> > +{
> > + struct pvscsi_ctx *ctx;
> > + int i;
> > +
> > + ctx = adapter->cmd_map;
> > + BUILD_BUG_ON(sizeof(struct pvscsi_sg_list) > SGL_SIZE);
> > +
> > + for (i = 0; i < adapter->req_depth; ++i, ++ctx) {
> > + ctx->sgl = kmalloc(SGL_SIZE, GFP_KERNEL);
> > + ctx->sglPA = 0;
> > + BUG_ON(!IS_ALIGNED(((unsigned long)ctx->sgl), PAGE_SIZE));
>
> Why not simply allocate a page? Seems different allocator or debugging
> options could trigger this.

Done.

> > +
> > +static int __devinit pvscsi_probe(struct pci_dev *pdev,
> > + const struct pci_device_id *id)
> > +{
> > + struct pvscsi_adapter *adapter;
> > + struct Scsi_Host *host;
> > + unsigned int i;
> > + int error;
> > +
> > + error = -ENODEV;
> > +
> > + if (pci_enable_device(pdev))
> > + return error;
>
> looks mmio only, pci_enable_device_mem()

We have a IOBAR as well though the driver doesn't use it.
Hence, I will skip this change since it is more future proof this way.
>
> > +
> > + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) == 0 &&
> > + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) == 0) {
> > + printk(KERN_INFO "vmw_pvscsi: using 64bit dma\n");
> > + } else if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) == 0 &&
> > + pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) == 0) {
> > + printk(KERN_INFO "vmw_pvscsi: using 32bit dma\n");
> > + } else {
> > + printk(KERN_ERR "vmw_pvscsi: failed to set DMA mask\n");
> > + goto out_disable_device;
> > + }
> > +
> > + pvscsi_template.can_queue =
> > + min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
> > + PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
> > + pvscsi_template.cmd_per_lun =
> > + min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);
>
> When/how are these tunables used? Are they still useful?

cmd_per_lun, is a commandline parameter.

>
> > + host = scsi_host_alloc(&pvscsi_template, sizeof(struct pvscsi_adapter));
> > + if (!host) {
> > + printk(KERN_ERR "vmw_pvscsi: failed to allocate host\n");
> > + goto out_disable_device;
> > + }
> > +
> > + adapter = shost_priv(host);
> > + memset(adapter, 0, sizeof(*adapter));
> > + adapter->dev = pdev;
> > + adapter->host = host;
> > +
> > + spin_lock_init(&adapter->hw_lock);
> > +
> > + host->max_channel = 0;
> > + host->max_id = 16;
> > + host->max_lun = 1;
> > + host->max_cmd_len = 16;
> > +
> > + adapter->rev = pdev->revision;
> > +
> > + if (pci_request_regions(pdev, "vmw_pvscsi")) {
> > + printk(KERN_ERR "vmw_pvscsi: pci memory selection failed\n");
> > + goto out_free_host;
> > + }
> > +
> > + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
> > + if ((pci_resource_flags(pdev, i) & PCI_BASE_ADDRESS_SPACE_IO))
> > + continue;
> > +
> > + if (pci_resource_len(pdev, i) < PVSCSI_MEM_SPACE_SIZE)
> > + continue;
> > +
> > + break;
> > + }
> > +
> > + if (i == DEVICE_COUNT_RESOURCE) {
> > + printk(KERN_ERR
> > + "vmw_pvscsi: adapter has no suitable MMIO region\n");
> > + goto out_release_resources;
> > + }
>
> Could simplify and just do pci_request_selected_regions.

this method is more future proof, that is if we decide to export some
more bars or anything of that sought. So, will keep it as is.
>
> > + adapter->mmioBase = pci_iomap(pdev, i, PVSCSI_MEM_SPACE_SIZE);
> > +
> > + if (!adapter->mmioBase) {
> > + printk(KERN_ERR
> > + "vmw_pvscsi: can't iomap for BAR %d memsize %lu\n",
> > + i, PVSCSI_MEM_SPACE_SIZE);
> > + goto out_release_resources;
> > + }
> > +
> > + pci_set_master(pdev);
> > + pci_set_drvdata(pdev, host);
> > +
> > + ll_adapter_reset(adapter);
> > +
> > + adapter->use_msg = pvscsi_setup_msg_workqueue(adapter);
> > +
> > + error = pvscsi_allocate_rings(adapter);
> > + if (error) {
> > + printk(KERN_ERR "vmw_pvscsi: unable to allocate ring memory\n");
> > + goto out_release_resources;
> > + }
> > +
> > + /*
> > + * From this point on we should reset the adapter if anything goes
> > + * wrong.
> > + */
> > + pvscsi_setup_all_rings(adapter);
> > +
> > + adapter->cmd_map = kcalloc(adapter->req_depth,
> > + sizeof(struct pvscsi_ctx), GFP_KERNEL);
> > + if (!adapter->cmd_map) {
> > + printk(KERN_ERR "vmw_pvscsi: failed to allocate memory.\n");
> > + error = -ENOMEM;
> > + goto out_reset_adapter;
> > + }
> > +
> > + INIT_LIST_HEAD(&adapter->cmd_pool);
> > + for (i = 0; i < adapter->req_depth; i++) {
> > + struct pvscsi_ctx *ctx = adapter->cmd_map + i;
> > + list_add(&ctx->list, &adapter->cmd_pool);
> > + }
> > +
> > + error = pvscsi_allocate_sg(adapter);
> > + if (error) {
> > + printk(KERN_ERR "vmw_pvscsi: unable to allocate s/g table\n");
> > + goto out_reset_adapter;
> > + }
> > +
> > + if (!pvscsi_disable_msix &&
> > + pvscsi_setup_msix(adapter, &adapter->irq) == 0) {
> > + printk(KERN_INFO "vmw_pvscsi: using MSI-X\n");
> > + adapter->use_msix = 1;
> > + } else if (!pvscsi_disable_msi && pci_enable_msi(pdev) == 0) {
> > + printk(KERN_INFO "vmw_pvscsi: using MSI\n");
> > + adapter->use_msi = 1;
> > + adapter->irq = pdev->irq;
> > + } else {
> > + printk(KERN_INFO "vmw_pvscsi: using INTx\n");
> > + adapter->irq = pdev->irq;
> > + }
> > +
> > + error = request_irq(adapter->irq, pvscsi_isr, IRQF_SHARED,
> > + "vmw_pvscsi", adapter);
>
> Typically IRQF_SHARED w/ INTx, not MSI and MSI-X.
>

Done.

Will send a V6 with all the changes.

Thanks,
Alok

2009-10-13 21:51:46

by Alok Kataria

[permalink] [raw]
Subject: SCSI driver for VMware's virtual HBA - V6.

Here is the V6, which addresses most of the suggestions from Chris'.

Please consider this for the scsi-tree.

Thanks,
Alok (Hoping Sixth time's a charm :)


VMware PVSCSI driver - v6.

Changelog (v6-v5)
- remove unneeded header file includes.
- make pvscsi_dev as a static function.
- IRQF_SHARED only for INT/X, pass zero for rest.
- use __get_free_pages to allocate ctx->sgl.

Changelog (v5-v4)
- use bool for pvscsi _disable_msi, _disable_msix, _use_msg
- fix for 16 byte CDBs, max_cmd_len should be initialized to 16.
- bump the driver version.
- add <[email protected]> in the MAINTAINERS file.
- rebase to 2.6.32-rc1.

Changelog (v4-v3)
- use size_t type for a loop iterator.
- use IS_ALIGNED instead of ~PAGE_MASK, for alignment checks
- use scsi_dma_ map/unmap API's
- reduce the pvscsi_info buffer to 256 bytes
- code cleanup in pvscsi_shutdown_intr

Changelog (v3-v2)
- use kmalloc for allocating sg_lists. And pci_map it for device access.
- re-use pdev->revision for setting up adapter->rev.

Changelog (v2-v1)
- use PCI_VDEVICE instead of PCI_DEVICE.
- use list_first_entry
- use parenthesis for every sizeof usage
- get rid of all #ifdef for CONFIG_PCI_MSI
- use PVSCSI_MEM_SPACE_SIZE while checking for MMIO resource len.
- use kcalloc instead of kmalloc.
- replaced a couple of scmd_printk usage with dev_dbg
- use dev_info and pr_info at couple of places.
- add a comment to pvscsi_map_context
- Add entry in MAINTAINERS.

Patch applies on top of 2.6.32-rc4.

--
SCSI driver for VMware's virtual HBA.

From: Alok N Kataria <[email protected]>

This is a driver for VMware's paravirtualized SCSI device,
which should improve disk performance for guests running
under control of VMware hypervisors that support such devices.

Signed-off-by: Alok N Kataria <[email protected]>
---

MAINTAINERS | 8
drivers/scsi/Kconfig | 8
drivers/scsi/Makefile | 1
drivers/scsi/vmw_pvscsi.c | 1407 +++++++++++++++++++++++++++++++++++++++++++++
drivers/scsi/vmw_pvscsi.h | 397 +++++++++++++
5 files changed, 1821 insertions(+), 0 deletions(-)
create mode 100644 drivers/scsi/vmw_pvscsi.c
create mode 100644 drivers/scsi/vmw_pvscsi.h


diff --git a/MAINTAINERS b/MAINTAINERS
index 09a2028..69112f9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5628,6 +5628,14 @@ S: Maintained
F: drivers/vlynq/vlynq.c
F: include/linux/vlynq.h

+VMware PVSCSI driver
+M: Alok Kataria <[email protected]>
+M: VMware PV-Drivers <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/scsi/vmw_pvscsi.c
+F: drivers/scsi/vmw_pvscsi.h
+
VOLTAGE AND CURRENT REGULATOR FRAMEWORK
M: Liam Girdwood <[email protected]>
M: Mark Brown <[email protected]>
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index 82bb3b2..ebfddff 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -620,6 +620,14 @@ config SCSI_FLASHPOINT
substantial, so users of MultiMaster Host Adapters may not
wish to include it.

+config VMWARE_PVSCSI
+ tristate "VMware PVSCSI driver support"
+ depends on PCI && SCSI && X86
+ help
+ This driver supports VMware's para virtualized SCSI HBA.
+ To compile this driver as a module, choose M here: the
+ module will be called vmw_pvscsi.
+
config LIBFC
tristate "LibFC module"
select SCSI_FC_ATTRS
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 61a94af..af41d8e 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -131,6 +131,7 @@ obj-$(CONFIG_PS3_ROM) += ps3rom.o
obj-$(CONFIG_SCSI_CXGB3_ISCSI) += libiscsi.o libiscsi_tcp.o cxgb3i/
obj-$(CONFIG_SCSI_BNX2_ISCSI) += libiscsi.o bnx2i/
obj-$(CONFIG_SCSI_PMCRAID) += pmcraid.o
+obj-$(CONFIG_VMWARE_PVSCSI) += vmw_pvscsi.o

obj-$(CONFIG_ARM) += arm/

diff --git a/drivers/scsi/vmw_pvscsi.c b/drivers/scsi/vmw_pvscsi.c
new file mode 100644
index 0000000..d2604c8
--- /dev/null
+++ b/drivers/scsi/vmw_pvscsi.c
@@ -0,0 +1,1407 @@
+/*
+ * Linux driver for VMware's para-virtualized SCSI HBA.
+ *
+ * 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: Alok N Kataria <[email protected]>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/pci.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+
+#include "vmw_pvscsi.h"
+
+#define PVSCSI_LINUX_DRIVER_DESC "VMware PVSCSI driver"
+
+MODULE_DESCRIPTION(PVSCSI_LINUX_DRIVER_DESC);
+MODULE_AUTHOR("VMware, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(PVSCSI_DRIVER_VERSION_STRING);
+
+#define PVSCSI_DEFAULT_NUM_PAGES_PER_RING 8
+#define PVSCSI_DEFAULT_NUM_PAGES_MSG_RING 1
+#define PVSCSI_DEFAULT_QUEUE_DEPTH 64
+#define SGL_SIZE PAGE_SIZE
+
+struct pvscsi_sg_list {
+ struct PVSCSISGElement sge[PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT];
+};
+
+struct pvscsi_ctx {
+ /*
+ * The index of the context in cmd_map serves as the context ID for a
+ * 1-to-1 mapping completions back to requests.
+ */
+ struct scsi_cmnd *cmd;
+ struct pvscsi_sg_list *sgl;
+ struct list_head list;
+ dma_addr_t dataPA;
+ dma_addr_t sensePA;
+ dma_addr_t sglPA;
+};
+
+struct pvscsi_adapter {
+ char *mmioBase;
+ unsigned int irq;
+ u8 rev;
+ bool use_msi;
+ bool use_msix;
+ bool use_msg;
+
+ spinlock_t hw_lock;
+
+ struct workqueue_struct *workqueue;
+ struct work_struct work;
+
+ struct PVSCSIRingReqDesc *req_ring;
+ unsigned req_pages;
+ unsigned req_depth;
+ dma_addr_t reqRingPA;
+
+ struct PVSCSIRingCmpDesc *cmp_ring;
+ unsigned cmp_pages;
+ dma_addr_t cmpRingPA;
+
+ struct PVSCSIRingMsgDesc *msg_ring;
+ unsigned msg_pages;
+ dma_addr_t msgRingPA;
+
+ struct PVSCSIRingsState *rings_state;
+ dma_addr_t ringStatePA;
+
+ struct pci_dev *dev;
+ struct Scsi_Host *host;
+
+ struct list_head cmd_pool;
+ struct pvscsi_ctx *cmd_map;
+};
+
+
+/* Command line parameters */
+static int pvscsi_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_PER_RING;
+static int pvscsi_msg_ring_pages = PVSCSI_DEFAULT_NUM_PAGES_MSG_RING;
+static int pvscsi_cmd_per_lun = PVSCSI_DEFAULT_QUEUE_DEPTH;
+static bool pvscsi_disable_msi;
+static bool pvscsi_disable_msix;
+static bool pvscsi_use_msg = true;
+
+#define PVSCSI_RW (S_IRUSR | S_IWUSR)
+
+module_param_named(ring_pages, pvscsi_ring_pages, int, PVSCSI_RW);
+MODULE_PARM_DESC(ring_pages, "Number of pages per req/cmp ring - (default="
+ __stringify(PVSCSI_DEFAULT_NUM_PAGES_PER_RING) ")");
+
+module_param_named(msg_ring_pages, pvscsi_msg_ring_pages, int, PVSCSI_RW);
+MODULE_PARM_DESC(msg_ring_pages, "Number of pages for the msg ring - (default="
+ __stringify(PVSCSI_DEFAULT_NUM_PAGES_MSG_RING) ")");
+
+module_param_named(cmd_per_lun, pvscsi_cmd_per_lun, int, PVSCSI_RW);
+MODULE_PARM_DESC(cmd_per_lun, "Maximum commands per lun - (default="
+ __stringify(PVSCSI_MAX_REQ_QUEUE_DEPTH) ")");
+
+module_param_named(disable_msi, pvscsi_disable_msi, bool, PVSCSI_RW);
+MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)");
+
+module_param_named(disable_msix, pvscsi_disable_msix, bool, PVSCSI_RW);
+MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default=0)");
+
+module_param_named(use_msg, pvscsi_use_msg, bool, PVSCSI_RW);
+MODULE_PARM_DESC(use_msg, "Use msg ring when available - (default=1)");
+
+static const struct pci_device_id pvscsi_pci_tbl[] = {
+ { PCI_VDEVICE(VMWARE, PCI_DEVICE_ID_VMWARE_PVSCSI) },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE(pci, pvscsi_pci_tbl);
+
+static struct device *
+pvscsi_dev(const struct pvscsi_adapter *adapter)
+{
+ return &(adapter->dev->dev);
+}
+
+static struct pvscsi_ctx *
+pvscsi_find_context(const struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
+{
+ struct pvscsi_ctx *ctx, *end;
+
+ end = &adapter->cmd_map[adapter->req_depth];
+ for (ctx = adapter->cmd_map; ctx < end; ctx++)
+ if (ctx->cmd == cmd)
+ return ctx;
+
+ return NULL;
+}
+
+static struct pvscsi_ctx *
+pvscsi_acquire_context(struct pvscsi_adapter *adapter, struct scsi_cmnd *cmd)
+{
+ struct pvscsi_ctx *ctx;
+
+ if (list_empty(&adapter->cmd_pool))
+ return NULL;
+
+ ctx = list_first_entry(&adapter->cmd_pool, struct pvscsi_ctx, list);
+ ctx->cmd = cmd;
+ list_del(&ctx->list);
+
+ return ctx;
+}
+
+static void pvscsi_release_context(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx)
+{
+ ctx->cmd = NULL;
+ list_add(&ctx->list, &adapter->cmd_pool);
+}
+
+/*
+ * Map a pvscsi_ctx struct to a context ID field value; we map to a simple
+ * non-zero integer. ctx always points to an entry in cmd_map array, hence
+ * the return value is always >=1.
+ */
+static u64 pvscsi_map_context(const struct pvscsi_adapter *adapter,
+ const struct pvscsi_ctx *ctx)
+{
+ return ctx - adapter->cmd_map + 1;
+}
+
+static struct pvscsi_ctx *
+pvscsi_get_context(const struct pvscsi_adapter *adapter, u64 context)
+{
+ return &adapter->cmd_map[context - 1];
+}
+
+static void pvscsi_reg_write(const struct pvscsi_adapter *adapter,
+ u32 offset, u32 val)
+{
+ writel(val, adapter->mmioBase + offset);
+}
+
+static u32 pvscsi_reg_read(const struct pvscsi_adapter *adapter, u32 offset)
+{
+ return readl(adapter->mmioBase + offset);
+}
+
+static u32 pvscsi_read_intr_status(const struct pvscsi_adapter *adapter)
+{
+ return pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_INTR_STATUS);
+}
+
+static void pvscsi_write_intr_status(const struct pvscsi_adapter *adapter,
+ u32 val)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_STATUS, val);
+}
+
+static void pvscsi_unmask_intr(const struct pvscsi_adapter *adapter)
+{
+ u32 intr_bits;
+
+ intr_bits = PVSCSI_INTR_CMPL_MASK;
+ if (adapter->use_msg)
+ intr_bits |= PVSCSI_INTR_MSG_MASK;
+
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, intr_bits);
+}
+
+static void pvscsi_mask_intr(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_INTR_MASK, 0);
+}
+
+static void pvscsi_write_cmd_desc(const struct pvscsi_adapter *adapter,
+ u32 cmd, const void *desc, size_t len)
+{
+ const u32 *ptr = desc;
+ size_t i;
+
+ len /= sizeof(*ptr);
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND, cmd);
+ for (i = 0; i < len; i++)
+ pvscsi_reg_write(adapter,
+ PVSCSI_REG_OFFSET_COMMAND_DATA, ptr[i]);
+}
+
+static void pvscsi_abort_cmd(const struct pvscsi_adapter *adapter,
+ const struct pvscsi_ctx *ctx)
+{
+ struct PVSCSICmdDescAbortCmd cmd = { 0 };
+
+ cmd.target = ctx->cmd->device->id;
+ cmd.context = pvscsi_map_context(adapter, ctx);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ABORT_CMD, &cmd, sizeof(cmd));
+}
+
+static void pvscsi_kick_rw_io(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_RW_IO, 0);
+}
+
+static void pvscsi_process_request_ring(const struct pvscsi_adapter *adapter)
+{
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_KICK_NON_RW_IO, 0);
+}
+
+static int scsi_is_rw(unsigned char op)
+{
+ return op == READ_6 || op == WRITE_6 ||
+ op == READ_10 || op == WRITE_10 ||
+ op == READ_12 || op == WRITE_12 ||
+ op == READ_16 || op == WRITE_16;
+}
+
+static void pvscsi_kick_io(const struct pvscsi_adapter *adapter,
+ unsigned char op)
+{
+ if (scsi_is_rw(op))
+ pvscsi_kick_rw_io(adapter);
+ else
+ pvscsi_process_request_ring(adapter);
+}
+
+static void ll_adapter_reset(const struct pvscsi_adapter *adapter)
+{
+ dev_dbg(pvscsi_dev(adapter), "Adapter Reset on %p\n", adapter);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_ADAPTER_RESET, NULL, 0);
+}
+
+static void ll_bus_reset(const struct pvscsi_adapter *adapter)
+{
+ dev_dbg(pvscsi_dev(adapter), "Reseting bus on %p\n", adapter);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_BUS, NULL, 0);
+}
+
+static void ll_device_reset(const struct pvscsi_adapter *adapter, u32 target)
+{
+ struct PVSCSICmdDescResetDevice cmd = { 0 };
+
+ dev_dbg(pvscsi_dev(adapter), "Reseting device: target=%u\n", target);
+
+ cmd.target = target;
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_DEVICE,
+ &cmd, sizeof(cmd));
+}
+
+static void pvscsi_create_sg(struct pvscsi_ctx *ctx,
+ struct scatterlist *sg, unsigned count)
+{
+ unsigned i;
+ struct PVSCSISGElement *sge;
+
+ BUG_ON(count > PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT);
+
+ sge = &ctx->sgl->sge[0];
+ for (i = 0; i < count; i++, sg++) {
+ sge[i].addr = sg_dma_address(sg);
+ sge[i].length = sg_dma_len(sg);
+ sge[i].flags = 0;
+ }
+}
+
+/*
+ * Map all data buffers for a command into PCI space and
+ * setup the scatter/gather list if needed.
+ */
+static void pvscsi_map_buffers(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd,
+ struct PVSCSIRingReqDesc *e)
+{
+ unsigned count;
+ unsigned bufflen = scsi_bufflen(cmd);
+ struct scatterlist *sg;
+
+ e->dataLen = bufflen;
+ e->dataAddr = 0;
+ if (bufflen == 0)
+ return;
+
+ sg = scsi_sglist(cmd);
+ count = scsi_sg_count(cmd);
+ if (count != 0) {
+ int segs = scsi_dma_map(cmd);
+ if (segs > 1) {
+ pvscsi_create_sg(ctx, sg, segs);
+
+ e->flags |= PVSCSI_FLAG_CMD_WITH_SG_LIST;
+ ctx->sglPA = pci_map_single(adapter->dev, ctx->sgl,
+ SGL_SIZE, PCI_DMA_TODEVICE);
+ e->dataAddr = ctx->sglPA;
+ } else
+ e->dataAddr = sg_dma_address(sg);
+ } else {
+ /*
+ * In case there is no S/G list, scsi_sglist points
+ * directly to the buffer.
+ */
+ ctx->dataPA = pci_map_single(adapter->dev, sg, bufflen,
+ cmd->sc_data_direction);
+ e->dataAddr = ctx->dataPA;
+ }
+}
+
+static void pvscsi_unmap_buffers(const struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx)
+{
+ struct scsi_cmnd *cmd;
+ unsigned bufflen;
+
+ cmd = ctx->cmd;
+ bufflen = scsi_bufflen(cmd);
+
+ if (bufflen != 0) {
+ unsigned count = scsi_sg_count(cmd);
+
+ if (count != 0) {
+ scsi_dma_unmap(cmd);
+ if (ctx->sglPA) {
+ pci_unmap_single(adapter->dev, ctx->sglPA,
+ SGL_SIZE, PCI_DMA_TODEVICE);
+ ctx->sglPA = 0;
+ }
+ } else
+ pci_unmap_single(adapter->dev, ctx->dataPA, bufflen,
+ cmd->sc_data_direction);
+ }
+ if (cmd->sense_buffer)
+ pci_unmap_single(adapter->dev, ctx->sensePA,
+ SCSI_SENSE_BUFFERSIZE, PCI_DMA_FROMDEVICE);
+}
+
+static int __devinit pvscsi_allocate_rings(struct pvscsi_adapter *adapter)
+{
+ adapter->rings_state = pci_alloc_consistent(adapter->dev, PAGE_SIZE,
+ &adapter->ringStatePA);
+ if (!adapter->rings_state)
+ return -ENOMEM;
+
+ adapter->req_pages = min(PVSCSI_MAX_NUM_PAGES_REQ_RING,
+ pvscsi_ring_pages);
+ adapter->req_depth = adapter->req_pages
+ * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ adapter->req_ring = pci_alloc_consistent(adapter->dev,
+ adapter->req_pages * PAGE_SIZE,
+ &adapter->reqRingPA);
+ if (!adapter->req_ring)
+ return -ENOMEM;
+
+ adapter->cmp_pages = min(PVSCSI_MAX_NUM_PAGES_CMP_RING,
+ pvscsi_ring_pages);
+ adapter->cmp_ring = pci_alloc_consistent(adapter->dev,
+ adapter->cmp_pages * PAGE_SIZE,
+ &adapter->cmpRingPA);
+ if (!adapter->cmp_ring)
+ return -ENOMEM;
+
+ BUG_ON(!IS_ALIGNED(adapter->ringStatePA, PAGE_SIZE));
+ BUG_ON(!IS_ALIGNED(adapter->reqRingPA, PAGE_SIZE));
+ BUG_ON(!IS_ALIGNED(adapter->cmpRingPA, PAGE_SIZE));
+
+ if (!adapter->use_msg)
+ return 0;
+
+ adapter->msg_pages = min(PVSCSI_MAX_NUM_PAGES_MSG_RING,
+ pvscsi_msg_ring_pages);
+ adapter->msg_ring = pci_alloc_consistent(adapter->dev,
+ adapter->msg_pages * PAGE_SIZE,
+ &adapter->msgRingPA);
+ if (!adapter->msg_ring)
+ return -ENOMEM;
+ BUG_ON(!IS_ALIGNED(adapter->msgRingPA, PAGE_SIZE));
+
+ return 0;
+}
+
+static void pvscsi_setup_all_rings(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSICmdDescSetupRings cmd = { 0 };
+ dma_addr_t base;
+ unsigned i;
+
+ cmd.ringsStatePPN = adapter->ringStatePA >> PAGE_SHIFT;
+ cmd.reqRingNumPages = adapter->req_pages;
+ cmd.cmpRingNumPages = adapter->cmp_pages;
+
+ base = adapter->reqRingPA;
+ for (i = 0; i < adapter->req_pages; i++) {
+ cmd.reqRingPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+
+ base = adapter->cmpRingPA;
+ for (i = 0; i < adapter->cmp_pages; i++) {
+ cmd.cmpRingPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+
+ memset(adapter->rings_state, 0, PAGE_SIZE);
+ memset(adapter->req_ring, 0, adapter->req_pages * PAGE_SIZE);
+ memset(adapter->cmp_ring, 0, adapter->cmp_pages * PAGE_SIZE);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_RINGS,
+ &cmd, sizeof(cmd));
+
+ if (adapter->use_msg) {
+ struct PVSCSICmdDescSetupMsgRing cmd_msg = { 0 };
+
+ cmd_msg.numPages = adapter->msg_pages;
+
+ base = adapter->msgRingPA;
+ for (i = 0; i < adapter->msg_pages; i++) {
+ cmd_msg.ringPPNs[i] = base >> PAGE_SHIFT;
+ base += PAGE_SIZE;
+ }
+ memset(adapter->msg_ring, 0, adapter->msg_pages * PAGE_SIZE);
+
+ pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_SETUP_MSG_RING,
+ &cmd_msg, sizeof(cmd_msg));
+ }
+}
+
+/*
+ * Pull a completion descriptor off and pass the completion back
+ * to the SCSI mid layer.
+ */
+static void pvscsi_complete_request(struct pvscsi_adapter *adapter,
+ const struct PVSCSIRingCmpDesc *e)
+{
+ struct pvscsi_ctx *ctx;
+ struct scsi_cmnd *cmd;
+ u32 btstat = e->hostStatus;
+ u32 sdstat = e->scsiStatus;
+
+ ctx = pvscsi_get_context(adapter, e->context);
+ cmd = ctx->cmd;
+ pvscsi_unmap_buffers(adapter, ctx);
+ pvscsi_release_context(adapter, ctx);
+ cmd->result = 0;
+
+ if (sdstat != SAM_STAT_GOOD &&
+ (btstat == BTSTAT_SUCCESS ||
+ btstat == BTSTAT_LINKED_COMMAND_COMPLETED ||
+ btstat == BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG)) {
+ cmd->result = (DID_OK << 16) | sdstat;
+ if (sdstat == SAM_STAT_CHECK_CONDITION && cmd->sense_buffer)
+ cmd->result |= (DRIVER_SENSE << 24);
+ } else
+ switch (btstat) {
+ case BTSTAT_SUCCESS:
+ case BTSTAT_LINKED_COMMAND_COMPLETED:
+ case BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG:
+ /* If everything went fine, let's move on.. */
+ cmd->result = (DID_OK << 16);
+ break;
+
+ case BTSTAT_DATARUN:
+ case BTSTAT_DATA_UNDERRUN:
+ /* Report residual data in underruns */
+ scsi_set_resid(cmd, scsi_bufflen(cmd) - e->dataLen);
+ cmd->result = (DID_ERROR << 16);
+ break;
+
+ case BTSTAT_SELTIMEO:
+ /* Our emulation returns this for non-connected devs */
+ cmd->result = (DID_BAD_TARGET << 16);
+ break;
+
+ case BTSTAT_LUNMISMATCH:
+ case BTSTAT_TAGREJECT:
+ case BTSTAT_BADMSG:
+ cmd->result = (DRIVER_INVALID << 24);
+ /* fall through */
+
+ case BTSTAT_HAHARDWARE:
+ case BTSTAT_INVPHASE:
+ case BTSTAT_HATIMEOUT:
+ case BTSTAT_NORESPONSE:
+ case BTSTAT_DISCONNECT:
+ case BTSTAT_HASOFTWARE:
+ case BTSTAT_BUSFREE:
+ case BTSTAT_SENSFAILED:
+ cmd->result |= (DID_ERROR << 16);
+ break;
+
+ case BTSTAT_SENTRST:
+ case BTSTAT_RECVRST:
+ case BTSTAT_BUSRESET:
+ cmd->result = (DID_RESET << 16);
+ break;
+
+ case BTSTAT_ABORTQUEUE:
+ cmd->result = (DID_ABORT << 16);
+ break;
+
+ case BTSTAT_SCSIPARITY:
+ cmd->result = (DID_PARITY << 16);
+ break;
+
+ default:
+ cmd->result = (DID_ERROR << 16);
+ scmd_printk(KERN_DEBUG, cmd,
+ "Unknown completion status: 0x%x\n",
+ btstat);
+ }
+
+ dev_dbg(&cmd->device->sdev_gendev,
+ "cmd=%p %x ctx=%p result=0x%x status=0x%x,%x\n",
+ cmd, cmd->cmnd[0], ctx, cmd->result, btstat, sdstat);
+
+ cmd->scsi_done(cmd);
+}
+
+/*
+ * barrier usage : Since the PVSCSI device is emulated, there could be cases
+ * where we may want to serialize some accesses between the driver and the
+ * emulation layer. We use compiler barriers instead of the more expensive
+ * memory barriers because PVSCSI is only supported on X86 which has strong
+ * memory access ordering.
+ */
+static void pvscsi_process_completion_ring(struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct PVSCSIRingCmpDesc *ring = adapter->cmp_ring;
+ u32 cmp_entries = s->cmpNumEntriesLog2;
+
+ while (s->cmpConsIdx != s->cmpProdIdx) {
+ struct PVSCSIRingCmpDesc *e = ring + (s->cmpConsIdx &
+ MASK(cmp_entries));
+ /*
+ * This barrier() ensures that *e is not dereferenced while
+ * the device emulation still writes data into the slot.
+ * Since the device emulation advances s->cmpProdIdx only after
+ * updating the slot we want to check it first.
+ */
+ barrier();
+ pvscsi_complete_request(adapter, e);
+ /*
+ * This barrier() ensures that compiler doesn't reorder write
+ * to s->cmpConsIdx before the read of (*e) inside
+ * pvscsi_complete_request. Otherwise, device emulation may
+ * overwrite *e before we had a chance to read it.
+ */
+ barrier();
+ s->cmpConsIdx++;
+ }
+}
+
+/*
+ * Translate a Linux SCSI request into a request ring entry.
+ */
+static int pvscsi_queue_ring(struct pvscsi_adapter *adapter,
+ struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd)
+{
+ struct PVSCSIRingsState *s;
+ struct PVSCSIRingReqDesc *e;
+ struct scsi_device *sdev;
+ u32 req_entries;
+
+ s = adapter->rings_state;
+ sdev = cmd->device;
+ req_entries = s->reqNumEntriesLog2;
+
+ /*
+ * If this condition holds, we might have room on the request ring, but
+ * we might not have room on the completion ring for the response.
+ * However, we have already ruled out this possibility - we would not
+ * have successfully allocated a context if it were true, since we only
+ * have one context per request entry. Check for it anyway, since it
+ * would be a serious bug.
+ */
+ if (s->reqProdIdx - s->cmpConsIdx >= 1 << req_entries) {
+ scmd_printk(KERN_ERR, cmd, "vmw_pvscsi: "
+ "ring full: reqProdIdx=%d cmpConsIdx=%d\n",
+ s->reqProdIdx, s->cmpConsIdx);
+ return -1;
+ }
+
+ e = adapter->req_ring + (s->reqProdIdx & MASK(req_entries));
+
+ e->bus = sdev->channel;
+ e->target = sdev->id;
+ memset(e->lun, 0, sizeof(e->lun));
+ e->lun[1] = sdev->lun;
+
+ if (cmd->sense_buffer) {
+ ctx->sensePA = pci_map_single(adapter->dev, cmd->sense_buffer,
+ SCSI_SENSE_BUFFERSIZE,
+ PCI_DMA_FROMDEVICE);
+ e->senseAddr = ctx->sensePA;
+ e->senseLen = SCSI_SENSE_BUFFERSIZE;
+ } else {
+ e->senseLen = 0;
+ e->senseAddr = 0;
+ }
+ e->cdbLen = cmd->cmd_len;
+ e->vcpuHint = smp_processor_id();
+ memcpy(e->cdb, cmd->cmnd, e->cdbLen);
+
+ e->tag = SIMPLE_QUEUE_TAG;
+ if (sdev->tagged_supported &&
+ (cmd->tag == HEAD_OF_QUEUE_TAG ||
+ cmd->tag == ORDERED_QUEUE_TAG))
+ e->tag = cmd->tag;
+
+ if (cmd->sc_data_direction == DMA_FROM_DEVICE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_TOHOST;
+ else if (cmd->sc_data_direction == DMA_TO_DEVICE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_TODEVICE;
+ else if (cmd->sc_data_direction == DMA_NONE)
+ e->flags = PVSCSI_FLAG_CMD_DIR_NONE;
+ else
+ e->flags = 0;
+
+ pvscsi_map_buffers(adapter, ctx, cmd, e);
+
+ e->context = pvscsi_map_context(adapter, ctx);
+
+ barrier();
+
+ s->reqProdIdx++;
+
+ return 0;
+}
+
+static int pvscsi_queue(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ struct pvscsi_ctx *ctx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ ctx = pvscsi_acquire_context(adapter, cmd);
+ if (!ctx || pvscsi_queue_ring(adapter, ctx, cmd) != 0) {
+ if (ctx)
+ pvscsi_release_context(adapter, ctx);
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ return SCSI_MLQUEUE_HOST_BUSY;
+ }
+
+ cmd->scsi_done = done;
+
+ dev_dbg(&cmd->device->sdev_gendev,
+ "queued cmd %p, ctx %p, op=%x\n", cmd, ctx, cmd->cmnd[0]);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ pvscsi_kick_io(adapter, cmd->cmnd[0]);
+
+ return 0;
+}
+
+static int pvscsi_abort(struct scsi_cmnd *cmd)
+{
+ struct pvscsi_adapter *adapter = shost_priv(cmd->device->host);
+ struct pvscsi_ctx *ctx;
+ unsigned long flags;
+
+ scmd_printk(KERN_DEBUG, cmd, "task abort on host %u, %p\n",
+ adapter->host->host_no, cmd);
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ /*
+ * Poll the completion ring first - we might be trying to abort
+ * a command that is waiting to be dispatched in the completion ring.
+ */
+ pvscsi_process_completion_ring(adapter);
+
+ /*
+ * If there is no context for the command, it either already succeeded
+ * or else was never properly issued. Not our problem.
+ */
+ ctx = pvscsi_find_context(adapter, cmd);
+ if (!ctx) {
+ scmd_printk(KERN_DEBUG, cmd, "Failed to abort cmd %p\n", cmd);
+ goto out;
+ }
+
+ pvscsi_abort_cmd(adapter, ctx);
+
+ pvscsi_process_completion_ring(adapter);
+
+out:
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ return SUCCESS;
+}
+
+/*
+ * Abort all outstanding requests. This is only safe to use if the completion
+ * ring will never be walked again or the device has been reset, because it
+ * destroys the 1-1 mapping between context field passed to emulation and our
+ * request structure.
+ */
+static void pvscsi_reset_all(struct pvscsi_adapter *adapter)
+{
+ unsigned i;
+
+ for (i = 0; i < adapter->req_depth; i++) {
+ struct pvscsi_ctx *ctx = &adapter->cmd_map[i];
+ struct scsi_cmnd *cmd = ctx->cmd;
+ if (cmd) {
+ scmd_printk(KERN_ERR, cmd,
+ "Forced reset on cmd %p\n", cmd);
+ pvscsi_unmap_buffers(adapter, ctx);
+ pvscsi_release_context(adapter, ctx);
+ cmd->result = (DID_RESET << 16);
+ cmd->scsi_done(cmd);
+ }
+ }
+}
+
+static int pvscsi_host_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+ bool use_msg;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI Host reset\n");
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ use_msg = adapter->use_msg;
+
+ if (use_msg) {
+ adapter->use_msg = 0;
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ /*
+ * Now that we know that the ISR won't add more work on the
+ * workqueue we can safely flush any outstanding work.
+ */
+ flush_workqueue(adapter->workqueue);
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+ }
+
+ /*
+ * We're going to tear down the entire ring structure and set it back
+ * up, so stalling new requests until all completions are flushed and
+ * the rings are back in place.
+ */
+
+ pvscsi_process_request_ring(adapter);
+
+ ll_adapter_reset(adapter);
+
+ /*
+ * Now process any completions. Note we do this AFTER adapter reset,
+ * which is strange, but stops races where completions get posted
+ * between processing the ring and issuing the reset. The backend will
+ * not touch the ring memory after reset, so the immediately pre-reset
+ * completion ring state is still valid.
+ */
+ pvscsi_process_completion_ring(adapter);
+
+ pvscsi_reset_all(adapter);
+ adapter->use_msg = use_msg;
+ pvscsi_setup_all_rings(adapter);
+ pvscsi_unmask_intr(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static int pvscsi_bus_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI Bus reset\n");
+
+ /*
+ * We don't want to queue new requests for this bus after
+ * flushing all pending requests to emulation, since new
+ * requests could then sneak in during this bus reset phase,
+ * so take the lock now.
+ */
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_request_ring(adapter);
+ ll_bus_reset(adapter);
+ pvscsi_process_completion_ring(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static int pvscsi_device_reset(struct scsi_cmnd *cmd)
+{
+ struct Scsi_Host *host = cmd->device->host;
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ unsigned long flags;
+
+ scmd_printk(KERN_INFO, cmd, "SCSI device reset on scsi%u:%u\n",
+ host->host_no, cmd->device->id);
+
+ /*
+ * We don't want to queue new requests for this device after flushing
+ * all pending requests to emulation, since new requests could then
+ * sneak in during this device reset phase, so take the lock now.
+ */
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_request_ring(adapter);
+ ll_device_reset(adapter, cmd->device->id);
+ pvscsi_process_completion_ring(adapter);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+
+ return SUCCESS;
+}
+
+static struct scsi_host_template pvscsi_template;
+
+static const char *pvscsi_info(struct Scsi_Host *host)
+{
+ struct pvscsi_adapter *adapter = shost_priv(host);
+ static char buf[256];
+
+ sprintf(buf, "VMware PVSCSI storage adapter rev %d, req/cmp/msg rings: "
+ "%u/%u/%u pages, cmd_per_lun=%u", adapter->rev,
+ adapter->req_pages, adapter->cmp_pages, adapter->msg_pages,
+ pvscsi_template.cmd_per_lun);
+
+ return buf;
+}
+
+static struct scsi_host_template pvscsi_template = {
+ .module = THIS_MODULE,
+ .name = "VMware PVSCSI Host Adapter",
+ .proc_name = "vmw_pvscsi",
+ .info = pvscsi_info,
+ .queuecommand = pvscsi_queue,
+ .this_id = -1,
+ .sg_tablesize = PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT,
+ .dma_boundary = UINT_MAX,
+ .max_sectors = 0xffff,
+ .use_clustering = ENABLE_CLUSTERING,
+ .eh_abort_handler = pvscsi_abort,
+ .eh_device_reset_handler = pvscsi_device_reset,
+ .eh_bus_reset_handler = pvscsi_bus_reset,
+ .eh_host_reset_handler = pvscsi_host_reset,
+};
+
+static void pvscsi_process_msg(const struct pvscsi_adapter *adapter,
+ const struct PVSCSIRingMsgDesc *e)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct Scsi_Host *host = adapter->host;
+ struct scsi_device *sdev;
+
+ printk(KERN_INFO "vmw_pvscsi: msg type: 0x%x - MSG RING: %u/%u (%u) \n",
+ e->type, s->msgProdIdx, s->msgConsIdx, s->msgNumEntriesLog2);
+
+ BUILD_BUG_ON(PVSCSI_MSG_LAST != 2);
+
+ if (e->type == PVSCSI_MSG_DEV_ADDED) {
+ struct PVSCSIMsgDescDevStatusChanged *desc;
+ desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
+
+ printk(KERN_INFO
+ "vmw_pvscsi: msg: device added at scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ if (!scsi_host_get(host))
+ return;
+
+ sdev = scsi_device_lookup(host, desc->bus, desc->target,
+ desc->lun[1]);
+ if (sdev) {
+ printk(KERN_INFO "vmw_pvscsi: device already exists\n");
+ scsi_device_put(sdev);
+ } else
+ scsi_add_device(adapter->host, desc->bus,
+ desc->target, desc->lun[1]);
+
+ scsi_host_put(host);
+ } else if (e->type == PVSCSI_MSG_DEV_REMOVED) {
+ struct PVSCSIMsgDescDevStatusChanged *desc;
+ desc = (struct PVSCSIMsgDescDevStatusChanged *)e;
+
+ printk(KERN_INFO
+ "vmw_pvscsi: msg: device removed at scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ if (!scsi_host_get(host))
+ return;
+
+ sdev = scsi_device_lookup(host, desc->bus, desc->target,
+ desc->lun[1]);
+ if (sdev) {
+ scsi_remove_device(sdev);
+ scsi_device_put(sdev);
+ } else
+ printk(KERN_INFO
+ "vmw_pvscsi: failed to lookup scsi%u:%u:%u\n",
+ desc->bus, desc->target, desc->lun[1]);
+
+ scsi_host_put(host);
+ }
+}
+
+static int pvscsi_msg_pending(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+
+ return s->msgProdIdx != s->msgConsIdx;
+}
+
+static void pvscsi_process_msg_ring(const struct pvscsi_adapter *adapter)
+{
+ struct PVSCSIRingsState *s = adapter->rings_state;
+ struct PVSCSIRingMsgDesc *ring = adapter->msg_ring;
+ u32 msg_entries = s->msgNumEntriesLog2;
+
+ while (pvscsi_msg_pending(adapter)) {
+ struct PVSCSIRingMsgDesc *e = ring + (s->msgConsIdx &
+ MASK(msg_entries));
+
+ barrier();
+ pvscsi_process_msg(adapter, e);
+ barrier();
+ s->msgConsIdx++;
+ }
+}
+
+static void pvscsi_msg_workqueue_handler(struct work_struct *data)
+{
+ struct pvscsi_adapter *adapter;
+
+ adapter = container_of(data, struct pvscsi_adapter, work);
+
+ pvscsi_process_msg_ring(adapter);
+}
+
+static int pvscsi_setup_msg_workqueue(struct pvscsi_adapter *adapter)
+{
+ char name[32];
+
+ if (!pvscsi_use_msg)
+ return 0;
+
+ pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND,
+ PVSCSI_CMD_SETUP_MSG_RING);
+
+ if (pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_COMMAND_STATUS) == -1)
+ return 0;
+
+ snprintf(name, sizeof(name),
+ "vmw_pvscsi_wq_%u", adapter->host->host_no);
+
+ adapter->workqueue = create_singlethread_workqueue(name);
+ if (!adapter->workqueue) {
+ printk(KERN_ERR "vmw_pvscsi: failed to create work queue\n");
+ return 0;
+ }
+ INIT_WORK(&adapter->work, pvscsi_msg_workqueue_handler);
+
+ return 1;
+}
+
+static irqreturn_t pvscsi_isr(int irq, void *devp)
+{
+ struct pvscsi_adapter *adapter = devp;
+ int handled;
+
+ if (adapter->use_msi || adapter->use_msix)
+ handled = true;
+ else {
+ u32 val = pvscsi_read_intr_status(adapter);
+ handled = (val & PVSCSI_INTR_ALL_SUPPORTED) != 0;
+ if (handled)
+ pvscsi_write_intr_status(devp, val);
+ }
+
+ if (handled) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&adapter->hw_lock, flags);
+
+ pvscsi_process_completion_ring(adapter);
+ if (adapter->use_msg && pvscsi_msg_pending(adapter))
+ queue_work(adapter->workqueue, &adapter->work);
+
+ spin_unlock_irqrestore(&adapter->hw_lock, flags);
+ }
+
+ return IRQ_RETVAL(handled);
+}
+
+static void pvscsi_free_sgls(const struct pvscsi_adapter *adapter)
+{
+ struct pvscsi_ctx *ctx = adapter->cmd_map;
+ unsigned i;
+
+ for (i = 0; i < adapter->req_depth; ++i, ++ctx)
+ free_pages((unsigned long)ctx->sgl, get_order(SGL_SIZE));
+}
+
+static int pvscsi_setup_msix(const struct pvscsi_adapter *adapter, int *irq)
+{
+ struct msix_entry entry = { 0, PVSCSI_VECTOR_COMPLETION };
+ int ret;
+
+ ret = pci_enable_msix(adapter->dev, &entry, 1);
+ if (ret)
+ return ret;
+
+ *irq = entry.vector;
+
+ return 0;
+}
+
+static void pvscsi_shutdown_intr(struct pvscsi_adapter *adapter)
+{
+ if (adapter->irq) {
+ free_irq(adapter->irq, adapter);
+ adapter->irq = 0;
+ }
+ if (adapter->use_msi) {
+ pci_disable_msi(adapter->dev);
+ adapter->use_msi = 0;
+ } else if (adapter->use_msix) {
+ pci_disable_msix(adapter->dev);
+ adapter->use_msix = 0;
+ }
+}
+
+static void pvscsi_release_resources(struct pvscsi_adapter *adapter)
+{
+ pvscsi_shutdown_intr(adapter);
+
+ if (adapter->workqueue)
+ destroy_workqueue(adapter->workqueue);
+
+ if (adapter->mmioBase)
+ pci_iounmap(adapter->dev, adapter->mmioBase);
+
+ pci_release_regions(adapter->dev);
+
+ if (adapter->cmd_map) {
+ pvscsi_free_sgls(adapter);
+ kfree(adapter->cmd_map);
+ }
+
+ if (adapter->rings_state)
+ pci_free_consistent(adapter->dev, PAGE_SIZE,
+ adapter->rings_state, adapter->ringStatePA);
+
+ if (adapter->req_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->req_pages * PAGE_SIZE,
+ adapter->req_ring, adapter->reqRingPA);
+
+ if (adapter->cmp_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->cmp_pages * PAGE_SIZE,
+ adapter->cmp_ring, adapter->cmpRingPA);
+
+ if (adapter->msg_ring)
+ pci_free_consistent(adapter->dev,
+ adapter->msg_pages * PAGE_SIZE,
+ adapter->msg_ring, adapter->msgRingPA);
+}
+
+/*
+ * Allocate scatter gather lists.
+ *
+ * These are statically allocated. Trying to be clever was not worth it.
+ *
+ * Dynamic allocation can fail, and we can't go deeep into the memory
+ * allocator, since we're a SCSI driver, and trying too hard to allocate
+ * memory might generate disk I/O. We also don't want to fail disk I/O
+ * in that case because we can't get an allocation - the I/O could be
+ * trying to swap out data to free memory. Since that is pathological,
+ * just use a statically allocated scatter list.
+ *
+ */
+static int __devinit pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
+{
+ struct pvscsi_ctx *ctx;
+ int i;
+
+ ctx = adapter->cmd_map;
+ BUILD_BUG_ON(sizeof(struct pvscsi_sg_list) > SGL_SIZE);
+
+ for (i = 0; i < adapter->req_depth; ++i, ++ctx) {
+ ctx->sgl = (void *)__get_free_pages(GFP_KERNEL,
+ get_order(SGL_SIZE));
+ ctx->sglPA = 0;
+ BUG_ON(!IS_ALIGNED(((unsigned long)ctx->sgl), PAGE_SIZE));
+ if (!ctx->sgl) {
+ for (; i >= 0; --i, --ctx) {
+ free_pages((unsigned long)ctx->sgl,
+ get_order(SGL_SIZE));
+ ctx->sgl = NULL;
+ }
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+
+static int __devinit pvscsi_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ struct pvscsi_adapter *adapter;
+ struct Scsi_Host *host;
+ unsigned int i;
+ unsigned long flags = 0;
+ int error;
+
+ error = -ENODEV;
+
+ if (pci_enable_device(pdev))
+ return error;
+
+ if (pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) == 0 &&
+ pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using 64bit dma\n");
+ } else if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32)) == 0 &&
+ pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using 32bit dma\n");
+ } else {
+ printk(KERN_ERR "vmw_pvscsi: failed to set DMA mask\n");
+ goto out_disable_device;
+ }
+
+ pvscsi_template.can_queue =
+ min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
+ PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
+ pvscsi_template.cmd_per_lun =
+ min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);
+ host = scsi_host_alloc(&pvscsi_template, sizeof(struct pvscsi_adapter));
+ if (!host) {
+ printk(KERN_ERR "vmw_pvscsi: failed to allocate host\n");
+ goto out_disable_device;
+ }
+
+ adapter = shost_priv(host);
+ memset(adapter, 0, sizeof(*adapter));
+ adapter->dev = pdev;
+ adapter->host = host;
+
+ spin_lock_init(&adapter->hw_lock);
+
+ host->max_channel = 0;
+ host->max_id = 16;
+ host->max_lun = 1;
+ host->max_cmd_len = 16;
+
+ adapter->rev = pdev->revision;
+
+ if (pci_request_regions(pdev, "vmw_pvscsi")) {
+ printk(KERN_ERR "vmw_pvscsi: pci memory selection failed\n");
+ goto out_free_host;
+ }
+
+ for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
+ if ((pci_resource_flags(pdev, i) & PCI_BASE_ADDRESS_SPACE_IO))
+ continue;
+
+ if (pci_resource_len(pdev, i) < PVSCSI_MEM_SPACE_SIZE)
+ continue;
+
+ break;
+ }
+
+ if (i == DEVICE_COUNT_RESOURCE) {
+ printk(KERN_ERR
+ "vmw_pvscsi: adapter has no suitable MMIO region\n");
+ goto out_release_resources;
+ }
+
+ adapter->mmioBase = pci_iomap(pdev, i, PVSCSI_MEM_SPACE_SIZE);
+
+ if (!adapter->mmioBase) {
+ printk(KERN_ERR
+ "vmw_pvscsi: can't iomap for BAR %d memsize %lu\n",
+ i, PVSCSI_MEM_SPACE_SIZE);
+ goto out_release_resources;
+ }
+
+ pci_set_master(pdev);
+ pci_set_drvdata(pdev, host);
+
+ ll_adapter_reset(adapter);
+
+ adapter->use_msg = pvscsi_setup_msg_workqueue(adapter);
+
+ error = pvscsi_allocate_rings(adapter);
+ if (error) {
+ printk(KERN_ERR "vmw_pvscsi: unable to allocate ring memory\n");
+ goto out_release_resources;
+ }
+
+ /*
+ * From this point on we should reset the adapter if anything goes
+ * wrong.
+ */
+ pvscsi_setup_all_rings(adapter);
+
+ adapter->cmd_map = kcalloc(adapter->req_depth,
+ sizeof(struct pvscsi_ctx), GFP_KERNEL);
+ if (!adapter->cmd_map) {
+ printk(KERN_ERR "vmw_pvscsi: failed to allocate memory.\n");
+ error = -ENOMEM;
+ goto out_reset_adapter;
+ }
+
+ INIT_LIST_HEAD(&adapter->cmd_pool);
+ for (i = 0; i < adapter->req_depth; i++) {
+ struct pvscsi_ctx *ctx = adapter->cmd_map + i;
+ list_add(&ctx->list, &adapter->cmd_pool);
+ }
+
+ error = pvscsi_allocate_sg(adapter);
+ if (error) {
+ printk(KERN_ERR "vmw_pvscsi: unable to allocate s/g table\n");
+ goto out_reset_adapter;
+ }
+
+ if (!pvscsi_disable_msix &&
+ pvscsi_setup_msix(adapter, &adapter->irq) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using MSI-X\n");
+ adapter->use_msix = 1;
+ } else if (!pvscsi_disable_msi && pci_enable_msi(pdev) == 0) {
+ printk(KERN_INFO "vmw_pvscsi: using MSI\n");
+ adapter->use_msi = 1;
+ adapter->irq = pdev->irq;
+ } else {
+ printk(KERN_INFO "vmw_pvscsi: using INTx\n");
+ adapter->irq = pdev->irq;
+ flags = IRQF_SHARED;
+ }
+
+ error = request_irq(adapter->irq, pvscsi_isr, flags,
+ "vmw_pvscsi", adapter);
+ if (error) {
+ printk(KERN_ERR
+ "vmw_pvscsi: unable to request IRQ: %d\n", error);
+ adapter->irq = 0;
+ goto out_reset_adapter;
+ }
+
+ error = scsi_add_host(host, &pdev->dev);
+ if (error) {
+ printk(KERN_ERR
+ "vmw_pvscsi: scsi_add_host failed: %d\n", error);
+ goto out_reset_adapter;
+ }
+
+ dev_info(&pdev->dev, "VMware PVSCSI rev %d host #%u\n",
+ adapter->rev, host->host_no);
+
+ pvscsi_unmask_intr(adapter);
+
+ scsi_scan_host(host);
+
+ return 0;
+
+out_reset_adapter:
+ ll_adapter_reset(adapter);
+out_release_resources:
+ pvscsi_release_resources(adapter);
+out_free_host:
+ scsi_host_put(host);
+out_disable_device:
+ pci_set_drvdata(pdev, NULL);
+ pci_disable_device(pdev);
+
+ return error;
+}
+
+static void __pvscsi_shutdown(struct pvscsi_adapter *adapter)
+{
+ pvscsi_mask_intr(adapter);
+
+ if (adapter->workqueue)
+ flush_workqueue(adapter->workqueue);
+
+ pvscsi_shutdown_intr(adapter);
+
+ pvscsi_process_request_ring(adapter);
+ pvscsi_process_completion_ring(adapter);
+ ll_adapter_reset(adapter);
+}
+
+static void pvscsi_shutdown(struct pci_dev *dev)
+{
+ struct Scsi_Host *host = pci_get_drvdata(dev);
+ struct pvscsi_adapter *adapter = shost_priv(host);
+
+ __pvscsi_shutdown(adapter);
+}
+
+static void pvscsi_remove(struct pci_dev *pdev)
+{
+ struct Scsi_Host *host = pci_get_drvdata(pdev);
+ struct pvscsi_adapter *adapter = shost_priv(host);
+
+ scsi_remove_host(host);
+
+ __pvscsi_shutdown(adapter);
+ pvscsi_release_resources(adapter);
+
+ scsi_host_put(host);
+
+ pci_set_drvdata(pdev, NULL);
+ pci_disable_device(pdev);
+}
+
+static struct pci_driver pvscsi_pci_driver = {
+ .name = "vmw_pvscsi",
+ .id_table = pvscsi_pci_tbl,
+ .probe = pvscsi_probe,
+ .remove = __devexit_p(pvscsi_remove),
+ .shutdown = pvscsi_shutdown,
+};
+
+static int __init pvscsi_init(void)
+{
+ pr_info("%s - version %s\n",
+ PVSCSI_LINUX_DRIVER_DESC, PVSCSI_DRIVER_VERSION_STRING);
+ return pci_register_driver(&pvscsi_pci_driver);
+}
+
+static void __exit pvscsi_exit(void)
+{
+ pci_unregister_driver(&pvscsi_pci_driver);
+}
+
+module_init(pvscsi_init);
+module_exit(pvscsi_exit);
diff --git a/drivers/scsi/vmw_pvscsi.h b/drivers/scsi/vmw_pvscsi.h
new file mode 100644
index 0000000..62e36e7
--- /dev/null
+++ b/drivers/scsi/vmw_pvscsi.h
@@ -0,0 +1,397 @@
+/*
+ * 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: Alok N Kataria <[email protected]>
+ *
+ */
+
+#ifndef _VMW_PVSCSI_H_
+#define _VMW_PVSCSI_H_
+
+#include <linux/types.h>
+
+#define PVSCSI_DRIVER_VERSION_STRING "1.0.1.0-k"
+
+#define PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT 128
+
+#define MASK(n) ((1 << (n)) - 1) /* make an n-bit mask */
+
+#define PCI_VENDOR_ID_VMWARE 0x15AD
+#define PCI_DEVICE_ID_VMWARE_PVSCSI 0x07C0
+
+/*
+ * 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 */
+};
+
+/*
+ * Command descriptor for PVSCSI_CMD_RESET_DEVICE --
+ */
+
+struct PVSCSICmdDescResetDevice {
+ u32 target;
+ u8 lun[8];
+} __packed;
+
+/*
+ * Command descriptor for PVSCSI_CMD_ABORT_CMD --
+ *
+ * - currently does not support specifying the LUN.
+ * - _pad should be 0.
+ */
+
+struct PVSCSICmdDescAbortCmd {
+ u64 context;
+ u32 target;
+ u32 _pad;
+} __packed;
+
+/*
+ * 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 {
+ u32 reqRingNumPages;
+ u32 cmpRingNumPages;
+ u64 ringsStatePPN;
+ u64 reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+ u64 cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES];
+} __packed;
+
+/*
+ * 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(u32).
+ * - 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 {
+ u32 numPages;
+ u32 _pad;
+ u64 ringPPNs[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES];
+} __packed;
+
+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 {
+ u32 type;
+ u32 args[31];
+} __packed;
+
+struct PVSCSIMsgDescDevStatusChanged {
+ u32 type; /* PVSCSI_MSG_DEV _ADDED / _REMOVED */
+ u32 bus;
+ u32 target;
+ u8 lun[8];
+ u32 pad[27];
+} __packed;
+
+/*
+ * 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 {
+ u32 reqProdIdx;
+ u32 reqConsIdx;
+ u32 reqNumEntriesLog2;
+
+ u32 cmpProdIdx;
+ u32 cmpConsIdx;
+ u32 cmpNumEntriesLog2;
+
+ u8 _pad[104];
+
+ u32 msgProdIdx;
+ u32 msgConsIdx;
+ u32 msgNumEntriesLog2;
+} __packed;
+
+/*
+ * 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 u32.
+ * 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)
+
+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;
+
+/*
+ * 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 {
+ u64 addr;
+ u32 length;
+ u32 flags;
+} __packed;
+
+/*
+ * 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 {
+ u64 context;
+ u64 dataLen;
+ u32 senseLen;
+ u16 hostStatus;
+ u16 scsiStatus;
+ u32 _pad[2];
+} __packed;
+
+/*
+ * 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 \
+ (PAGE_SIZE / sizeof(struct PVSCSIRingReqDesc))
+
+#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 * PAGE_SIZE)
+
+#endif /* _VMW_PVSCSI_H_ */

2009-10-13 22:02:12

by Chris Wright

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.

* Alok Kataria ([email protected]) wrote:
> On Mon, 2009-10-12 at 22:37 -0700, Chris Wright wrote:
> > > + pvscsi_template.can_queue =
> > > + min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
> > > + PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
> > > + pvscsi_template.cmd_per_lun =
> > > + min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);
> >
> > When/how are these tunables used? Are they still useful?
>
> cmd_per_lun, is a commandline parameter.

Actually I meant how is a user supposed to use these? They looked to me
like tunables that were part of developing a driver that may not be
needed any longer. What are the right values, what are the tradeoffs,
etc? Or should they just be removed?

thanks,
-chris

2009-10-13 22:13:46

by Alok Kataria

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V5.


On Tue, 2009-10-13 at 15:00 -0700, Chris Wright wrote:
> * Alok Kataria ([email protected]) wrote:
> > On Mon, 2009-10-12 at 22:37 -0700, Chris Wright wrote:
> > > > + pvscsi_template.can_queue =
> > > > + min(PVSCSI_MAX_NUM_PAGES_REQ_RING, pvscsi_ring_pages) *
> > > > + PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE;
> > > > + pvscsi_template.cmd_per_lun =
> > > > + min(pvscsi_template.can_queue, pvscsi_cmd_per_lun);
> > >
> > > When/how are these tunables used? Are they still useful?
> >
> > cmd_per_lun, is a commandline parameter.
>
> Actually I meant how is a user supposed to use these? They looked to me
> like tunables that were part of developing a driver that may not be
> needed any longer.
> What are the right values, what are the tradeoffs,
> etc? Or should they just be removed?

The defaults are the right values, and though a normal user won't change
those in practice (he can if he wants to by passing values during
insmod), a developer working on performance tuning can use it.

So I would like to keep these tunables around as it helps any future
performance work.

Thanks,
Alok

>
> thanks,
> -chris

2009-10-13 23:43:32

by Chris Wright

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V6.

* Alok Kataria ([email protected]) wrote:
> SCSI driver for VMware's virtual HBA.
>
> From: Alok N Kataria <[email protected]>
>
> This is a driver for VMware's paravirtualized SCSI device,
> which should improve disk performance for guests running
> under control of VMware hypervisors that support such devices.
>
> Signed-off-by: Alok N Kataria <[email protected]>

Looks in good shape to me.

Reviewed-by: Chris Wright <[email protected]>

2009-10-28 20:28:37

by Alok Kataria

[permalink] [raw]
Subject: Re: SCSI driver for VMware's virtual HBA - V6.


On Tue, 2009-10-13 at 16:41 -0700, Chris Wright wrote:
> * Alok Kataria ([email protected]) wrote:
> > SCSI driver for VMware's virtual HBA.
> >
> > From: Alok N Kataria <[email protected]>
> >
> > This is a driver for VMware's paravirtualized SCSI device,
> > which should improve disk performance for guests running
> > under control of VMware hypervisors that support such devices.
> >
> > Signed-off-by: Alok N Kataria <[email protected]>
>
> Looks in good shape to me.
>
> Reviewed-by: Chris Wright <[email protected]>

<ping>

This seems to have slipped through the cracks, maybe all the conferences
last week managed to take our attention off this :-).

I just checked the SCSI tree and don't see this merged in any of those
trees yet, though I don't think there is anything else that's pending on
for the driver merge. Some feedback about when this can be merged will
be really helpful.

Thanks,
Alok