Index: sys/dev/vmware/pvscsi/compat_freebsd.h =================================================================== --- /dev/null +++ sys/dev/vmware/pvscsi/compat_freebsd.h @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2011-2013 + * Isilon Systems, LLC. All rights reserved. + */ + +/* + * Some of the code in here copied from src/sys/infiniband/legacy/fbsd_pci.h + */ + +#ifndef _COMPAT_FREEBSD_H_ +#define _COMPAT_FREEBSD_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "fbsd_list.h" + +#define printk printf + +#define KERN_WARNING "Warning:" +#define KERN_DEBUG "Debug:" +#define KERN_ERR "Error:" +#define KERN_INFO "Info:" +#define KERN_NOTICE "Notice:" + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +typedef u_int8_t u8; +typedef u_int16_t u16; +typedef u_int32_t u32; +typedef u_int64_t u64; + +typedef u_int16_t __be16; +typedef u_int32_t __be32; +typedef u_int64_t __be64; + +#define BUG_ON(x) KASSERT(!(x), ("BUG")) + +#define irqreturn_t int + +/* #define dev_dbg device_printf */ +#define LOG(d, ...) + +typedef bus_addr_t dma_addr_t; + +static inline vm_paddr_t +virt_to_phys(void *va) +{ + uintptr_t v = (uintptr_t)va; + return (vtophys(v & ~PAGE_MASK)+(v&PAGE_MASK)); +} + +MALLOC_DECLARE(M_PVSCSI); +MALLOC_DECLARE(M_PVSCSI_PCI); +MALLOC_DECLARE(M_PVSCSI_SGL); + +static __inline void * +vmalloc(size_t size, int flags) +{ + return malloc(size, M_PVSCSI, flags); +} + +static __inline void +vfree(void *addr) +{ + return free(addr, M_PVSCSI); +} + +/* + * pci_alloc_consistent returns two values: the virtual address which + * you can use to access it from the CPU and dma_handle which you pass + * to the card. + */ +static inline void * +pci_alloc_consistent_fn(unsigned int size, dma_addr_t *phys) +{ + void *r; + + /* + * Note that BSD does not guarantee physically contiguous + * memory from malloc(), but contigmalloc is very slow. + */ + r = contigmalloc(size, M_PVSCSI_PCI, M_WAITOK, 0ul, ~0ul, PAGE_SIZE, 0); + if (r) + *phys = virt_to_phys(r); + + return(r); +} + +#define pci_alloc_consistent(d,s,p) pci_alloc_consistent_fn(s,p) + +static inline dma_addr_t +pci_map_single_fn(void *va, size_t len) +{ + bus_addr_t retval, lastb; + + retval = virt_to_phys(va); + if (len) { + lastb = virt_to_phys((char *)va + len - 1); + KASSERT((lastb & ~PAGE_MASK) == (retval & ~PAGE_MASK), + ("%lx %lx %p %zu", lastb, retval, va, len)); + } + + return retval; +} + +#define pci_map_single(dev,va,len,dir) pci_map_single_fn(va,len) + +static inline void * +kcalloc_fn(size_t n, size_t size) +{ + if (size != 0 && n > ULONG_MAX / size) + return NULL; + return contigmalloc(n * size, M_PVSCSI_PCI, M_WAITOK|M_ZERO, 0ul, + ~0ul, PAGE_SIZE, 0); +} + +#define kcalloc(n,s,f) kcalloc_fn(n,s) + +#define kfree(a,s) contigfree(a, s, M_PVSCSI_PCI); +#define pci_free_consistent(d,s,a,h) contigfree(a, s, M_PVSCSI_PCI); + + +static __inline void *__get_free_page_fn(void) +{ + return contigmalloc(PAGE_SIZE, M_PVSCSI_SGL, M_WAITOK, + 0ul, ~0ul, PAGE_SIZE, 0); +} +#define __get_free_page(f) __get_free_page_fn() + +static __inline void free_page(unsigned long p) +{ + contigfree((void *)p, PAGE_SIZE, M_PVSCSI_SGL); +} + +struct workqueue_struct { + struct taskqueue *taskqueue; +}; + +struct work_struct { + struct task work_task; + struct taskqueue *taskqueue; + void (*fn)(struct work_struct *); +}; + +struct delayed_work { + struct work_struct work; + struct callout timer; +}; + +static inline struct workqueue_struct * +_create_workqueue_common(char *name, int cpus) +{ + struct workqueue_struct *wq; + + wq = vmalloc(sizeof(*wq), M_WAITOK); + wq->taskqueue = taskqueue_create((name), M_WAITOK, + taskqueue_thread_enqueue, &wq->taskqueue); + taskqueue_start_threads(&wq->taskqueue, cpus, PWAIT, "pvscsi_wq"); + + return (wq); +} + + +#define create_singlethread_workqueue(name) \ + _create_workqueue_common(name, 1) + +static inline void +_work_fn(void *context, int pending) +{ + struct work_struct *work; + + work = context; + work->fn(work); +} + +#define COMPAT_INIT_WORK(work, func, dud) \ +do { \ + (work)->fn = (func); \ + (work)->taskqueue = NULL; \ + TASK_INIT(&(work)->work_task, 0, _work_fn, (work)); \ +} while (0) + +static inline void +destroy_workqueue(struct workqueue_struct *wq) +{ + taskqueue_free(wq->taskqueue); + vfree(wq); +} + +#define queue_work(q, work) \ +do { \ + (work)->taskqueue = (q)->taskqueue; \ + taskqueue_enqueue((q)->taskqueue, &(work)->work_task); \ +} while (0) + + +/* Optimization barrier */ +/* The "volatile" is due to gcc bugs */ +#define barrier() __asm__ __volatile__("": : :"memory") + +#define __devinit + +#define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0) + +#define typeof __typeof + +#define list_first_entry(h, t, m) list_entry((h)->next, t, m) + +#define smp_processor_id() PCPU_GET(cpuid) +#define SCSI_MLQUEUE_HOST_BUSY CAM_RESRC_UNAVAIL +#define SUCCESS 0 + +#define ASSERT_ON_COMPILE CTASSERT + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; +typedef uint64_t PA; + +#define QWORD(_hi, _lo) ((((uint64)(_hi)) << 32) | ((uint32)(_lo))) + +#define COMPAT_WORK_GET_DATA container_of + +#ifndef ISILON +#define __container_of(entry, type, field, uniq) ({ \ + const __typeof(((type *)NULL)->field) *uniq = (entry); \ + \ + (uniq == NULL) ? NULL : \ + (type *)((uintptr_t)uniq - __offsetof(type, field)); \ +}) +/* Create a unique variable name to allow nested calls without shadowing */ +#define __UNIQ __CONCAT(__uniq, __COUNTER__) +#ifndef container_of +#define container_of(entry, type, field) \ + __container_of(entry, type, field, __UNIQ) +#endif +#endif +#endif /* _COMPAT_FREEBSD_H_ */ Index: sys/dev/vmware/pvscsi/fbsd_list.h =================================================================== --- /dev/null +++ sys/dev/vmware/pvscsi/fbsd_list.h @@ -0,0 +1,24 @@ +#ifndef _FBSD_LIST_H_ +#define _FBSD_LIST_H_ + +struct list_head { + struct list_head *next,*last; +}; +#define INIT_LIST_HEAD(a) {(a)->next=(a)->last=(a);} +#define list_entry(p,t,m) container_of(p, t, m) + +#define list_empty(a) ((a)->next==(a)) + +#define list_del(a)\ + {(a)->last->next=(a)->next;\ + (a)->next->last=(a)->last;} + +#define list_del_rcu(a)\ + {atomic_store_rel_ptr(&(a)->last->next, (a)->next);\ + (a)->next->last=(a)->last;} + +#define list_add(a,b)\ + {(a)->last=(b); (a)->next=(b)->next ;\ + (a)->next->last=(a); (b)->next=(a);} + +#endif /* !_FBSD_LIST_H_ */ Index: sys/dev/vmware/pvscsi/vmw_pvscsi.h =================================================================== --- /dev/null +++ sys/dev/vmware/pvscsi/vmw_pvscsi.h @@ -0,0 +1,603 @@ +/* ************************************************************************ + * Copyright 2008 VMware, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ************************************************************************/ + +#ifndef _VMW_PVSCSI_H_ +#define _VMW_PVSCSI_H_ + +#include "compat_freebsd.h" +#ifdef __ISILON__ +#include +#include +#include +#endif +#include + +#define PVSCSI_DRIVER_VERSION_STRING "1.0.1.0-k" + +#define PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT 128 + +#define MASK(n) ((1 << (n)) - 1) /* make an n-bit mask */ + +#define PCI_VENDOR_ID_VMWARE 0x15AD +#define PCI_DEVICE_ID_VMWARE_PVSCSI 0x07C0 + +/* + * host adapter status/error codes + */ +enum HostBusAdapterStatus { + BTSTAT_SUCCESS = 0x00, // CCB complete normally with no errors + BTSTAT_LINKED_COMMAND_COMPLETED = 0x0a, + BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG = 0x0b, + BTSTAT_DATA_UNDERRUN = 0x0c, + BTSTAT_SELTIMEO = 0x11, // SCSI selection timeout + BTSTAT_DATARUN = 0x12, // data overrun/underrun + BTSTAT_BUSFREE = 0x13, // unexpected bus free + BTSTAT_INVPHASE = 0x14, // invalid bus phase or sequence requested by target + BTSTAT_INVCODE = 0x15, // invalid action code in outgoing mailbox + BTSTAT_INVOPCODE = 0x16, // invalid operation code in CCB + BTSTAT_LUNMISMATCH = 0x17, // linked CCB has different LUN from first CCB + BTSTAT_INVPARAM = 0x1a, // invalid parameter in CCB or segment list + BTSTAT_SENSFAILED = 0x1b, // auto request sense failed + BTSTAT_TAGREJECT = 0x1c, // SCSI II tagged queueing message rejected by target + BTSTAT_BADMSG = 0x1d, // unsupported message received by the host adapter + BTSTAT_HAHARDWARE = 0x20, // host adapter hardware failed + BTSTAT_NORESPONSE = 0x21, // target did not respond to SCSI ATN, sent a SCSI RST + BTSTAT_SENTRST = 0x22, // host adapter asserted a SCSI RST + BTSTAT_RECVRST = 0x23, // other SCSI devices asserted a SCSI RST + BTSTAT_DISCONNECT = 0x24, // target device reconnected improperly (w/o tag) + BTSTAT_BUSRESET = 0x25, // host adapter issued BUS device reset + BTSTAT_ABORTQUEUE = 0x26, // abort queue generated + BTSTAT_HASOFTWARE = 0x27, // host adapter software error + BTSTAT_HATIMEOUT = 0x30, // host adapter hardware timeout error + BTSTAT_SCSIPARITY = 0x34, // SCSI parity error detected +}; + +// scsi device status values +typedef enum { + SDSTAT_GOOD = 0x00, // no errors + SDSTAT_CHECK = 0x02, // check condition + SDSTAT_CONDITION_MET = 0x04, // condition met + SDSTAT_BUSY = 0x08, // device busy + SDSTAT_INTERMEDIATE = 0x10, + SDSTAT_INTERMEDIATE_CONDITION = 0x14, + SDSTAT_RESERVATION_CONFLICT = 0x18, // device reserved by another host + SDSTAT_COMMAND_TERMINATED = 0x22, + SDSTAT_TASK_SET_FULL = 0x28, + SDSTAT_ACA_ACTIVE = 0x30, + SDSTAT_TASK_ABORTED = 0x40, +} SCSIDeviceStatus; + +/* + * Register offsets. + * + * These registers are accessible both via i/o space and mm i/o. + */ + +enum PVSCSIRegOffset { + PVSCSI_REG_OFFSET_COMMAND = 0x0, + PVSCSI_REG_OFFSET_COMMAND_DATA = 0x4, + PVSCSI_REG_OFFSET_COMMAND_STATUS = 0x8, + PVSCSI_REG_OFFSET_LAST_STS_0 = 0x100, + PVSCSI_REG_OFFSET_LAST_STS_1 = 0x104, + PVSCSI_REG_OFFSET_LAST_STS_2 = 0x108, + PVSCSI_REG_OFFSET_LAST_STS_3 = 0x10c, + PVSCSI_REG_OFFSET_INTR_STATUS = 0x100c, + PVSCSI_REG_OFFSET_INTR_MASK = 0x2010, + PVSCSI_REG_OFFSET_KICK_NON_RW_IO = 0x3014, + PVSCSI_REG_OFFSET_DEBUG = 0x3018, + PVSCSI_REG_OFFSET_KICK_RW_IO = 0x4018, +}; + + +/* + * Configuration pages. Structure sizes are 4 byte multiples. + */ + +enum PVSCSIConfigPageType { + PVSCSI_CONFIG_PAGE_CONTROLLER = 0x1958, + PVSCSI_CONFIG_PAGE_PHY = 0x1959, + PVSCSI_CONFIG_PAGE_DEVICE = 0x195a, +}; + + +enum PVSCSIConfigPageAddressType { + PVSCSI_CONFIG_CONTROLLER_ADDRESS = 0x2120, + PVSCSI_CONFIG_BUSTARGET_ADDRESS = 0x2121, + PVSCSI_CONFIG_PHY_ADDRESS = 0x2122, +}; + +struct PVSCSIConfigPageHeader { + uint32 pageNum; + uint16 numDwords; /* Including the header. */ + uint16 hostStatus; + uint16 scsiStatus; + uint16 reserved[3]; +} __packed; +typedef struct PVSCSIConfigPageHeader PVSCSIConfigPageHeader; + + +struct PVSCSIConfigPageDevice { + PVSCSIConfigPageHeader header; + uint64 deviceWWN; + uint64 phyWWN; + uint32 phyNum; + uint8 target; + uint8 bus; + uint8 reserved[2]; +} __packed; +typedef struct PVSCSIConfigPageDevice PVSCSIConfigPageDevice; + + +struct PVSCSIConfigPageController { + PVSCSIConfigPageHeader header; + uint64 nodeWWN; /* Device name as defined in the SAS spec. */ + uint16 manufacturer[64]; + uint16 serialNumber[64]; + uint16 opromVersion[32]; + uint16 hwVersion[32]; + uint16 firmwareVersion[32]; + uint32 numPhys; + uint8 useConsecutivePhyWWNs; + uint8 reserved[3]; +} __packed; +typedef struct PVSCSIConfigPageController PVSCSIConfigPageController; + + +/* + * Virtual h/w commands. + */ + +enum PVSCSICommands { + PVSCSI_CMD_FIRST = 0, /* has to be first */ + + PVSCSI_CMD_ADAPTER_RESET = 1, + PVSCSI_CMD_ISSUE_SCSI = 2, + PVSCSI_CMD_SETUP_RINGS = 3, + PVSCSI_CMD_RESET_BUS = 4, + PVSCSI_CMD_RESET_DEVICE = 5, + PVSCSI_CMD_ABORT_CMD = 6, + PVSCSI_CMD_CONFIG = 7, + PVSCSI_CMD_SETUP_MSG_RING = 8, + PVSCSI_CMD_DEVICE_UNPLUG = 9, + + PVSCSI_CMD_LAST = 10 /* has to be last */ +}; + +/* + * Command descriptor for PVSCSI_CMD_RESET_DEVICE -- + */ + +struct PVSCSICmdDescResetDevice { + u32 target; + u8 lun[8]; +} __packed; + +/* + * Command descriptor for PVSCSI_CMD_ABORT_CMD -- + * + * - currently does not support specifying the LUN. + * - _pad should be 0. + */ + +struct PVSCSICmdDescAbortCmd { + u64 context; + u32 target; + u32 _pad; +} __packed; + +/* + * Command descriptor for PVSCSI_CMD_SETUP_RINGS -- + * + * Notes: + * - reqRingNumPages and cmpRingNumPages need to be power of two. + * - reqRingNumPages and cmpRingNumPages need to be different from 0, + * - reqRingNumPages and cmpRingNumPages need to be inferior to + * PVSCSI_SETUP_RINGS_MAX_NUM_PAGES. + */ + +#define PVSCSI_SETUP_RINGS_MAX_NUM_PAGES 32 +struct PVSCSICmdDescSetupRings { + u32 reqRingNumPages; + u32 cmpRingNumPages; + u64 ringsStatePPN; + u64 reqRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES]; + u64 cmpRingPPNs[PVSCSI_SETUP_RINGS_MAX_NUM_PAGES]; +} __packed; + + +/* + * Command descriptor for PVSCSI_CMD_CONFIG -- + */ + +struct PVSCSICmdDescConfigCmd { + PA cmpAddr; + uint64 configPageAddress; + uint32 configPageNum; + uint32 _pad; +} __packed; +typedef struct PVSCSICmdDescConfigCmd PVSCSICmdDescConfigCmd; + + +/* + * Command descriptor for PVSCSI_CMD_SETUP_MSG_RING -- + * + * Notes: + * - this command was not supported in the initial revision of the h/w + * interface. Before using it, you need to check that it is supported by + * writing PVSCSI_CMD_SETUP_MSG_RING to the 'command' register, then + * immediately after read the 'command status' register: + * * a value of -1 means that the cmd is NOT supported, + * * a value != -1 means that the cmd IS supported. + * If it's supported the 'command status' register should return: + * sizeof(PVSCSICmdDescSetupMsgRing) / sizeof(u32). + * - this command should be issued _after_ the usual SETUP_RINGS so that the + * RingsState page is already setup. If not, the command is a nop. + * - numPages needs to be a power of two, + * - numPages needs to be different from 0, + * - _pad should be zero. + */ + +#define PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES 16 + +struct PVSCSICmdDescSetupMsgRing { + u32 numPages; + u32 _pad; + u64 ringPPNs[PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES]; +} __packed; + +enum PVSCSIMsgType { + PVSCSI_MSG_DEV_ADDED = 0, + PVSCSI_MSG_DEV_REMOVED = 1, + PVSCSI_MSG_LAST = 2, +}; + +/* + * Msg descriptor. + * + * sizeof(struct PVSCSIRingMsgDesc) == 128. + * + * - type is of type enum PVSCSIMsgType. + * - the content of args depend on the type of event being delivered. + */ + +struct PVSCSIRingMsgDesc { + u32 type; + u32 args[31]; +} __packed; + +struct PVSCSIMsgDescDevStatusChanged { + u32 type; /* PVSCSI_MSG_DEV _ADDED / _REMOVED */ + u32 bus; + u32 target; + u8 lun[8]; + u32 pad[27]; +} __packed; + +/* + * Rings state. + * + * - the fields: + * . msgProdIdx, + * . msgConsIdx, + * . msgNumEntriesLog2, + * .. are only used once the SETUP_MSG_RING cmd has been issued. + * - '_pad' helps to ensure that the msg related fields are on their own + * cache-line. + */ + +struct PVSCSIRingsState { + u32 reqProdIdx; + u32 reqConsIdx; + u32 reqNumEntriesLog2; + + u32 cmpProdIdx; + u32 cmpConsIdx; + u32 cmpNumEntriesLog2; + + u8 _pad[104]; + + u32 msgProdIdx; + u32 msgConsIdx; + u32 msgNumEntriesLog2; +} __packed; + +/* + * Request descriptor. + * + * sizeof(RingReqDesc) = 128 + * + * - context: is a unique identifier of a command. It could normally be any + * 64bit value, however we currently store it in the serialNumber variable + * of struct SCSI_Command, so we have the following restrictions due to the + * way this field is handled in the vmkernel storage stack: + * * this value can't be 0, + * * the upper 32bit need to be 0 since serialNumber is as a u32. + * Currently tracked as PR 292060. + * - dataLen: contains the total number of bytes that need to be transferred. + * - dataAddr: + * * if PVSCSI_FLAG_CMD_WITH_SG_LIST is set: dataAddr is the PA of the first + * s/g table segment, each s/g segment is entirely contained on a single + * page of physical memory, + * * if PVSCSI_FLAG_CMD_WITH_SG_LIST is NOT set, then dataAddr is the PA of + * the buffer used for the DMA transfer, + * - flags: + * * PVSCSI_FLAG_CMD_WITH_SG_LIST: see dataAddr above, + * * PVSCSI_FLAG_CMD_DIR_NONE: no DMA involved, + * * PVSCSI_FLAG_CMD_DIR_TOHOST: transfer from device to main memory, + * * PVSCSI_FLAG_CMD_DIR_TODEVICE: transfer from main memory to device, + * * PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB: reserved to handle CDBs larger than + * 16bytes. To be specified. + * - vcpuHint: vcpuId of the processor that will be most likely waiting for the + * completion of the i/o. For guest OSes that use lowest priority message + * delivery mode (such as windows), we use this "hint" to deliver the + * completion action to the proper vcpu. For now, we can use the vcpuId of + * the processor that initiated the i/o as a likely candidate for the vcpu + * that will be waiting for the completion.. + * - bus should be 0: we currently only support bus 0 for now. + * - unused should be zero'd. + */ + +#define PVSCSI_FLAG_CMD_WITH_SG_LIST (1 << 0) +#define PVSCSI_FLAG_CMD_OUT_OF_BAND_CDB (1 << 1) +#define PVSCSI_FLAG_CMD_DIR_NONE (1 << 2) +#define PVSCSI_FLAG_CMD_DIR_TOHOST (1 << 3) +#define PVSCSI_FLAG_CMD_DIR_TODEVICE (1 << 4) + +struct PVSCSIRingReqDesc { + u64 context; + u64 dataAddr; + u64 dataLen; + u64 senseAddr; + u32 senseLen; + u32 flags; + u8 cdb[16]; + u8 cdbLen; + u8 lun[8]; + u8 tag; + u8 bus; + u8 target; + u8 vcpuHint; + u8 unused[59]; +} __packed; + +/* + * Scatter-gather list management. + * + * As described above, when PVSCSI_FLAG_CMD_WITH_SG_LIST is set in the + * RingReqDesc.flags, then RingReqDesc.dataAddr is the PA of the first s/g + * table segment. + * + * - each segment of the s/g table contain a succession of struct + * PVSCSISGElement. + * - each segment is entirely contained on a single physical page of memory. + * - a "chain" s/g element has the flag PVSCSI_SGE_FLAG_CHAIN_ELEMENT set in + * PVSCSISGElement.flags and in this case: + * * addr is the PA of the next s/g segment, + * * length is undefined, assumed to be 0. + */ + +struct PVSCSISGElement { + u64 addr; + u32 length; + u32 flags; +} __packed; + +/* + * Completion descriptor. + * + * sizeof(RingCmpDesc) = 32 + * + * - context: identifier of the command. The same thing that was specified + * under "context" as part of struct RingReqDesc at initiation time, + * - dataLen: number of bytes transferred for the actual i/o operation, + * - senseLen: number of bytes written into the sense buffer, + * - hostStatus: adapter status, + * - scsiStatus: device status, + * - _pad should be zero. + */ + +struct PVSCSIRingCmpDesc { + u64 context; + u64 dataLen; + u32 senseLen; + u16 hostStatus; + u16 scsiStatus; + u32 _pad[2]; +} __packed; + +/* + * Interrupt status / IRQ bits. + */ + +#define PVSCSI_INTR_CMPL_0 (1 << 0) +#define PVSCSI_INTR_CMPL_1 (1 << 1) +#define PVSCSI_INTR_CMPL_MASK MASK(2) + +#define PVSCSI_INTR_MSG_0 (1 << 2) +#define PVSCSI_INTR_MSG_1 (1 << 3) +#define PVSCSI_INTR_MSG_MASK (MASK(2) << 2) + +#define PVSCSI_INTR_ALL_SUPPORTED MASK(4) + +/* + * Number of MSI-X vectors supported. + */ +#define PVSCSI_MAX_INTRS 24 + +/* + * Enumeration of supported MSI-X vectors + */ +#define PVSCSI_VECTOR_COMPLETION 0 + +/* + * Misc constants for the rings. + */ + +#define PVSCSI_MAX_NUM_PAGES_REQ_RING PVSCSI_SETUP_RINGS_MAX_NUM_PAGES +#define PVSCSI_MAX_NUM_PAGES_CMP_RING PVSCSI_SETUP_RINGS_MAX_NUM_PAGES +#define PVSCSI_MAX_NUM_PAGES_MSG_RING PVSCSI_SETUP_MSG_RING_MAX_NUM_PAGES + +#define PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE \ + (PAGE_SIZE / sizeof(struct PVSCSIRingReqDesc)) + +#define PVSCSI_MAX_REQ_QUEUE_DEPTH \ + (PVSCSI_MAX_NUM_PAGES_REQ_RING * PVSCSI_MAX_NUM_REQ_ENTRIES_PER_PAGE) + +#define PVSCSI_MEM_SPACE_COMMAND_NUM_PAGES 1 +#define PVSCSI_MEM_SPACE_INTR_STATUS_NUM_PAGES 1 +#define PVSCSI_MEM_SPACE_MISC_NUM_PAGES 2 +#define PVSCSI_MEM_SPACE_KICK_IO_NUM_PAGES 2 +#define PVSCSI_MEM_SPACE_MSIX_NUM_PAGES 2 + +enum PVSCSIMemSpace { + PVSCSI_MEM_SPACE_COMMAND_PAGE = 0, + PVSCSI_MEM_SPACE_INTR_STATUS_PAGE = 1, + PVSCSI_MEM_SPACE_MISC_PAGE = 2, + PVSCSI_MEM_SPACE_KICK_IO_PAGE = 4, + PVSCSI_MEM_SPACE_MSIX_TABLE_PAGE = 6, + PVSCSI_MEM_SPACE_MSIX_PBA_PAGE = 7, +}; + +#define PVSCSI_MEM_SPACE_NUM_PAGES \ + (PVSCSI_MEM_SPACE_COMMAND_NUM_PAGES + \ + PVSCSI_MEM_SPACE_INTR_STATUS_NUM_PAGES + \ + PVSCSI_MEM_SPACE_MISC_NUM_PAGES + \ + PVSCSI_MEM_SPACE_KICK_IO_NUM_PAGES + \ + PVSCSI_MEM_SPACE_MSIX_NUM_PAGES) + +#define PVSCSI_MEM_SPACE_SIZE (PVSCSI_MEM_SPACE_NUM_PAGES * PAGE_SIZE) + +struct pvscsi_sg_list { + struct PVSCSISGElement sge[PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT]; +}; + +#define PVSCSI_ABORT_TIMEOUT 5 +struct pvscsi_adapter; + +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 pvscsi_sg_list *sgl; + struct list_head list; + dma_addr_t dataPA; + dma_addr_t sensePA; + dma_addr_t sglPA; + struct ccb_scsiio *cmd; + int dmamapping_errno; + struct PVSCSIRingReqDesc *e; + bus_dmamap_t dmap; + struct callout calloutx; + bool toed; + bool debugerr_checked; + struct PVSCSIRingCmpDesc cmpcpy; + struct pvscsi_adapter *adapter; +}; + +typedef struct pvscsitarg { + uint16_t pvt_ntos; /* number of timeouts since last reset */ + uint16_t pvt_ntrs; /* number of target resets */ +} pvscsitarg_t; + +#include + +struct pvscsi_dbgfail { + uint32_t ctrl; /* See sys/geom/isi_disk.h */ + uint32_t unit; + uint64_t lba; + struct fail_point rate; +}; + +/* Expects a pvscsinst_t *adapter has been initialized */ +#define PVSCSILCK mtx_lock(&((adapter)->pvs_camlock)) +#define PVSCSIULCK mtx_unlock(&((adapter)->pvs_camlock)) + + +struct pvscsi_adapter { + char *mmioBase; + unsigned int irq; + u8 rev; + char use_msi; + char use_msix; + char use_msg; + + struct mtx pvs_camlock; + + 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 list_head cmd_pool; + struct pvscsi_ctx *cmd_map; + unsigned long cmd_map_size; + device_t pvs_dev; + + struct resource *pvs_mmres; + int pvs_mmrid; + bus_space_handle_t pvs_mmhndl; + bus_space_tag_t pvs_mmtag; + + bool pvs_intmsix; + struct resource *pvs_intres; + int pvs_intrid; + void *pvs_intcookie; + struct cam_devq *pvs_camdevq; + struct cam_sim *pvs_camsim; + struct cam_path *pvs_campath; + bus_dma_tag_t pvs_dmat; + int pvs_int_allocd; + + uint32 pvs_max_targets; + pvscsitarg_t *pvs_tarrg; + target_id_t pvs_timeout_one_comm_targ; + uint32 pvs_reset_target_on_timeout; + uint64 pvs_intrcnt; + + struct pvscsi_dbgfail *pvscsi_dbgfails; + int pvscsi_dbgfail_cnt; +}; + +void pvscsi_complete_request(struct pvscsi_adapter *adapter, + const struct PVSCSIRingCmpDesc *e); + +#ifdef __ISILON__ +#include "isln_pvscsi.h" +#endif +#endif /* _VMW_PVSCSI_H_ */ Index: sys/dev/vmware/pvscsi/vmw_pvscsi.c =================================================================== --- /dev/null +++ sys/dev/vmware/pvscsi/vmw_pvscsi.c @@ -0,0 +1,1894 @@ +/* + * Copyright (c) 2014 EMC Corporation, Inc. All rights reserved. + */ + +/* ************************************************************************ + * Copyright 2008 VMware, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ************************************************************************/ + +/* + * pvscsi.c -- + * + * This is a driver for the VMware PVSCSI HBA adapter. + */ + +#include "vmw_pvscsi.h" + +#define PVSCSI_DEFAULT_NUM_PAGES_PER_RING 8 +#define PVSCSI_DEFAULT_NUM_PAGES_MSG_RING 1 +#define PVSCSI_DEFAULT_QUEUE_DEPTH 64 + + + + + +typedef struct pvscsi_adapter pvscsinst_t; + +#define MODNM pvscsi +enum { + PVSCSI_MSIX_VEC0 = 0, /* Only one MSI-X interrupt required */ + PVSCSI_NUM_MSIX +}; + +MALLOC_DEFINE(M_PVSCSI, "pvscsi", "VMware's para-virtualized scsi driver"); +MALLOC_DEFINE(M_PVSCSI_PCI, "pvscsi_pci", "pvscsi's ring queues"); +MALLOC_DEFINE(M_PVSCSI_SGL, "pvscsi_sgl", "pvscsi's scatter-gather list"); + + + +/* 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 bool pvscsi_use_msg = true; + +#define MAX_CMPLTNS_WO_INTRS 5 + +static volatile uint64 pvscsi_intr_count; +static volatile uint64 pvscsi_to_count; +static bool pvscsi_intr_rcvd = FALSE; + +#define pvscsi_dev(adapter) ((adapter)->pvs_dev) +#define pvscsi_simdev(sim) (pvscsi_dev((pvscsinst_t *)cam_sim_softc(sim))) + +/* + * To share code between the Linux and Isilon ports we key off the variable + * "irq" to figure out if we need to actually lock or not + */ +#define spin_lock_irqsave(l,f) if (irq) PVSCSILCK +#define spin_unlock_irqrestore(l,f) if (irq) PVSCSIULCK + +#define SCSI_SENSE_BUFFERSIZE sizeof(csio->sense_data) +#define SIMPLE_QUEUE_TAG MSG_SIMPLE_Q_TAG +#define HEAD_OF_QUEUE_TAG MSG_HEAD_OF_Q_TAG +#define ORDERED_QUEUE_TAG MSG_ORDERED_Q_TAG + +enum dma_data_direction { + DMA_BIDIRECTIONAL = 0, + DMA_TO_DEVICE = 1, + DMA_FROM_DEVICE = 2, + DMA_NONE = 3, +}; + +struct scsi_cmnd { + struct ccb_scsiio *qsc_csio; + void *sense_buffer; + unsigned short cmd_len; + unsigned char *cmn; + struct scsi_device *device; + enum dma_data_direction sc_data_direction; + unsigned char tag; + unsigned char *cmnd; + pvscsinst_t *adapter; +}; + +struct scsi_device { + unsigned int id, lun, channel; + bool tagged_supported; +}; + +static struct pvscsi_ctx * +pvscsi_find_context(struct pvscsi_adapter *adapter, struct ccb_scsiio *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_entry(adapter->cmd_pool.next, struct pvscsi_ctx, list); + ctx->cmd = cmd->qsc_csio; + ctx->toed = false; + ctx->debugerr_checked = false; + list_del(&ctx->list); + + return ctx; +} + +static void pvscsi_release_context(struct pvscsi_adapter *adapter, + struct pvscsi_ctx *ctx) +{ + ctx->cmd = NULL; + ctx->e = 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. + */ +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]; +} + +/************************************************************** + * + * VMWARE PVSCSI Hypervisor Communication Implementation + * + * This code is largely independent of any Linux internals. + * + **************************************************************/ + +static void pvscsi_reg_write(const struct pvscsi_adapter *adapter, + u32 offset, u32 val) +{ + bus_space_write_4(adapter->pvs_mmtag, adapter->pvs_mmhndl, offset, val); +} + +static u32 pvscsi_reg_read(const struct pvscsi_adapter *adapter, u32 offset) +{ + return bus_space_read_4(adapter->pvs_mmtag, adapter->pvs_mmhndl, 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 = (const u32 *)desc; + unsigned i; + + len /= sizeof(u32); + 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(struct pvscsi_adapter *adapter, + struct pvscsi_ctx *ctx, + target_id_t trg) +{ + struct PVSCSICmdDescAbortCmd cmd = { 0 }; + cmd.target = trg; + 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) +{ + LOG(0, "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) +{ + LOG(0, "Reseting bus on %p\n", adapter); + + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_BUS, NULL, 0); +} + +static void ll_device_reset(const struct pvscsi_adapter *adapter, u32 target) +{ + struct PVSCSICmdDescResetDevice cmd = { 0 }; + + LOG(0, "Reseting device: target=%u\n", target); + + device_t device = pvscsi_dev(adapter); + device_printf(device, "Resetting target %u\n", target); + cmd.target = target; + + pvscsi_write_cmd_desc(adapter, PVSCSI_CMD_RESET_DEVICE, + &cmd, sizeof cmd); + device_printf(device, "Done resetting target %u\n", target); +} + + + +/* + * Takes a list of physical segments and translates them into the VMware + * device emulation's scatter/gather format. It does not initiate the I/O. + * It reports any errors in the translation through the ctx structure. + * + * The bus_dma_segment_t pointed to by dm_segs is allocated on the stack. + */ +static void +pvscsi_queue_io(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error) +{ + struct pvscsi_ctx *ctx = (struct pvscsi_ctx *)arg; + + if (error || ctx->dmamapping_errno) { + ctx->dmamapping_errno = error; + return; + } + + if (nseg > PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT) { + ctx->dmamapping_errno = EFBIG; + return; + } + + unsigned i; + struct PVSCSISGElement *sge = &ctx->sgl->sge[0]; + + struct PVSCSIRingReqDesc *e = ctx->e; + e->flags |= PVSCSI_FLAG_CMD_WITH_SG_LIST; + + for (i = 0; i < nseg; i++) { + sge[i].addr = dm_segs[i].ds_addr; + sge[i].length = dm_segs[i].ds_len; + sge[i].flags = 0; + } + + ctx->dmamapping_errno = 0; + e->flags |= PVSCSI_FLAG_CMD_WITH_SG_LIST; + ctx->sglPA = pci_map_single(adapter->dev, ctx->sgl, + PAGE_SIZE, PCI_DMA_TODEVICE); + e->dataAddr = ctx->sglPA; +} +#define scsi_bufflen(cmd) (cmd)->qsc_csio->dxfer_len +#define pvscsi_create_sg(a,b,c) +#define scsi_sg_count(a) 2 +#define scsi_dma_map(a) 2 + +static inline dma_addr_t +sg_dma_address_fn(void) +{ + panic("This code-path shouldn't have been taken"); + return 0; +} +#define sg_dma_address(sg) sg_dma_address_fn() +#define IRQ_RETVAL(a) 0 + +#define SAM_STAT_GOOD SCSI_STATUS_OK +#define SAM_STAT_CHECK_CONDITION SCSI_STATUS_CHECK_COND +#define SAM_STAT_COMMAND_TERMINATED SCSI_STATUS_CMD_TERMINATED +/* + * 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); + + e->dataLen = bufflen; + e->dataAddr = 0; + if (bufflen == 0) + return; + + struct ccb_scsiio *csio = cmd->qsc_csio; + ctx->e = e; + ctx->dmamapping_errno = 0; + + int error; + + error = bus_dmamap_load_ccb(adapter->pvs_dmat, ctx->dmap, + (union ccb *)csio, pvscsi_queue_io, ctx, BUS_DMA_NOWAIT); + if (error) + ctx->dmamapping_errno = error; + + if (ctx->dmamapping_errno) { + if (ctx->dmamapping_errno == EFBIG) + csio->ccb_h.flags = CAM_REQ_TOO_BIG; + else + csio->ccb_h.flags = CAM_REQ_CMP_ERR; + } + + /* + * Setup 'count' and 'segs' so that we choose the path that sets + * PVSCSI_FLAG_CMD_WITH_SG_LIST and uses a scatter/gather list + */ + count = scsi_sg_count(cmd); + if (count != 0) { + int segs = 2; /* Force the more generic path below */ + 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, + PAGE_SIZE, PCI_DMA_TODEVICE); + e->dataAddr = ctx->sglPA; + } else + e->dataAddr = sg_dma_address(sg); + } else { + e->dataAddr = ctx->dataPA; + } +} + +static void pvscsi_unmap_buffers(const struct pvscsi_adapter *adapter, + struct pvscsi_ctx *ctx) +{ + struct ccb_scsiio *csio = ctx->cmd; + + if (csio->dxfer_len) + bus_dmamap_unload(adapter->pvs_dmat, ctx->dmap); +} + +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(adapter->ringStatePA & PAGE_MASK); + BUG_ON(adapter->reqRingPA & PAGE_MASK); + BUG_ON(adapter->cmpRingPA & PAGE_MASK); + + 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(adapter->msgRingPA & PAGE_MASK); + + 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. + */ +void pvscsi_complete_request(struct pvscsi_adapter *adapter, + const struct PVSCSIRingCmpDesc *e) +{ + struct pvscsi_ctx *ctx; + bool toed = false; + struct ccb_scsiio *cmd; + device_t device = pvscsi_dev(adapter); + int debugerr = 0; + u32 btstat = e->hostStatus; + u32 sdstat = e->scsiStatus; + u64 edataLen = e->dataLen; + + mtx_assert(&adapter->pvs_camlock, MA_OWNED); + ctx = pvscsi_get_context(adapter, e->context); + cmd = ctx->cmd; + +#ifdef __ISILON__ + /* + * check debugerr failpoints now so that we can do nothing if we're + * delaying the completion with a timer. Only check them once per + * command. + */ + if (!ctx->debugerr_checked) { + ctx->debugerr_checked = true; /* For when this very routine is + * invoked from the FP callout */ + debugerr = pvscsi_debugerr_check(adapter, ctx); + if (debugerr == PVSCSI_DEBUGERR_QUEUED) { + /* + * Copy off the completion descriptor because it will + * be recycled once this routine exits. The second time + * through this routine, this copy will be used as "e" + */ + ctx->cmpcpy = *e; + return; + } + } +#endif + + callout_stop(&ctx->calloutx); /* disables ABORT or SCSI IO callout */ + toed = ctx->toed; + if (toed) { + device_printf(device, "ccb:%p marked for timeout returned with," + "ctx:%p, h:%u s:%u\n", cmd, ctx, btstat, sdstat); + } + e = NULL; + pvscsi_unmap_buffers(adapter, ctx); + pvscsi_release_context(adapter, ctx); + /* After this point there should be no more references to e */ + + + if (sdstat != SAM_STAT_GOOD && + (btstat == BTSTAT_SUCCESS || + btstat == BTSTAT_LINKED_COMMAND_COMPLETED || + btstat == BTSTAT_LINKED_COMMAND_COMPLETED_WITH_FLAG)) { + cmd->scsi_status = sdstat; + if (sdstat == SAM_STAT_COMMAND_TERMINATED) + cmd->ccb_h.status = CAM_SCSI_BUS_RESET; + else if (sdstat == SAM_STAT_CHECK_CONDITION) + cmd->ccb_h.status = CAM_SCSI_STATUS_ERROR | + CAM_AUTOSNS_VALID; + else + cmd->ccb_h.status = CAM_SCSI_STATUS_ERROR; + } 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->scsi_status = sdstat; + cmd->ccb_h.status = CAM_REQ_CMP; + break; + + case BTSTAT_DATARUN: + case BTSTAT_DATA_UNDERRUN: + cmd->scsi_status = sdstat; + cmd->ccb_h.status = CAM_DATA_RUN_ERR; + cmd->resid = cmd->dxfer_len - edataLen; + break; + + case BTSTAT_SELTIMEO: + /* Our emulation returns this for non-connected devs */ + cmd->scsi_status = sdstat; + cmd->ccb_h.status = CAM_SEL_TIMEOUT; + break; + + case BTSTAT_LUNMISMATCH: + case BTSTAT_TAGREJECT: + case BTSTAT_BADMSG: + cmd->scsi_status = sdstat; + cmd->ccb_h.status = CAM_LUN_INVALID; + break; + /* 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->scsi_status = sdstat; + cmd->ccb_h.status = CAM_REQ_CMP_ERR; + break; + + case BTSTAT_SENTRST: + case BTSTAT_RECVRST: + case BTSTAT_BUSRESET: + cmd->scsi_status = sdstat; + cmd->ccb_h.status = CAM_SCSI_BUS_RESET; + break; + + case BTSTAT_ABORTQUEUE: + cmd->scsi_status = sdstat; + device_printf(device, "Command %s\n", toed ? + "timedout" : "aborted"); + if(toed) { + cmd->ccb_h.status = CAM_CMD_TIMEOUT; + } else { + cmd->ccb_h.status = CAM_REQ_ABORTED; + } + break; + + case BTSTAT_SCSIPARITY: + cmd->scsi_status = sdstat; + cmd->ccb_h.status = CAM_UNCOR_PARITY; + break; + + default: + cmd->scsi_status = sdstat; + cmd->ccb_h.status = CAM_REQ_CMP_ERR; + device_printf(device, "Unknown completion status: " + "0x%x\n", btstat); + } + + if (debugerr != 0) { + /* inject an error */ + union ccb *ccb = (union ccb *)cmd; + ccb->ccb_h.status = CAM_UNCOR_PARITY; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + } + xpt_done((union ccb *)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++; + } +} + +#ifdef __ISILON__ +static inline void +PRINT_CTX(struct pvscsi_ctx *ctx) +{ + printf("pvscsi:ctx %p [0]>%lx [l]>%x %lx %lx %lx %d %p %p\n", + ctx->sgl, + ctx->sgl->sge[0].addr, + ctx->sgl->sge[0].length, + ctx->dataPA, + ctx->sensePA, + ctx->sglPA, + ctx->dmamapping_errno, + ctx->e, + ctx->dmap); +} + + +static inline void +PRINT_REQ(struct PVSCSIRingReqDesc *e) +{ + printf("pvscsi:req cid>%lx dat>%lx dlen>%lx sns>%lx slen>%x fl>%x " + "c0>%u cl>%u lu>%u tg>%u b>%u trg>%u cpu>%u\n", + e->context, + e->dataAddr, + e->dataLen, + e->senseAddr, + e->senseLen, + e->flags, + e->cdb[0], + e->cdbLen, + e->lun[0], + e->tag, + e->bus, + e->target, + e->vcpuHint); +} +#endif + +/* + * 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; + struct ccb_scsiio *csio = cmd->qsc_csio; + + 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) { + device_printf(pvscsi_dev(adapter), "Error, ring full: reqProdIdx=%d cmpConsIdx=%d\n", + s->reqProdIdx, s->cmpConsIdx); + return CAM_RESRC_UNAVAIL; + } + + e = adapter->req_ring + (s->reqProdIdx & MASK(req_entries)); + + e->bus = sdev->channel; + e->target = sdev->id; + memset(e->lun, 0, sizeof e->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 inline void +PRINT_RINGSTATE(pvscsinst_t *adapter) +{ + struct PVSCSIRingsState *s __unused; + + s = adapter->rings_state; + LOG(0, + "req> %d %d %d cmp> %d %d %d msg> %d %d %d\n", + s->reqProdIdx, + s->reqConsIdx, + s->reqNumEntriesLog2, + s->cmpProdIdx, + s->cmpConsIdx, + s->cmpNumEntriesLog2, + s->msgProdIdx, + s->msgConsIdx, + s->msgNumEntriesLog2); +} + + +static void +pvscsi_abort_timeout(void *data) +{ + struct pvscsi_ctx *ctx; + pvscsinst_t *adapter; + target_id_t tid; + struct ccb_scsiio *cmd; + pvscsitarg_t *targ; + + ctx = (struct pvscsi_ctx *)data; + adapter = ctx->adapter; + cmd = ctx->cmd; + + if (!cmd) { + device_printf(pvscsi_dev(adapter), + "abort TIMEOUT ctx>%p with NULL cmd\n", ctx); + return; + } + + tid = cmd->ccb_h.target_id; + + targ = adapter->pvs_tarrg + tid; + targ->pvt_ntrs++; + + pvscsi_process_request_ring(adapter); + ll_device_reset(adapter, tid); + pvscsi_process_completion_ring(adapter); +} + + +static void +pvscsi_scsiio_timeout(void *data) +{ + struct pvscsi_ctx *ctx; + pvscsinst_t *adapter; + pvscsitarg_t *targ; + struct timeval tv; + struct ccb_scsiio *cmd; + + ctx = (struct pvscsi_ctx *)data; + adapter = ctx->adapter; + cmd = ctx->cmd; + + mtx_assert(&adapter->pvs_camlock, MA_OWNED); + + if (!cmd) { + device_printf(pvscsi_dev(adapter), + "SCSI IO TIMEOUT ctx>%p with NULL cmd\n", ctx); + return; + } + + pvscsi_process_completion_ring(adapter); + if (ctx->cmd != cmd) { + uint64 to_count; + + to_count = atomic_fetchadd_long(&pvscsi_to_count, 1); + + device_printf(pvscsi_dev(adapter), "I/O timeout fired, but I/O " + "marked completed in ctx %p.\n" + "\tGlobal pvscsi_intr_count %lu, instance count %lu,\n" + "\tpvscsi_intr_rcvd %s, I/O completion w/o intr %lu\n" + "\tintr_status %u\n", + ctx, pvscsi_intr_count, adapter->pvs_intrcnt, + pvscsi_intr_rcvd == TRUE ? "TRUE" : "FALSE", to_count + 1, + pvscsi_read_intr_status(adapter)); + + if ((to_count > MAX_CMPLTNS_WO_INTRS) && + (pvscsi_intr_rcvd != TRUE)) { + printf("PVSCSI: Node-wide Interrupt delivery failure.\n" + "\tRebooting\n"); + kern_reboot(RB_NOSYNC); + } + + return; + } + + getmicrotime(&tv); + device_printf(pvscsi_dev(adapter), "SCSI IO TIMEOUT ctx>%p ccb>%p at " + "%ld.%06ld\n", ctx, ctx->cmd, tv.tv_sec, tv.tv_usec); + + /* Update the targ_t from the targ array with the TO info */ + targ = adapter->pvs_tarrg + ctx->cmd->ccb_h.target_id; + targ->pvt_ntos++; + ctx->toed = true; + + /* Kick off a task abort and process completion once more */ + pvscsi_abort_cmd(adapter, ctx, cmd->ccb_h.target_id); + pvscsi_process_completion_ring(adapter); + + /* If the cmd has not been aborted start a timer for the abort */ + if (ctx->cmd == cmd) { + callout_reset(&ctx->calloutx, PVSCSI_ABORT_TIMEOUT * hz, + pvscsi_abort_timeout, ctx); + } +} + +static int pvscsi_queue_locked(struct scsi_cmnd *cmd, + void (*done)(struct scsi_cmnd *)) +{ + struct pvscsi_adapter *adapter = cmd->adapter; + int timeout = (cmd->qsc_csio->ccb_h.timeout * hz)/1000; + struct pvscsi_ctx *ctx; + + ctx = pvscsi_acquire_context(adapter, cmd); + if (!ctx || pvscsi_queue_ring(adapter, ctx, cmd) != 0) { + if (ctx) + pvscsi_release_context(adapter, ctx); + return SCSI_MLQUEUE_HOST_BUSY; + } + + ctx->debugerr_checked = false; + ctx->toed = false; + if (adapter->pvs_timeout_one_comm_targ == + cmd->qsc_csio->ccb_h.target_id) { + timeout = 1; + adapter->pvs_timeout_one_comm_targ = -1; + } + callout_reset(&ctx->calloutx, timeout, + adapter->pvs_reset_target_on_timeout ? + pvscsi_abort_timeout: pvscsi_scsiio_timeout, + ctx); + cmd->qsc_csio->ccb_h.status |= CAM_SIM_QUEUED; + + + pvscsi_kick_io(adapter, cmd->cmnd[0]); + + return 0; +} + +static int pvscsi_abort(pvscsinst_t *adapter, struct ccb_scsiio *cmd) +{ + struct pvscsi_ctx *ctx; + int irq = 0; + + 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) { + device_t device = pvscsi_dev(adapter); + device_printf(device, "Failed to abort cmd %p\n", cmd); + goto out; + } + + pvscsi_abort_cmd(adapter, ctx, cmd->ccb_h.target_id); + + pvscsi_process_completion_ring(adapter); + +out: + spin_unlock_irqrestore(&adapter->hw_lock, flags); + return SUCCESS; +} + + +static int pvscsi_bus_reset(pvscsinst_t *adapter) +{ + int irq = 0; /* So that we do not lock */ + + /* + * 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(pvscsinst_t *adapter, target_id_t trg) +{ + int irq = 0; + + /* + * 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, trg); + pvscsi_process_completion_ring(adapter); + + spin_unlock_irqrestore(&adapter->hw_lock, flags); + + return SUCCESS; +} + +static void +pvscsi_device_lost_or_found(pvscsinst_t *adapter, u32 bus, u32 trg, u8 lun, + bool lost) +{ + struct cam_path *path; + struct ccb_getdev ccb = { }; + cam_status err; +#ifdef __ISILON__ + diskevt_event_t diskevent; + int eventnum = -1; +#endif + + if (lun) { + device_printf(pvscsi_dev(adapter), "hotplug/removal event for " + "a non-zero LUN '%d:%d'. Ignoring event\n", trg, lun); + return; + } + + PVSCSILCK; + err = xpt_create_path(&path, NULL, cam_sim_path(adapter->pvs_camsim), + trg, lun); + if (err != CAM_REQ_CMP) { + device_printf(pvscsi_dev(adapter), "hotplug/removal event " + "failed to allocate path. '%d:%d' Ignoring event\n", + trg, lun); + PVSCSIULCK; + return; + } + + xpt_setup_ccb(&(ccb.ccb_h), path, /*priority*/5); + if (lost) { + device_printf(pvscsi_dev(adapter), "removing targ:lun %d:%d\n", + trg, lun); + xpt_async(AC_LOST_DEVICE, path, NULL); + } else { + device_printf(pvscsi_dev(adapter), "adding targ:lun %d:%d\n", + trg, lun); + union ccb *scan_ccb; + + scan_ccb = xpt_alloc_ccb_nowait(); + if (scan_ccb == NULL) { + device_printf(pvscsi_dev(adapter), + "unable to alloc CCB for rescan\n"); + return; + } + scan_ccb->ccb_h.func_code = XPT_SCAN_TGT; + if (xpt_create_path(&scan_ccb->ccb_h.path, NULL, + cam_sim_path(adapter->pvs_camsim), trg, lun) != CAM_REQ_CMP) { + device_printf(pvscsi_dev(adapter), + "unable to create path for rescan\n"); + return; + } + xpt_rescan(scan_ccb); + } + + xpt_free_path(path); + +#ifdef __ISILON__ + eventnum = ISI_UNIT(IDSK_TYPE_DA, + CAM_BTL_2ISIUNIT(cam_sim_path(adapter->pvs_camsim), trg, lun)); +#endif + PVSCSIULCK; + +#ifdef __ISILON__ + if (eventnum >= 0) { + bzero(&diskevent, sizeof(diskevent)); + diskevent.unitnum = eventnum; + diskevent.type = lost ? DISKEVT_DISK_ABSENT : + DISKEVT_DISK_PRESENT; + diskevt_cdev_notify_event(&diskevent); + } else { + device_printf(pvscsi_dev(adapter), + "Couldn't post drive %s DISKEVT. e:t:l %d:%d:%d\n", + lost ? "remove" : "add", eventnum, trg, lun); + } +#endif +} + +static void pvscsi_process_msg(struct pvscsi_adapter *adapter, + struct PVSCSIRingMsgDesc *e) +{ + ASSERT_ON_COMPILE(PVSCSI_MSG_LAST == 2); + + if (e->type == PVSCSI_MSG_DEV_ADDED) { + struct PVSCSIMsgDescDevStatusChanged *desc; + desc = (struct PVSCSIMsgDescDevStatusChanged *)e; + + pvscsi_device_lost_or_found(adapter, desc->bus, desc->target, + desc->lun[1], false); + } else if (e->type == PVSCSI_MSG_DEV_REMOVED) { + struct PVSCSIMsgDescDevStatusChanged *desc; + desc = (struct PVSCSIMsgDescDevStatusChanged *)e; + + pvscsi_device_lost_or_found(adapter, desc->bus, desc->target, + desc->lun[1], true); + } +} + +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(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 = COMPAT_WORK_GET_DATA(data, struct pvscsi_adapter, work); + + pvscsi_process_msg_ring(adapter); +} + +static int pvscsi_setup_msg_workqueue(struct pvscsi_adapter *adapter) +{ + char name[32]; + + if (!pvscsi_use_msg) + return 0; + + pvscsi_reg_write(adapter, PVSCSI_REG_OFFSET_COMMAND, + PVSCSI_CMD_SETUP_MSG_RING); + + if (pvscsi_reg_read(adapter, PVSCSI_REG_OFFSET_COMMAND_STATUS) == -1) + return 0; + + snprintf(name, sizeof name, "pvscsi_wq_%u", + device_get_unit(adapter->pvs_dev)); + + adapter->workqueue = create_singlethread_workqueue(name); + if (!adapter->workqueue) { + printk(KERN_ERR "pvscsi: failed to create work queue\n"); + return 0; + } + COMPAT_INIT_WORK(&adapter->work, pvscsi_msg_workqueue_handler, adapter); + 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 { + /* + * N.B INTx interrupts will not work with xpt_polled_action() + * The symptom will be a swatchdog involving dashutdown() + */ + u32 val = pvscsi_read_intr_status(adapter); + handled = (val & PVSCSI_INTR_ALL_SUPPORTED) != 0; + if (handled) + pvscsi_write_intr_status(devp, val); + } + + if (handled) { + + /* Bump up SIM driver interrupt count across all instances */ + if (!atomic_fetchadd_long(&pvscsi_intr_count, 1)) { + /* On first interrupt received set bool to TRUE */ + pvscsi_intr_rcvd = TRUE; + } + + spin_lock_irqsave(&adapter->hw_lock, flags); + + adapter->pvs_intrcnt++; /* SIM instance count bumped */ + 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_isr_freebsd(void *adapter) +{ + (void)pvscsi_isr(1, adapter); +} + +static void +pvscsi_poll(struct cam_sim *sim) +{ + /* N.B. This mechanism will not work with INTx interrupts */ + pvscsinst_t *adapter = (pvscsinst_t *)cam_sim_softc(sim); + (void)pvscsi_isr(0, adapter); +} + +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_page((unsigned long)ctx->sgl); +} + +static bool +pvscsi_setup_intr(pvscsinst_t *adapter, void *isr, bool msix) +{ + int rid = 0; + struct resource *res; + int err; + device_t device = adapter->pvs_dev; + + if (msix) { + int msix_vecs_needed = PVSCSI_NUM_MSIX; + + rid++; /* RID 1 in the interrupt space is for MSIX interrupts */ + + if (pci_msix_count(adapter->pvs_dev) < PVSCSI_NUM_MSIX) { + device_printf(device, "pci_msix_count():%d < " + "PVSCSI_NUM_MSIX\n", + pci_msix_count(adapter->pvs_dev)); + return false; + } + + err = pci_alloc_msix(adapter->pvs_dev, &msix_vecs_needed); + if (err || (msix_vecs_needed < PVSCSI_NUM_MSIX)) { + device_printf(device, "retval>%d, " + "msix_vecs_needed>%d\n", + err, msix_vecs_needed); + return false; + } + } + + res = bus_alloc_resource_any(adapter->pvs_dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (!res) { + device_printf(device, "Couldn't allocate interrupt resource\n"); + if (msix) pci_release_msi(adapter->pvs_dev); + return false; + } + + adapter->pvs_int_allocd = 1; + err = bus_setup_intr(adapter->pvs_dev, res, INTR_MPSAFE | INTR_TYPE_CAM, + NULL, isr, adapter, &adapter->pvs_intcookie); + if (err) { + device_printf(device, "bus_setup_intr()>%d\n", err); + bus_release_resource(adapter->pvs_dev, SYS_RES_IRQ, rid, res); + if (msix) pci_release_msi(adapter->pvs_dev); + return false; + } + + adapter->pvs_int_allocd = 2; + adapter->pvs_intmsix = msix; + adapter->pvs_intres = res; + adapter->pvs_intrid = rid; + + return true; +} + +static void pvscsi_shutdown_intr(struct pvscsi_adapter *adapter) +{ + /* Undo the interrupt isr registration */ + if (adapter->pvs_int_allocd > 1) + bus_teardown_intr(adapter->pvs_dev, adapter->pvs_intres, + adapter->pvs_intcookie); + if (adapter->pvs_int_allocd > 0) + bus_release_resource(adapter->pvs_dev, SYS_RES_IRQ, + adapter->pvs_intrid, adapter->pvs_intres); + if (adapter->pvs_intmsix) pci_release_msi(adapter->pvs_dev); +} + +static void pvscsi_release_resources(struct pvscsi_adapter *adapter) +{ + pvscsi_shutdown_intr(adapter); + + if (adapter->workqueue) + destroy_workqueue(adapter->workqueue); + + + if (adapter->cmd_map) { + KASSERT(adapter->cmd_map_size, ("adapter->cmd_map_size is 0")); + pvscsi_free_sgls(adapter); + kfree(adapter->cmd_map, adapter->cmd_map_size); + } + + 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); + if (adapter->cmd_map) { + } + + /* Undo the memory-mapped register mapping */ + bus_release_resource(adapter->pvs_dev, SYS_RES_MEMORY, + adapter->pvs_mmrid, adapter->pvs_mmres); +} + +/* + * Allocate scatter gather lists. + * + * These are statically allocated. Trying to be clever was not worth it. + * + * Dynamic allocation can fail, and we can't go deeep into the memory + * allocator, since we're a SCSI driver, and trying too hard to allocate + * memory might generate disk I/O. We also don't want to fail disk I/O + * in that case because we can't get an allocation - the I/O could be + * trying to swap out data to free memory. Since that is pathological, + * just use a statically allocated scatter list. + * + */ +static int pvscsi_allocate_sg(struct pvscsi_adapter *adapter) +{ + struct pvscsi_ctx *ctx; + int i; + + ctx = adapter->cmd_map; + ASSERT_ON_COMPILE(sizeof(struct pvscsi_sg_list) <= PAGE_SIZE); + + for (i = 0; i < adapter->req_depth; ++i, ++ctx) { + ctx->sgl = (void *)__get_free_page(GFP_KERNEL); + ctx->sglPA = 0; + BUG_ON(ctx->sglPA & PAGE_MASK); + if (!ctx->sgl) { + for (; i >= 0; --i, --ctx) { + free_page((unsigned long)ctx->sgl); + 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 uint32 pvscsi_get_max_targets(struct pvscsi_adapter *adapter) +{ + PVSCSICmdDescConfigCmd cmd; + PVSCSIConfigPageHeader *header; + dma_addr_t configPagePA; + void *config_page; + uint32 numPhys; + + ASSERT_ON_COMPILE(sizeof(PVSCSIConfigPageController) <= PAGE_SIZE); + + numPhys = 16; + config_page = pci_alloc_consistent(adapter->dev, PAGE_SIZE, + &configPagePA); + if (!config_page) { + printk(KERN_INFO "pvscsi: failed to allocate memory for" + " config page\n"); + goto exit; + } + + BUG_ON(configPagePA & PAGE_MASK); + + /* Fetch config info from the device. */ + cmd.configPageAddress = QWORD(PVSCSI_CONFIG_CONTROLLER_ADDRESS, 0); + 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) { + PVSCSIConfigPageController *config; + + config = config_page; + numPhys = config->numPhys; + } else + printk(KERN_INFO "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 void +pvscsi_action(struct cam_sim *psim, union ccb *pccb) +{ + pvscsinst_t * adapter = cam_sim_softc(psim); + + device_t device = pvscsi_dev(adapter); + + switch (pccb->ccb_h.func_code) { + default: { + /* + device_printf(device, "%s(%d) invoked for '%u'\n", + __FUNCTION__, pccb->ccb_h.func_code, + pccb->ccb_h.target_id); + */ + pccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(pccb); + break; + } + + case XPT_SCSI_IO: { + struct ccb_scsiio *csio = &pccb->csio; + + LOG(0, "Command(0x%x) for %d:%d\n", + csio->cdb_io.cdb_bytes[0], + pccb->ccb_h.target_id, + pccb->ccb_h.target_lun); + + struct scsi_cmnd command = { 0 }, *cmd = &command; + struct scsi_device quasi_sdev = { 0 }; + + cmd->qsc_csio = csio; + cmd->device = &quasi_sdev; + + if (csio->ccb_h.target_lun) { + pccb->ccb_h.status = CAM_LUN_INVALID; + xpt_done(pccb); + break; + } + + cmd->cmd_len = csio->cdb_len; + if (csio->ccb_h.flags & CAM_CDB_POINTER) + cmd->cmnd = (void *)csio->cdb_io.cdb_ptr; + else + cmd->cmnd = (void *)&csio->cdb_io.cdb_bytes; + + KASSERT(!(csio->ccb_h.flags & + (CAM_SENSE_PHYS|CAM_SENSE_PTR)), ("%x", + csio->ccb_h.flags)); /* We expect a struct */ + cmd->sense_buffer = &csio->sense_data; + + #define CSIODIR (csio->ccb_h.flags & CAM_DIR_MASK) + if (CSIODIR == CAM_DIR_IN) + cmd->sc_data_direction = DMA_FROM_DEVICE; + else if (CSIODIR == CAM_DIR_OUT) + cmd->sc_data_direction = DMA_TO_DEVICE; + else if (CSIODIR == CAM_DIR_NONE) + cmd->sc_data_direction = DMA_NONE; + else + cmd->sc_data_direction = 0; + + quasi_sdev.channel = 0; + quasi_sdev.id = csio->ccb_h.target_id; + quasi_sdev.lun = csio->ccb_h.target_lun; + if (csio->ccb_h.flags & CAM_TAG_ACTION_VALID) { + quasi_sdev.tagged_supported = true; + cmd->tag = csio->tag_action; + } + cmd->adapter = adapter; + + pvscsi_queue_locked(cmd, NULL); + break; + } + + case XPT_PATH_INQ: { + struct ccb_pathinq *cpi = &pccb->cpi; + + cpi->version_num = 1; + cpi->hba_inquiry = PI_TAG_ABLE; + cpi->target_sprt = 0; + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = adapter->pvs_max_targets - 1; + cpi->max_lun = 0; /* 7 or 0 */ + cpi->initiator_id = 7; + cpi->bus_id = cam_sim_bus(psim); + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "VMware", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(psim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(psim); + cpi->transport = XPORT_SPI; + cpi->transport_version = 2; + cpi->protocol = PROTO_SCSI; + cpi->protocol_version = SCSI_REV_SPC2; + + pccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(pccb); + break; + } + + case XPT_RESET_BUS: { + device_printf(device, "Bus reset initiated\n"); + pvscsi_bus_reset(adapter); + pccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(pccb); + device_printf(device, "Bus reset completed\n"); + break; + } + + case XPT_RESET_DEV: { + target_id_t trg = pccb->ccb_h.target_id; + + if (pccb->ccb_h.target_lun) { + device_printf(device, "Non-zero LU number %lu\n", + pccb->ccb_h.target_lun); + pccb->ccb_h.status = CAM_LUN_INVALID; + xpt_done(pccb); + break; + } + device_printf(device, "target %d reset initiated\n", + trg); + pvscsi_device_reset(adapter, trg); + device_printf(device, "target %d reset completed\n", + trg); + pccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(pccb); + break; + } + + case XPT_ABORT: { + pvscsi_abort(adapter, + (struct ccb_scsiio *)(pccb->cab.abort_ccb)); + pccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(pccb); + break; + } + + case XPT_GET_TRAN_SETTINGS: { + struct ccb_trans_settings *cts = &pccb->cts; + + cts->transport = XPORT_SPI; + cts->transport_version = 2; + cts->protocol = PROTO_SCSI; + cts->protocol_version = SCSI_REV_SPC2; + + pccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(pccb); + break; + } + } + return; +} + + +static int +pvscsi_pci_detach(device_t device) +{ + pvscsinst_t *adapter = device_get_softc(device); + + PVSCSILCK; + xpt_async(AC_LOST_DEVICE, adapter->pvs_campath, NULL); + xpt_free_path(adapter->pvs_campath); + xpt_bus_deregister(cam_sim_path(adapter->pvs_camsim)); + + cam_sim_free(adapter->pvs_camsim, true); + PVSCSIULCK; + mtx_destroy(&adapter->pvs_camlock); + free(adapter->pvs_tarrg, M_PVSCSI); + adapter->pvs_tarrg = NULL; + + pvscsi_mask_intr(adapter); + + pci_disable_io(device, SYS_RES_MEMORY); + pci_disable_busmaster(device); + + pvscsi_release_resources(adapter); + + return 0; +} + +static int +pvscsi_pci_attach(device_t device) +{ + int retval; + struct resource *res = NULL; + pvscsinst_t *adapter = device_get_softc(device); + int rid = -1, i, error; + + memset(adapter, 0, sizeof(*adapter)); + adapter->pvs_timeout_one_comm_targ = -1; + adapter->pvs_reset_target_on_timeout = 0; + adapter->pvs_int_allocd = 0; + + retval = pci_enable_busmaster(device); + if(retval) { + device_printf(device, "Could not enable bus-mastering, %d", + retval); + return retval; + } + + retval = pci_enable_io(device, SYS_RES_MEMORY); + if(retval) { + device_printf(device, "Could not enable memory range, %d", + retval); + pci_disable_busmaster(device); + return retval; + } + + for (i = 0; i < PCIR_MAX_BAR_0; i++) { + rid = PCIR_BAR(i); + + res = bus_alloc_resource_any(device, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (res) + break; + } + + if (!res) { + device_printf(device, "Could not find/activate memory range\n"); + goto out_disable_device; + } + + LOG(0, "Acquired device registers at rid %d\n", rid); + + adapter->pvs_mmres = res; + adapter->pvs_mmrid = rid; + adapter->pvs_mmtag = rman_get_bustag(adapter->pvs_mmres); + adapter->pvs_mmhndl = rman_get_bushandle(adapter->pvs_mmres); + adapter->pvs_dev = device; + + 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. + */ + adapter->pvs_max_targets = pvscsi_get_max_targets(adapter); + device_printf(device, "Maximum number of targets is %u\n", + adapter->pvs_max_targets); + + /* + * 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) { + device_printf(device, "failed to allocate memory.\n"); + error = -ENOMEM; + goto out_reset_adapter; + } + adapter->cmd_map_size = adapter->req_depth * sizeof(struct pvscsi_ctx); + + if (bus_dma_tag_create(NULL, 1, 0, BUS_SPACE_MAXADDR, + BUS_SPACE_MAXADDR, NULL, NULL, BUS_SPACE_MAXADDR, + PVSCSI_MAX_NUM_SG_ENTRIES_PER_SEGMENT, BUS_SPACE_MAXADDR, 0, + busdma_lock_mutex, &adapter->pvs_camlock, + &adapter->pvs_dmat) != 0) { + device_printf(device, "DMA tag\n"); + goto out_reset_adapter; + } + + adapter->pvs_tarrg = malloc((sizeof(pvscsitarg_t)) * + adapter->pvs_max_targets, M_PVSCSI,M_WAITOK|M_ZERO); + if (!adapter->pvs_tarrg) { + goto out_reset_adapter; + } +#ifdef __ISILON__ + adapter->pvscsi_dbgfail_cnt = 0; + adapter->pvscsi_dbgfails = malloc(sizeof(struct pvscsi_dbgfail) * + IDISKFP_DBGFAILCNT, M_PVSCSI,M_WAITOK|M_ZERO); + if (!adapter->pvscsi_dbgfails) { + goto out_free_pvs_tarrg; + } +#endif + mtx_init(&adapter->pvs_camlock, "pvscsi camlock", NULL, MTX_DEF); + + INIT_LIST_HEAD(&adapter->cmd_pool); + for (i = 0; i < adapter->req_depth; i++) { + struct pvscsi_ctx *ctx = adapter->cmd_map + i; + if (bus_dmamap_create(adapter->pvs_dmat, 0, &ctx->dmap) != 0) { + device_printf(device, "dmap alloc failed, %d\n", i); + goto out_delete_dmat; + } + ctx->adapter = adapter; + callout_init_mtx(&ctx->calloutx, &adapter->pvs_camlock, 0); + list_add(&ctx->list, &adapter->cmd_pool); + } + + error = pvscsi_allocate_sg(adapter); + if (error) { + device_printf(device, "Unable to allocate s/g table\n"); + goto out_delete_dmat; + } + + if (pvscsi_setup_intr(adapter, pvscsi_isr_freebsd, true)) { + device_printf(device, "Using MSI-X interrupts\n"); + adapter->use_msix = 1; + } else if (pvscsi_setup_intr(adapter, pvscsi_isr_freebsd, false)) { + device_printf(device, "Using INTx interrupts\n"); + adapter->use_msix = adapter->use_msi = 0; + } else { + goto out_delete_dmat; + } + + SYSCTL_ADD_UINT(device_get_sysctl_ctx(device), + SYSCTL_CHILDREN(device_get_sysctl_tree(device)), OID_AUTO, + "drop_next_command_to_target", CTLFLAG_RW, + &adapter->pvs_timeout_one_comm_targ, 0U, + "Drop the next I/O to this target(for test purposes)"); + + SYSCTL_ADD_UINT(device_get_sysctl_ctx(device), + SYSCTL_CHILDREN(device_get_sysctl_tree(device)), OID_AUTO, + "reset_target_on_command_timeout", CTLFLAG_RW, + &adapter->pvs_reset_target_on_timeout, 0U, + "Reset the target on I/O timing out(for test purposes)"); + +#ifdef __ISILON__ + pvscsi_debugerr_add_sysctls(adapter); +#endif + + /* Register with CAM as a SIM */ + adapter->pvs_camdevq = cam_simq_alloc(adapter->req_depth); + if (!adapter->pvs_camdevq) { + device_printf(device, "cam_simq_alloc(%d) failed\n", + adapter->req_depth); + goto out_delete_dmat; + } + adapter->pvs_camsim = cam_sim_alloc(pvscsi_action, pvscsi_poll, + "pvscsi", adapter, + device_get_unit(adapter->pvs_dev), + &adapter->pvs_camlock, adapter->req_depth, + adapter->req_depth, adapter->pvs_camdevq); + + if (!adapter->pvs_camsim) { + device_printf(device, "cam_sim_alloc() failed\n"); + goto out_cam_simq; + } + + PVSCSILCK; + if (xpt_bus_register(adapter->pvs_camsim, NULL, 0) != CAM_SUCCESS) { + PVSCSIULCK; + device_printf(device, "xpt_bus_register() failed\n"); + goto out_cam_sim; + } + + if (xpt_create_path(&adapter->pvs_campath, NULL, + cam_sim_path(adapter->pvs_camsim), + CAM_TARGET_WILDCARD, + CAM_LUN_WILDCARD) != CAM_REQ_CMP) { + PVSCSIULCK; + device_printf(device, "xpt_create_path() failed\n"); + goto out_cam_busreg; + } + PVSCSIULCK; + + PRINT_RINGSTATE(adapter); + device_printf(device, "softc:%p, unmasking interrupts w/intr-status " + "%d\n", adapter, pvscsi_read_intr_status(adapter)); + + pvscsi_unmask_intr(adapter); + + /* pvscsi_dma(adapter); */ + return 0; + +out_cam_busreg: + xpt_bus_deregister(cam_sim_path(adapter->pvs_camsim)); +out_cam_sim: + cam_sim_free(adapter->pvs_camsim, false); +out_cam_simq: + cam_simq_free(adapter->pvs_camdevq); +out_delete_dmat: + bus_dma_tag_destroy(adapter->pvs_dmat); + mtx_destroy(&adapter->pvs_camlock); +#ifdef __ISILON__ + free(adapter->pvscsi_dbgfails, M_PVSCSI); + adapter->pvscsi_dbgfails = NULL; +out_free_pvs_tarrg: +#endif + free(adapter->pvs_tarrg, M_PVSCSI); + adapter->pvs_tarrg = NULL; +out_reset_adapter: + ll_adapter_reset(adapter); +out_release_resources: + pvscsi_release_resources(adapter); +out_disable_device: + pci_disable_io(device, SYS_RES_MEMORY); + pci_disable_busmaster(device); + return -ENXIO; +} + +static int +pvscsi_pci_probe(device_t device) +{ + if ((pci_get_vendor(device) != PCI_VENDOR_ID_VMWARE) || + (pci_get_device(device) != PCI_DEVICE_ID_VMWARE_PVSCSI)) { + return ENXIO; + } + + device_set_desc(device, "VMware para-virtual SCSI driver v1.1.2.0"); + + return 0; +} + +static device_method_t +pvscsi_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, pvscsi_pci_probe), + DEVMETHOD(device_attach, pvscsi_pci_attach), + DEVMETHOD(device_detach, pvscsi_pci_detach), + { 0, 0 } +}; + +static driver_t pvscsi_pci_driver = { + "pvscsi", + pvscsi_pci_methods, + sizeof(pvscsinst_t), +}; + +static int +pvscsi_mod(module_t modp, int modev, void *arg) +{ + switch(modev) { + case MOD_LOAD: { + /* One time module initialization here */ + break; + } + case MOD_UNLOAD: { + /* One time module uninitialization here */ + break; + } + default: + break; + } + + return 0; +} + +static devclass_t pvscsi_devclass; +DRIVER_MODULE(MODNM, pci, pvscsi_pci_driver, pvscsi_devclass, pvscsi_mod, NULL); +MODULE_VERSION(MODNM, 1); Index: sys/modules/vmware/Makefile =================================================================== --- sys/modules/vmware/Makefile +++ sys/modules/vmware/Makefile @@ -23,6 +23,7 @@ # SUCH DAMAGE. # -SUBDIR= vmxnet3 +SUBDIR= vmxnet3 +SUBDIR+= pvscsi .include Index: sys/modules/vmware/pvscsi/Makefile =================================================================== --- /dev/null +++ sys/modules/vmware/pvscsi/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../dev/vmware/pvscsi +DEBUG_FLAGS = -g + +KMOD= vmw_pvscsi +SRCS= vmw_pvscsi.c +SRCS+= opt_cam.h bus_if.h device_if.h pci_if.h + +.include