2009-10-13 21:51:05 +00:00
|
|
|
/*
|
|
|
|
* Linux driver for VMware's para-virtualized SCSI HBA.
|
|
|
|
*
|
2014-03-08 20:51:12 +00:00
|
|
|
* Copyright (C) 2008-2014, VMware, Inc. All Rights Reserved.
|
2009-10-13 21:51:05 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/interrupt.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
|
|
|
#include <linux/slab.h>
|
2009-10-13 21:51:05 +00:00
|
|
|
#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>
|
2014-03-08 21:04:45 +00:00
|
|
|
#include <scsi/scsi_tcq.h>
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
#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
|
2014-03-08 21:04:45 +00:00
|
|
|
#define PVSCSI_DEFAULT_QUEUE_DEPTH 254
|
2009-10-13 21:51:05 +00:00
|
|
|
#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;
|
2014-03-08 20:51:12 +00:00
|
|
|
struct completion *abort_cmp;
|
2009-10-13 21:51:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct pvscsi_adapter {
|
|
|
|
char *mmioBase;
|
|
|
|
u8 rev;
|
|
|
|
bool use_msg;
|
2014-03-11 20:51:33 +00:00
|
|
|
bool use_req_threshold;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
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 */
|
2014-03-08 21:04:45 +00:00
|
|
|
static int pvscsi_ring_pages;
|
2009-10-13 21:51:05 +00:00
|
|
|
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;
|
2014-03-11 20:51:33 +00:00
|
|
|
static bool pvscsi_use_req_threshold = true;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
#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="
|
2014-03-08 21:04:45 +00:00
|
|
|
__stringify(PVSCSI_DEFAULT_NUM_PAGES_PER_RING)
|
|
|
|
"[up to 16 targets],"
|
|
|
|
__stringify(PVSCSI_SETUP_RINGS_MAX_NUM_PAGES)
|
|
|
|
"[for 16+ targets])");
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
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="
|
2014-03-08 21:04:45 +00:00
|
|
|
__stringify(PVSCSI_DEFAULT_QUEUE_DEPTH) ")");
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
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)");
|
|
|
|
|
2014-03-11 20:51:33 +00:00
|
|
|
module_param_named(use_req_threshold, pvscsi_use_req_threshold,
|
|
|
|
bool, PVSCSI_RW);
|
|
|
|
MODULE_PARM_DESC(use_req_threshold, "Use driver-based request coalescing if configured - (default=1)");
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
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;
|
2014-03-08 20:51:12 +00:00
|
|
|
ctx->abort_cmp = NULL;
|
2009-10-13 21:51:05 +00:00
|
|
|
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)
|
|
|
|
{
|
2014-03-11 20:51:33 +00:00
|
|
|
if (scsi_is_rw(op)) {
|
|
|
|
struct PVSCSIRingsState *s = adapter->rings_state;
|
|
|
|
|
|
|
|
if (!adapter->use_req_threshold ||
|
|
|
|
s->reqProdIdx - s->reqConsIdx >= s->reqCallThreshold)
|
|
|
|
pvscsi_kick_rw_io(adapter);
|
|
|
|
} else {
|
2009-10-13 21:51:05 +00:00
|
|
|
pvscsi_process_request_ring(adapter);
|
2014-03-11 20:51:33 +00:00
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2012-04-24 15:24:16 +00:00
|
|
|
dev_dbg(pvscsi_dev(adapter), "Resetting bus on %p\n", adapter);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
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 };
|
|
|
|
|
2012-04-24 15:24:16 +00:00
|
|
|
dev_dbg(pvscsi_dev(adapter), "Resetting device: target=%u\n", target);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
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];
|
2019-06-18 01:37:42 +00:00
|
|
|
for (i = 0; i < count; i++, sg = sg_next(sg)) {
|
2009-10-13 21:51:05 +00:00
|
|
|
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.
|
|
|
|
*/
|
2015-12-03 13:27:59 +00:00
|
|
|
static int pvscsi_map_buffers(struct pvscsi_adapter *adapter,
|
|
|
|
struct pvscsi_ctx *ctx, struct scsi_cmnd *cmd,
|
|
|
|
struct PVSCSIRingReqDesc *e)
|
2009-10-13 21:51:05 +00:00
|
|
|
{
|
|
|
|
unsigned count;
|
|
|
|
unsigned bufflen = scsi_bufflen(cmd);
|
|
|
|
struct scatterlist *sg;
|
|
|
|
|
|
|
|
e->dataLen = bufflen;
|
|
|
|
e->dataAddr = 0;
|
|
|
|
if (bufflen == 0)
|
2015-12-03 13:27:59 +00:00
|
|
|
return 0;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
sg = scsi_sglist(cmd);
|
|
|
|
count = scsi_sg_count(cmd);
|
|
|
|
if (count != 0) {
|
|
|
|
int segs = scsi_dma_map(cmd);
|
2015-12-03 13:27:59 +00:00
|
|
|
|
|
|
|
if (segs == -ENOMEM) {
|
2019-12-03 19:30:52 +00:00
|
|
|
scmd_printk(KERN_DEBUG, cmd,
|
2015-12-03 13:27:59 +00:00
|
|
|
"vmw_pvscsi: Failed to map cmd sglist for DMA.\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
} else if (segs > 1) {
|
2009-10-13 21:51:05 +00:00
|
|
|
pvscsi_create_sg(ctx, sg, segs);
|
|
|
|
|
|
|
|
e->flags |= PVSCSI_FLAG_CMD_WITH_SG_LIST;
|
2018-10-10 18:22:40 +00:00
|
|
|
ctx->sglPA = dma_map_single(&adapter->dev->dev,
|
|
|
|
ctx->sgl, SGL_SIZE, DMA_TO_DEVICE);
|
|
|
|
if (dma_mapping_error(&adapter->dev->dev, ctx->sglPA)) {
|
2015-12-03 13:27:59 +00:00
|
|
|
scmd_printk(KERN_ERR, cmd,
|
|
|
|
"vmw_pvscsi: Failed to map ctx sglist for DMA.\n");
|
|
|
|
scsi_dma_unmap(cmd);
|
|
|
|
ctx->sglPA = 0;
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
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.
|
|
|
|
*/
|
2018-10-10 18:22:40 +00:00
|
|
|
ctx->dataPA = dma_map_single(&adapter->dev->dev, sg, bufflen,
|
2009-10-13 21:51:05 +00:00
|
|
|
cmd->sc_data_direction);
|
2018-10-10 18:22:40 +00:00
|
|
|
if (dma_mapping_error(&adapter->dev->dev, ctx->dataPA)) {
|
2019-12-03 19:30:52 +00:00
|
|
|
scmd_printk(KERN_DEBUG, cmd,
|
2015-12-03 13:27:59 +00:00
|
|
|
"vmw_pvscsi: Failed to map direct data buffer for DMA.\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
e->dataAddr = ctx->dataPA;
|
|
|
|
}
|
2015-12-03 13:27:59 +00:00
|
|
|
|
|
|
|
return 0;
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
2019-12-03 19:30:51 +00:00
|
|
|
/*
|
|
|
|
* The device incorrectly doesn't clear the first byte of the sense
|
|
|
|
* buffer in some cases. We have to do it ourselves.
|
|
|
|
* Otherwise we run into trouble when SWIOTLB is forced.
|
|
|
|
*/
|
|
|
|
static void pvscsi_patch_sense(struct scsi_cmnd *cmd)
|
|
|
|
{
|
|
|
|
if (cmd->sense_buffer)
|
|
|
|
cmd->sense_buffer[0] = 0;
|
|
|
|
}
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
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) {
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_unmap_single(&adapter->dev->dev, ctx->sglPA,
|
|
|
|
SGL_SIZE, DMA_TO_DEVICE);
|
2009-10-13 21:51:05 +00:00
|
|
|
ctx->sglPA = 0;
|
|
|
|
}
|
|
|
|
} else
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_unmap_single(&adapter->dev->dev, ctx->dataPA,
|
|
|
|
bufflen, cmd->sc_data_direction);
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
if (cmd->sense_buffer)
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_unmap_single(&adapter->dev->dev, ctx->sensePA,
|
|
|
|
SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE);
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
2012-12-21 21:08:55 +00:00
|
|
|
static int pvscsi_allocate_rings(struct pvscsi_adapter *adapter)
|
2009-10-13 21:51:05 +00:00
|
|
|
{
|
2018-10-10 18:22:40 +00:00
|
|
|
adapter->rings_state = dma_alloc_coherent(&adapter->dev->dev, PAGE_SIZE,
|
|
|
|
&adapter->ringStatePA, GFP_KERNEL);
|
2009-10-13 21:51:05 +00:00
|
|
|
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;
|
2018-10-10 18:22:40 +00:00
|
|
|
adapter->req_ring = dma_alloc_coherent(&adapter->dev->dev,
|
|
|
|
adapter->req_pages * PAGE_SIZE, &adapter->reqRingPA,
|
|
|
|
GFP_KERNEL);
|
2009-10-13 21:51:05 +00:00
|
|
|
if (!adapter->req_ring)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
adapter->cmp_pages = min(PVSCSI_MAX_NUM_PAGES_CMP_RING,
|
|
|
|
pvscsi_ring_pages);
|
2018-10-10 18:22:40 +00:00
|
|
|
adapter->cmp_ring = dma_alloc_coherent(&adapter->dev->dev,
|
|
|
|
adapter->cmp_pages * PAGE_SIZE, &adapter->cmpRingPA,
|
|
|
|
GFP_KERNEL);
|
2009-10-13 21:51:05 +00:00
|
|
|
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);
|
2018-10-10 18:22:40 +00:00
|
|
|
adapter->msg_ring = dma_alloc_coherent(&adapter->dev->dev,
|
|
|
|
adapter->msg_pages * PAGE_SIZE, &adapter->msgRingPA,
|
|
|
|
GFP_KERNEL);
|
2009-10-13 21:51:05 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-13 14:08:42 +00:00
|
|
|
static int pvscsi_change_queue_depth(struct scsi_device *sdev, int qdepth)
|
2014-03-08 21:04:45 +00:00
|
|
|
{
|
|
|
|
if (!sdev->tagged_supported)
|
2014-11-13 13:27:41 +00:00
|
|
|
qdepth = 1;
|
2014-11-13 14:14:43 +00:00
|
|
|
return scsi_change_queue_depth(sdev, qdepth);
|
2014-03-08 21:04:45 +00:00
|
|
|
}
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
2014-03-08 20:51:12 +00:00
|
|
|
struct completion *abort_cmp;
|
2009-10-13 21:51:05 +00:00
|
|
|
u32 btstat = e->hostStatus;
|
|
|
|
u32 sdstat = e->scsiStatus;
|
|
|
|
|
|
|
|
ctx = pvscsi_get_context(adapter, e->context);
|
|
|
|
cmd = ctx->cmd;
|
2014-03-08 20:51:12 +00:00
|
|
|
abort_cmp = ctx->abort_cmp;
|
2009-10-13 21:51:05 +00:00
|
|
|
pvscsi_unmap_buffers(adapter, ctx);
|
2019-12-03 19:30:51 +00:00
|
|
|
if (sdstat != SAM_STAT_CHECK_CONDITION)
|
|
|
|
pvscsi_patch_sense(cmd);
|
2009-10-13 21:51:05 +00:00
|
|
|
pvscsi_release_context(adapter, ctx);
|
2014-03-08 20:51:12 +00:00
|
|
|
if (abort_cmp) {
|
|
|
|
/*
|
|
|
|
* The command was requested to be aborted. Just signal that
|
|
|
|
* the request completed and swallow the actual cmd completion
|
|
|
|
* here. The abort handler will post a completion for this
|
|
|
|
* command indicating that it got successfully aborted.
|
|
|
|
*/
|
|
|
|
complete(abort_cmp);
|
|
|
|
return;
|
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
|
2014-03-08 20:51:12 +00:00
|
|
|
cmd->result = 0;
|
2009-10-13 21:51:05 +00:00
|
|
|
if (sdstat != SAM_STAT_GOOD &&
|
|
|
|
(btstat == BTSTAT_SUCCESS ||
|
|
|
|
btstat == BTSTAT_LINKED_COMMAND_COMPLETED ||
|
|
|
|
btstat == BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG)) {
|
2018-08-02 21:13:30 +00:00
|
|
|
if (sdstat == SAM_STAT_COMMAND_TERMINATED) {
|
|
|
|
cmd->result = (DID_RESET << 16);
|
|
|
|
} else {
|
|
|
|
cmd->result = (DID_OK << 16) | sdstat;
|
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
} else
|
|
|
|
switch (btstat) {
|
|
|
|
case BTSTAT_SUCCESS:
|
|
|
|
case BTSTAT_LINKED_COMMAND_COMPLETED:
|
|
|
|
case BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG:
|
2021-05-19 09:49:32 +00:00
|
|
|
/*
|
|
|
|
* Commands like INQUIRY may transfer less data than
|
|
|
|
* requested by the initiator via bufflen. Set residual
|
|
|
|
* count to make upper layer aware of the actual amount
|
|
|
|
* of data returned.
|
|
|
|
*/
|
|
|
|
scsi_set_resid(cmd, scsi_bufflen(cmd) - e->dataLen);
|
2009-10-13 21:51:05 +00:00
|
|
|
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:
|
|
|
|
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:
|
2018-04-21 02:04:47 +00:00
|
|
|
cmd->result = (DID_BUS_BUSY << 16);
|
2009-10-13 21:51:05 +00:00
|
|
|
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) {
|
2018-10-10 18:22:40 +00:00
|
|
|
ctx->sensePA = dma_map_single(&adapter->dev->dev,
|
|
|
|
cmd->sense_buffer, SCSI_SENSE_BUFFERSIZE,
|
|
|
|
DMA_FROM_DEVICE);
|
|
|
|
if (dma_mapping_error(&adapter->dev->dev, ctx->sensePA)) {
|
2019-12-03 19:30:52 +00:00
|
|
|
scmd_printk(KERN_DEBUG, cmd,
|
2015-12-03 13:27:59 +00:00
|
|
|
"vmw_pvscsi: Failed to map sense buffer for DMA.\n");
|
|
|
|
ctx->sensePA = 0;
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
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 (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;
|
|
|
|
|
2015-12-03 13:27:59 +00:00
|
|
|
if (pvscsi_map_buffers(adapter, ctx, cmd, e) != 0) {
|
|
|
|
if (cmd->sense_buffer) {
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_unmap_single(&adapter->dev->dev, ctx->sensePA,
|
2015-12-03 13:27:59 +00:00
|
|
|
SCSI_SENSE_BUFFERSIZE,
|
2018-10-10 18:22:40 +00:00
|
|
|
DMA_FROM_DEVICE);
|
2015-12-03 13:27:59 +00:00
|
|
|
ctx->sensePA = 0;
|
|
|
|
}
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
e->context = pvscsi_map_context(adapter, ctx);
|
|
|
|
|
|
|
|
barrier();
|
|
|
|
|
|
|
|
s->reqProdIdx++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-11-16 07:10:29 +00:00
|
|
|
static int pvscsi_queue_lck(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
|
2009-10-13 21:51:05 +00:00
|
|
|
{
|
|
|
|
struct Scsi_Host *host = cmd->device->host;
|
|
|
|
struct pvscsi_adapter *adapter = shost_priv(host);
|
|
|
|
struct pvscsi_ctx *ctx;
|
|
|
|
unsigned long flags;
|
2019-06-19 07:05:41 +00:00
|
|
|
unsigned char op;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
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;
|
2019-06-19 07:05:41 +00:00
|
|
|
op = cmd->cmnd[0];
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
dev_dbg(&cmd->device->sdev_gendev,
|
2019-06-19 07:05:41 +00:00
|
|
|
"queued cmd %p, ctx %p, op=%x\n", cmd, ctx, op);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
spin_unlock_irqrestore(&adapter->hw_lock, flags);
|
|
|
|
|
2019-06-19 07:05:41 +00:00
|
|
|
pvscsi_kick_io(adapter, op);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-11-16 07:10:29 +00:00
|
|
|
static DEF_SCSI_QCMD(pvscsi_queue)
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
static int pvscsi_abort(struct scsi_cmnd *cmd)
|
|
|
|
{
|
|
|
|
struct pvscsi_adapter *adapter = shost_priv(cmd->device->host);
|
|
|
|
struct pvscsi_ctx *ctx;
|
|
|
|
unsigned long flags;
|
2014-03-08 20:51:12 +00:00
|
|
|
int result = SUCCESS;
|
|
|
|
DECLARE_COMPLETION_ONSTACK(abort_cmp);
|
2016-10-28 16:27:26 +00:00
|
|
|
int done;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-03-08 20:51:12 +00:00
|
|
|
/*
|
|
|
|
* Mark that the command has been requested to be aborted and issue
|
|
|
|
* the abort.
|
|
|
|
*/
|
|
|
|
ctx->abort_cmp = &abort_cmp;
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
pvscsi_abort_cmd(adapter, ctx);
|
2014-03-08 20:51:12 +00:00
|
|
|
spin_unlock_irqrestore(&adapter->hw_lock, flags);
|
|
|
|
/* Wait for 2 secs for the completion. */
|
2016-10-28 16:27:26 +00:00
|
|
|
done = wait_for_completion_timeout(&abort_cmp, msecs_to_jiffies(2000));
|
2014-03-08 20:51:12 +00:00
|
|
|
spin_lock_irqsave(&adapter->hw_lock, flags);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
2016-10-28 16:27:26 +00:00
|
|
|
if (!done) {
|
2014-03-08 20:51:12 +00:00
|
|
|
/*
|
|
|
|
* Failed to abort the command, unmark the fact that it
|
|
|
|
* was requested to be aborted.
|
|
|
|
*/
|
|
|
|
ctx->abort_cmp = NULL;
|
|
|
|
result = FAILED;
|
|
|
|
scmd_printk(KERN_DEBUG, cmd,
|
|
|
|
"Failed to get completion for aborted cmd %p\n",
|
|
|
|
cmd);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Successfully aborted the command.
|
|
|
|
*/
|
|
|
|
cmd->result = (DID_ABORT << 16);
|
|
|
|
cmd->scsi_done(cmd);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
out:
|
|
|
|
spin_unlock_irqrestore(&adapter->hw_lock, flags);
|
2014-03-08 20:51:12 +00:00
|
|
|
return result;
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
2019-12-03 19:30:51 +00:00
|
|
|
pvscsi_patch_sense(cmd);
|
2009-10-13 21:51:05 +00:00
|
|
|
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) {
|
2020-04-30 12:17:29 +00:00
|
|
|
adapter->use_msg = false;
|
2009-10-13 21:51:05 +00:00
|
|
|
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,
|
2014-03-08 21:04:45 +00:00
|
|
|
.change_queue_depth = pvscsi_change_queue_depth,
|
2009-10-13 21:51:05 +00:00
|
|
|
.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;
|
|
|
|
}
|
|
|
|
|
2014-03-11 20:51:33 +00:00
|
|
|
static bool pvscsi_setup_req_threshold(struct pvscsi_adapter *adapter,
|
|
|
|
bool enable)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
if (!pvscsi_use_req_threshold)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND,
|
|
|
|
PVSCSI_CMD_SETUP_REQCALLTHRESHOLD);
|
|
|
|
val = pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_COMMAND_STATUS);
|
|
|
|
if (val == -1) {
|
|
|
|
printk(KERN_INFO "vmw_pvscsi: device does not support req_threshold\n");
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
struct PVSCSICmdDescSetupReqCall cmd_msg = { 0 };
|
|
|
|
cmd_msg.enable = enable;
|
|
|
|
printk(KERN_INFO
|
|
|
|
"vmw_pvscsi: %sabling reqCallThreshold\n",
|
|
|
|
enable ? "en" : "dis");
|
|
|
|
pvscsi_write_cmd_desc(adapter,
|
|
|
|
PVSCSI_CMD_SETUP_REQCALLTHRESHOLD,
|
|
|
|
&cmd_msg, sizeof(cmd_msg));
|
|
|
|
return pvscsi_reg_read(adapter,
|
|
|
|
PVSCSI_REG_OFFSET_COMMAND_STATUS) != 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
static irqreturn_t pvscsi_isr(int irq, void *devp)
|
|
|
|
{
|
|
|
|
struct pvscsi_adapter *adapter = devp;
|
2016-11-18 06:02:38 +00:00
|
|
|
unsigned long flags;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
2016-11-18 06:02:38 +00:00
|
|
|
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);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
2016-11-18 06:02:38 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
2009-10-13 21:51:05 +00:00
|
|
|
|
2016-11-18 06:02:38 +00:00
|
|
|
static irqreturn_t pvscsi_shared_isr(int irq, void *devp)
|
|
|
|
{
|
|
|
|
struct pvscsi_adapter *adapter = devp;
|
|
|
|
u32 val = pvscsi_read_intr_status(adapter);
|
2009-10-13 21:51:05 +00:00
|
|
|
|
2016-11-18 06:02:38 +00:00
|
|
|
if (!(val & PVSCSI_INTR_ALL_SUPPORTED))
|
|
|
|
return IRQ_NONE;
|
|
|
|
pvscsi_write_intr_status(devp, val);
|
|
|
|
return pvscsi_isr(irq, devp);
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 void pvscsi_shutdown_intr(struct pvscsi_adapter *adapter)
|
|
|
|
{
|
2016-11-18 06:02:38 +00:00
|
|
|
free_irq(pci_irq_vector(adapter->dev, 0), adapter);
|
|
|
|
pci_free_irq_vectors(adapter->dev);
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void pvscsi_release_resources(struct pvscsi_adapter *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)
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_free_coherent(&adapter->dev->dev, PAGE_SIZE,
|
2009-10-13 21:51:05 +00:00
|
|
|
adapter->rings_state, adapter->ringStatePA);
|
|
|
|
|
|
|
|
if (adapter->req_ring)
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_free_coherent(&adapter->dev->dev,
|
2009-10-13 21:51:05 +00:00
|
|
|
adapter->req_pages * PAGE_SIZE,
|
|
|
|
adapter->req_ring, adapter->reqRingPA);
|
|
|
|
|
|
|
|
if (adapter->cmp_ring)
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_free_coherent(&adapter->dev->dev,
|
2009-10-13 21:51:05 +00:00
|
|
|
adapter->cmp_pages * PAGE_SIZE,
|
|
|
|
adapter->cmp_ring, adapter->cmpRingPA);
|
|
|
|
|
|
|
|
if (adapter->msg_ring)
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_free_coherent(&adapter->dev->dev,
|
2009-10-13 21:51:05 +00:00
|
|
|
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.
|
|
|
|
*
|
2011-11-29 04:31:00 +00:00
|
|
|
* Dynamic allocation can fail, and we can't go deep into the memory
|
2009-10-13 21:51:05 +00:00
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
*/
|
2012-12-21 21:08:55 +00:00
|
|
|
static int pvscsi_allocate_sg(struct pvscsi_adapter *adapter)
|
2009-10-13 21:51:05 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-03-08 10:18:53 +00:00
|
|
|
/*
|
|
|
|
* Query the device, fetch the config info and return the
|
|
|
|
* maximum number of targets on the adapter. In case of
|
|
|
|
* failure due to any reason return default i.e. 16.
|
|
|
|
*/
|
|
|
|
static u32 pvscsi_get_max_targets(struct pvscsi_adapter *adapter)
|
|
|
|
{
|
|
|
|
struct PVSCSICmdDescConfigCmd cmd;
|
|
|
|
struct PVSCSIConfigPageHeader *header;
|
|
|
|
struct device *dev;
|
|
|
|
dma_addr_t configPagePA;
|
|
|
|
void *config_page;
|
|
|
|
u32 numPhys = 16;
|
|
|
|
|
|
|
|
dev = pvscsi_dev(adapter);
|
2018-10-10 18:22:40 +00:00
|
|
|
config_page = dma_alloc_coherent(&adapter->dev->dev, PAGE_SIZE,
|
|
|
|
&configPagePA, GFP_KERNEL);
|
2012-03-08 10:18:53 +00:00
|
|
|
if (!config_page) {
|
|
|
|
dev_warn(dev, "vmw_pvscsi: failed to allocate memory for config page\n");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
BUG_ON(configPagePA & ~PAGE_MASK);
|
|
|
|
|
|
|
|
/* Fetch config info from the device. */
|
|
|
|
cmd.configPageAddress = ((u64)PVSCSI_CONFIG_CONTROLLER_ADDRESS) << 32;
|
|
|
|
cmd.configPageNum = PVSCSI_CONFIG_PAGE_CONTROLLER;
|
|
|
|
cmd.cmpAddr = configPagePA;
|
|
|
|
cmd._pad = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Mark the completion page header with error values. If the device
|
|
|
|
* completes the command successfully, it sets the status values to
|
|
|
|
* indicate success.
|
|
|
|
*/
|
|
|
|
header = config_page;
|
|
|
|
memset(header, 0, sizeof *header);
|
|
|
|
header->hostStatus = BTSTAT_INVPARAM;
|
|
|
|
header->scsiStatus = SDSTAT_CHECK;
|
|
|
|
|
|
|
|
pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_CONFIG, &cmd, sizeof cmd);
|
|
|
|
|
|
|
|
if (header->hostStatus == BTSTAT_SUCCESS &&
|
|
|
|
header->scsiStatus == SDSTAT_GOOD) {
|
|
|
|
struct PVSCSIConfigPageController *config;
|
|
|
|
|
|
|
|
config = config_page;
|
|
|
|
numPhys = config->numPhys;
|
|
|
|
} else
|
|
|
|
dev_warn(dev, "vmw_pvscsi: PVSCSI_CMD_CONFIG failed. hostStatus = 0x%x, scsiStatus = 0x%x\n",
|
|
|
|
header->hostStatus, header->scsiStatus);
|
2018-10-10 18:22:40 +00:00
|
|
|
dma_free_coherent(&adapter->dev->dev, PAGE_SIZE, config_page,
|
|
|
|
configPagePA);
|
2012-03-08 10:18:53 +00:00
|
|
|
exit:
|
|
|
|
return numPhys;
|
|
|
|
}
|
|
|
|
|
2012-12-21 21:08:55 +00:00
|
|
|
static int pvscsi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
2009-10-13 21:51:05 +00:00
|
|
|
{
|
2016-11-18 06:02:38 +00:00
|
|
|
unsigned int irq_flag = PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY;
|
2009-10-13 21:51:05 +00:00
|
|
|
struct pvscsi_adapter *adapter;
|
2014-03-08 21:04:45 +00:00
|
|
|
struct pvscsi_adapter adapter_temp;
|
|
|
|
struct Scsi_Host *host = NULL;
|
2009-10-13 21:51:05 +00:00
|
|
|
unsigned int i;
|
|
|
|
int error;
|
2014-03-08 21:04:45 +00:00
|
|
|
u32 max_id;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
|
|
|
error = -ENODEV;
|
|
|
|
|
|
|
|
if (pci_enable_device(pdev))
|
|
|
|
return error;
|
|
|
|
|
2018-10-10 18:22:40 +00:00
|
|
|
if (!dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))) {
|
2009-10-13 21:51:05 +00:00
|
|
|
printk(KERN_INFO "vmw_pvscsi: using 64bit dma\n");
|
2018-10-10 18:22:40 +00:00
|
|
|
} else if (!dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))) {
|
2009-10-13 21:51:05 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-03-08 21:04:45 +00:00
|
|
|
/*
|
|
|
|
* Let's use a temp pvscsi_adapter struct until we find the number of
|
|
|
|
* targets on the adapter, after that we will switch to the real
|
|
|
|
* allocated struct.
|
|
|
|
*/
|
|
|
|
adapter = &adapter_temp;
|
2009-10-13 21:51:05 +00:00
|
|
|
memset(adapter, 0, sizeof(*adapter));
|
|
|
|
adapter->dev = pdev;
|
|
|
|
adapter->rev = pdev->revision;
|
|
|
|
|
|
|
|
if (pci_request_regions(pdev, "vmw_pvscsi")) {
|
|
|
|
printk(KERN_ERR "vmw_pvscsi: pci memory selection failed\n");
|
2014-03-08 21:04:45 +00:00
|
|
|
goto out_disable_device;
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2014-03-08 21:04:45 +00:00
|
|
|
goto out_release_resources_and_disable;
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2014-03-08 21:04:45 +00:00
|
|
|
goto out_release_resources_and_disable;
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pci_set_master(pdev);
|
2014-03-08 21:04:45 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Ask the device for max number of targets before deciding the
|
|
|
|
* default pvscsi_ring_pages value.
|
|
|
|
*/
|
|
|
|
max_id = pvscsi_get_max_targets(adapter);
|
|
|
|
printk(KERN_INFO "vmw_pvscsi: max_id: %u\n", max_id);
|
|
|
|
|
|
|
|
if (pvscsi_ring_pages == 0)
|
|
|
|
/*
|
|
|
|
* Set the right default value. Up to 16 it is 8, above it is
|
|
|
|
* max.
|
|
|
|
*/
|
|
|
|
pvscsi_ring_pages = (max_id > 16) ?
|
|
|
|
PVSCSI_SETUP_RINGS_MAX_NUM_PAGES :
|
|
|
|
PVSCSI_DEFAULT_NUM_PAGES_PER_RING;
|
|
|
|
printk(KERN_INFO
|
|
|
|
"vmw_pvscsi: setting ring_pages to %d\n",
|
|
|
|
pvscsi_ring_pages);
|
|
|
|
|
|
|
|
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_release_resources_and_disable;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Let's use the real pvscsi_adapter struct here onwards.
|
|
|
|
*/
|
|
|
|
adapter = shost_priv(host);
|
|
|
|
memset(adapter, 0, sizeof(*adapter));
|
|
|
|
adapter->dev = pdev;
|
|
|
|
adapter->host = host;
|
|
|
|
/*
|
|
|
|
* Copy back what we already have to the allocated adapter struct.
|
|
|
|
*/
|
|
|
|
adapter->rev = adapter_temp.rev;
|
|
|
|
adapter->mmioBase = adapter_temp.mmioBase;
|
|
|
|
|
|
|
|
spin_lock_init(&adapter->hw_lock);
|
|
|
|
host->max_channel = 0;
|
|
|
|
host->max_lun = 1;
|
|
|
|
host->max_cmd_len = 16;
|
|
|
|
host->max_id = max_id;
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-11-18 06:02:38 +00:00
|
|
|
if (pvscsi_disable_msix)
|
|
|
|
irq_flag &= ~PCI_IRQ_MSIX;
|
|
|
|
if (pvscsi_disable_msi)
|
|
|
|
irq_flag &= ~PCI_IRQ_MSI;
|
|
|
|
|
|
|
|
error = pci_alloc_irq_vectors(adapter->dev, 1, 1, irq_flag);
|
2017-03-03 14:57:16 +00:00
|
|
|
if (error < 0)
|
2016-11-18 06:02:38 +00:00
|
|
|
goto out_reset_adapter;
|
2009-10-13 21:51:05 +00:00
|
|
|
|
2014-03-11 20:51:33 +00:00
|
|
|
adapter->use_req_threshold = pvscsi_setup_req_threshold(adapter, true);
|
|
|
|
printk(KERN_DEBUG "vmw_pvscsi: driver-based request coalescing %sabled\n",
|
|
|
|
adapter->use_req_threshold ? "en" : "dis");
|
|
|
|
|
2016-11-18 06:02:38 +00:00
|
|
|
if (adapter->dev->msix_enabled || adapter->dev->msi_enabled) {
|
|
|
|
printk(KERN_INFO "vmw_pvscsi: using MSI%s\n",
|
|
|
|
adapter->dev->msix_enabled ? "-X" : "");
|
|
|
|
error = request_irq(pci_irq_vector(pdev, 0), pvscsi_isr,
|
|
|
|
0, "vmw_pvscsi", adapter);
|
|
|
|
} else {
|
|
|
|
printk(KERN_INFO "vmw_pvscsi: using INTx\n");
|
|
|
|
error = request_irq(pci_irq_vector(pdev, 0), pvscsi_shared_isr,
|
|
|
|
IRQF_SHARED, "vmw_pvscsi", adapter);
|
|
|
|
}
|
|
|
|
|
2009-10-13 21:51:05 +00:00
|
|
|
if (error) {
|
|
|
|
printk(KERN_ERR
|
|
|
|
"vmw_pvscsi: unable to request IRQ: %d\n", error);
|
|
|
|
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:
|
2018-11-27 19:28:53 +00:00
|
|
|
pvscsi_shutdown_intr(adapter);
|
2009-10-13 21:51:05 +00:00
|
|
|
pvscsi_release_resources(adapter);
|
|
|
|
scsi_host_put(host);
|
|
|
|
out_disable_device:
|
|
|
|
pci_disable_device(pdev);
|
|
|
|
|
|
|
|
return error;
|
2014-03-08 21:04:45 +00:00
|
|
|
|
|
|
|
out_release_resources_and_disable:
|
2018-11-27 19:28:53 +00:00
|
|
|
pvscsi_shutdown_intr(adapter);
|
2014-03-08 21:04:45 +00:00
|
|
|
pvscsi_release_resources(adapter);
|
|
|
|
goto out_disable_device;
|
2009-10-13 21:51:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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_disable_device(pdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct pci_driver pvscsi_pci_driver = {
|
|
|
|
.name = "vmw_pvscsi",
|
|
|
|
.id_table = pvscsi_pci_tbl,
|
|
|
|
.probe = pvscsi_probe,
|
2012-12-21 21:08:55 +00:00
|
|
|
.remove = pvscsi_remove,
|
2009-10-13 21:51:05 +00:00
|
|
|
.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);
|