| /* | |
| * 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: Arvind Kumar <arvindkumar@vmware.com> | |
| * | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/slab.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), "Resetting 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), "Resetting 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 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_lck(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 DEF_SCSI_QCMD(pvscsi_queue) | |
| 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, | |
| unsigned 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 deep 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 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; | |
| } | |
| /* | |
| * 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); | |
| config_page = pci_alloc_consistent(adapter->dev, PAGE_SIZE, | |
| &configPagePA); | |
| 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); | |
| pci_free_consistent(adapter->dev, PAGE_SIZE, config_page, configPagePA); | |
| exit: | |
| return numPhys; | |
| } | |
| static int pvscsi_probe(struct pci_dev *pdev, const struct pci_device_id *id) | |
| { | |
| struct pvscsi_adapter *adapter; | |
| struct Scsi_Host *host; | |
| struct device *dev; | |
| 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; | |
| } | |
| /* | |
| * Ask the device for max number of targets. | |
| */ | |
| host->max_id = pvscsi_get_max_targets(adapter); | |
| dev = pvscsi_dev(adapter); | |
| dev_info(dev, "vmw_pvscsi: host->max_id: %u\n", host->max_id); | |
| /* | |
| * 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_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_disable_device(pdev); | |
| } | |
| static struct pci_driver pvscsi_pci_driver = { | |
| .name = "vmw_pvscsi", | |
| .id_table = pvscsi_pci_tbl, | |
| .probe = pvscsi_probe, | |
| .remove = 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); |