![Ken Depro](/assets/img/avatar_default.png)
This patch removes extra blank lines from the virthba header and source files. This is the first of a set of patches that will clean up the virthba source file. The only checkpatch issue in the header file was the blank line warnings. Signed-off-by: Ken Depro <kenneth.depro@unisys.com> Signed-off-by: Ben Romer <benjamin.romer@unisys.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1764 lines
55 KiB
C
1764 lines
55 KiB
C
/* virthba.c
|
|
*
|
|
* Copyright (C) 2010 - 2013 UNISYS CORPORATION
|
|
* 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; either version 2 of the License, or (at
|
|
* your option) any 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.
|
|
*/
|
|
|
|
#define EXPORT_SYMTAB
|
|
|
|
/* if you want to turn on some debugging of write device data or read
|
|
* device data, define these two undefs. You will probably want to
|
|
* customize the code which is here since it was written assuming
|
|
* reading and writing a specific data file df.64M.txt which is a
|
|
* 64Megabyte file created by Art Nilson using a scritp I wrote called
|
|
* cr_test_data.pl. The data file consists of 256 byte lines of text
|
|
* which start with an 8 digit sequence number, a colon, and then
|
|
* letters after that */
|
|
|
|
#undef DBGINF
|
|
|
|
#include <linux/kernel.h>
|
|
#ifdef CONFIG_MODVERSIONS
|
|
#include <config/modversions.h>
|
|
#endif
|
|
|
|
#include "uniklog.h"
|
|
#include "diagnostics/appos_subsystems.h"
|
|
#include "uisutils.h"
|
|
#include "uisqueue.h"
|
|
#include "uisthread.h"
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/device.h>
|
|
#include <linux/slab.h>
|
|
#include <scsi/scsi.h>
|
|
#include <scsi/scsi_host.h>
|
|
#include <scsi/scsi_cmnd.h>
|
|
#include <scsi/scsi_device.h>
|
|
#include <asm/param.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "virthba.h"
|
|
#include "virtpci.h"
|
|
#include "visorchipset.h"
|
|
#include "version.h"
|
|
#include "guestlinuxdebug.h"
|
|
/* this is shorter than using __FILE__ (full path name) in
|
|
* debug/info/error messages
|
|
*/
|
|
#define CURRENT_FILE_PC VIRT_HBA_PC_virthba_c
|
|
#define __MYFILE__ "virthba.c"
|
|
|
|
/* NOTE: L1_CACHE_BYTES >=128 */
|
|
#define DEVICE_ATTRIBUTE struct device_attribute
|
|
|
|
/* MAX_BUF = 6 lines x 10 MAXVHBA x 80 characters
|
|
* = 4800 bytes ~ 2^13 = 8192 bytes
|
|
*/
|
|
#define MAX_BUF 8192
|
|
|
|
/*****************************************************/
|
|
/* Forward declarations */
|
|
/*****************************************************/
|
|
static int virthba_probe(struct virtpci_dev *dev,
|
|
const struct pci_device_id *id);
|
|
static void virthba_remove(struct virtpci_dev *dev);
|
|
static int virthba_abort_handler(struct scsi_cmnd *scsicmd);
|
|
static int virthba_bus_reset_handler(struct scsi_cmnd *scsicmd);
|
|
static int virthba_device_reset_handler(struct scsi_cmnd *scsicmd);
|
|
static int virthba_host_reset_handler(struct scsi_cmnd *scsicmd);
|
|
static const char *virthba_get_info(struct Scsi_Host *shp);
|
|
static int virthba_ioctl(struct scsi_device *dev, int cmd, void __user *arg);
|
|
static int virthba_queue_command_lck(struct scsi_cmnd *scsicmd,
|
|
void (*virthba_cmnd_done)(struct scsi_cmnd *));
|
|
|
|
static const struct x86_cpu_id unisys_spar_ids[] = {
|
|
{ X86_VENDOR_INTEL, 6, 62, X86_FEATURE_ANY },
|
|
{}
|
|
};
|
|
|
|
/* Autoload */
|
|
MODULE_DEVICE_TABLE(x86cpu, unisys_spar_ids);
|
|
|
|
#ifdef DEF_SCSI_QCMD
|
|
static DEF_SCSI_QCMD(virthba_queue_command)
|
|
#else
|
|
#define virthba_queue_command virthba_queue_command_lck
|
|
#endif
|
|
|
|
static int virthba_slave_alloc(struct scsi_device *scsidev);
|
|
static int virthba_slave_configure(struct scsi_device *scsidev);
|
|
static void virthba_slave_destroy(struct scsi_device *scsidev);
|
|
static int process_incoming_rsps(void *);
|
|
static int virthba_serverup(struct virtpci_dev *virtpcidev);
|
|
static int virthba_serverdown(struct virtpci_dev *virtpcidev, u32 state);
|
|
static void doDiskAddRemove(struct work_struct *work);
|
|
static void virthba_serverdown_complete(struct work_struct *work);
|
|
static ssize_t info_debugfs_read(struct file *file, char __user *buf,
|
|
size_t len, loff_t *offset);
|
|
static ssize_t enable_ints_write(struct file *file,
|
|
const char __user *buffer, size_t count, loff_t *ppos);
|
|
|
|
/*****************************************************/
|
|
/* Globals */
|
|
/*****************************************************/
|
|
|
|
static int rsltq_wait_usecs = 4000; /* Default 4ms */
|
|
static unsigned int MaxBuffLen;
|
|
|
|
/* Module options */
|
|
static char *virthba_options = "NONE";
|
|
|
|
static const struct pci_device_id virthba_id_table[] = {
|
|
{PCI_DEVICE(PCI_VENDOR_ID_UNISYS, PCI_DEVICE_ID_VIRTHBA)},
|
|
{0},
|
|
};
|
|
|
|
/* export virthba_id_table */
|
|
MODULE_DEVICE_TABLE(pci, virthba_id_table);
|
|
|
|
static struct workqueue_struct *virthba_serverdown_workqueue;
|
|
|
|
static struct virtpci_driver virthba_driver = {
|
|
.name = "uisvirthba",
|
|
.version = VERSION,
|
|
.vertag = NULL,
|
|
.id_table = virthba_id_table,
|
|
.probe = virthba_probe,
|
|
.remove = virthba_remove,
|
|
.resume = virthba_serverup,
|
|
.suspend = virthba_serverdown
|
|
};
|
|
|
|
/* The Send and Recive Buffers of the IO Queue may both be full */
|
|
#define MAX_PENDING_REQUESTS (MIN_NUMSIGNALS*2)
|
|
#define INTERRUPT_VECTOR_MASK 0x3F
|
|
|
|
struct scsipending {
|
|
char cmdtype; /* Type of pointer that is being stored */
|
|
void *sent; /* The Data being tracked */
|
|
/* struct scsi_cmnd *type for virthba_queue_command */
|
|
/* struct uiscmdrsp *type for management commands */
|
|
};
|
|
|
|
#define VIRTHBA_ERROR_COUNT 30
|
|
#define IOS_ERROR_THRESHOLD 1000
|
|
struct virtdisk_info {
|
|
u32 valid;
|
|
u32 channel, id, lun; /* Disk Path */
|
|
atomic_t ios_threshold;
|
|
atomic_t error_count;
|
|
struct virtdisk_info *next;
|
|
};
|
|
/* Each Scsi_Host has a host_data area that contains this struct. */
|
|
struct virthba_info {
|
|
struct Scsi_Host *scsihost;
|
|
struct virtpci_dev *virtpcidev;
|
|
struct list_head dev_info_list;
|
|
struct chaninfo chinfo;
|
|
struct irq_info intr; /* use recvInterrupt info to receive
|
|
interrupts when IOs complete */
|
|
int interrupt_vector;
|
|
struct scsipending pending[MAX_PENDING_REQUESTS]; /* Tracks the requests
|
|
that have been */
|
|
/* forwarded to the IOVM and haven't returned yet */
|
|
unsigned int nextinsert; /* Start search for next pending
|
|
free slot here */
|
|
spinlock_t privlock;
|
|
bool serverdown;
|
|
bool serverchangingstate;
|
|
unsigned long long acquire_failed_cnt;
|
|
unsigned long long interrupts_rcvd;
|
|
unsigned long long interrupts_notme;
|
|
unsigned long long interrupts_disabled;
|
|
struct work_struct serverdown_completion;
|
|
u64 __iomem *flags_addr;
|
|
atomic_t interrupt_rcvd;
|
|
wait_queue_head_t rsp_queue;
|
|
struct virtdisk_info head;
|
|
};
|
|
|
|
/* Work Data for DARWorkQ */
|
|
struct diskaddremove {
|
|
u8 add; /* 0-remove, 1-add */
|
|
struct Scsi_Host *shost; /* Scsi Host for this virthba instance */
|
|
u32 channel, id, lun; /* Disk Path */
|
|
struct diskaddremove *next;
|
|
};
|
|
|
|
#define virtpci_dev_to_virthba_virthba_get_info(d) \
|
|
container_of(d, struct virthba_info, virtpcidev)
|
|
|
|
static DEVICE_ATTRIBUTE *virthba_shost_attrs[];
|
|
static struct scsi_host_template virthba_driver_template = {
|
|
.name = "Unisys Virtual HBA",
|
|
.info = virthba_get_info,
|
|
.ioctl = virthba_ioctl,
|
|
.queuecommand = virthba_queue_command,
|
|
.eh_abort_handler = virthba_abort_handler,
|
|
.eh_device_reset_handler = virthba_device_reset_handler,
|
|
.eh_bus_reset_handler = virthba_bus_reset_handler,
|
|
.eh_host_reset_handler = virthba_host_reset_handler,
|
|
.shost_attrs = virthba_shost_attrs,
|
|
|
|
#define VIRTHBA_MAX_CMNDS 128
|
|
.can_queue = VIRTHBA_MAX_CMNDS,
|
|
.sg_tablesize = 64, /* largest number of address/length pairs */
|
|
.this_id = -1,
|
|
.slave_alloc = virthba_slave_alloc,
|
|
.slave_configure = virthba_slave_configure,
|
|
.slave_destroy = virthba_slave_destroy,
|
|
.use_clustering = ENABLE_CLUSTERING,
|
|
};
|
|
|
|
struct virthba_devices_open {
|
|
struct virthba_info *virthbainfo;
|
|
};
|
|
|
|
static const struct file_operations debugfs_info_fops = {
|
|
.read = info_debugfs_read,
|
|
};
|
|
|
|
static const struct file_operations debugfs_enable_ints_fops = {
|
|
.write = enable_ints_write,
|
|
};
|
|
|
|
/*****************************************************/
|
|
/* Structs */
|
|
/*****************************************************/
|
|
|
|
#define VIRTHBASOPENMAX 1
|
|
/* array of open devices maintained by open() and close(); */
|
|
static struct virthba_devices_open VirtHbasOpen[VIRTHBASOPENMAX];
|
|
static struct dentry *virthba_debugfs_dir;
|
|
|
|
/*****************************************************/
|
|
/* Local Functions */
|
|
/*****************************************************/
|
|
static int
|
|
add_scsipending_entry(struct virthba_info *vhbainfo, char cmdtype, void *new)
|
|
{
|
|
unsigned long flags;
|
|
int insert_location;
|
|
|
|
spin_lock_irqsave(&vhbainfo->privlock, flags);
|
|
insert_location = vhbainfo->nextinsert;
|
|
while (vhbainfo->pending[insert_location].sent != NULL) {
|
|
insert_location = (insert_location + 1) % MAX_PENDING_REQUESTS;
|
|
if (insert_location == (int) vhbainfo->nextinsert) {
|
|
LOGERR("Queue should be full. insert_location<<%d>> Unable to find open slot for pending commands.\n",
|
|
insert_location);
|
|
spin_unlock_irqrestore(&vhbainfo->privlock, flags);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
vhbainfo->pending[insert_location].cmdtype = cmdtype;
|
|
vhbainfo->pending[insert_location].sent = new;
|
|
vhbainfo->nextinsert = (insert_location + 1) % MAX_PENDING_REQUESTS;
|
|
spin_unlock_irqrestore(&vhbainfo->privlock, flags);
|
|
|
|
return insert_location;
|
|
}
|
|
|
|
static unsigned int
|
|
add_scsipending_entry_with_wait(struct virthba_info *vhbainfo, char cmdtype,
|
|
void *new)
|
|
{
|
|
int insert_location = add_scsipending_entry(vhbainfo, cmdtype, new);
|
|
|
|
while (insert_location == -1) {
|
|
LOGERR("Failed to find empty queue slot. Waiting to try again\n");
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
schedule_timeout(msecs_to_jiffies(10));
|
|
insert_location = add_scsipending_entry(vhbainfo, cmdtype, new);
|
|
}
|
|
|
|
return (unsigned int) insert_location;
|
|
}
|
|
|
|
static void *
|
|
del_scsipending_entry(struct virthba_info *vhbainfo, uintptr_t del)
|
|
{
|
|
unsigned long flags;
|
|
void *sent = NULL;
|
|
|
|
if (del >= MAX_PENDING_REQUESTS) {
|
|
LOGERR("Invalid queue position <<%lu>> given to delete. MAX_PENDING_REQUESTS <<%d>>\n",
|
|
(unsigned long) del, MAX_PENDING_REQUESTS);
|
|
} else {
|
|
spin_lock_irqsave(&vhbainfo->privlock, flags);
|
|
|
|
if (vhbainfo->pending[del].sent == NULL)
|
|
LOGERR("Deleting already cleared queue entry at <<%lu>>.\n",
|
|
(unsigned long) del);
|
|
|
|
sent = vhbainfo->pending[del].sent;
|
|
|
|
vhbainfo->pending[del].cmdtype = 0;
|
|
vhbainfo->pending[del].sent = NULL;
|
|
spin_unlock_irqrestore(&vhbainfo->privlock, flags);
|
|
}
|
|
|
|
return sent;
|
|
}
|
|
|
|
/* DARWorkQ (Disk Add/Remove) */
|
|
static struct work_struct DARWorkQ;
|
|
static struct diskaddremove *DARWorkQHead;
|
|
static spinlock_t DARWorkQLock;
|
|
static unsigned short DARWorkQSched;
|
|
#define QUEUE_DISKADDREMOVE(dar) { \
|
|
spin_lock_irqsave(&DARWorkQLock, flags); \
|
|
if (!DARWorkQHead) { \
|
|
DARWorkQHead = dar; \
|
|
dar->next = NULL; \
|
|
} \
|
|
else { \
|
|
dar->next = DARWorkQHead; \
|
|
DARWorkQHead = dar; \
|
|
} \
|
|
if (!DARWorkQSched) { \
|
|
schedule_work(&DARWorkQ); \
|
|
DARWorkQSched = 1; \
|
|
} \
|
|
spin_unlock_irqrestore(&DARWorkQLock, flags); \
|
|
}
|
|
|
|
static inline void
|
|
SendDiskAddRemove(struct diskaddremove *dar)
|
|
{
|
|
struct scsi_device *sdev;
|
|
int error;
|
|
|
|
sdev = scsi_device_lookup(dar->shost, dar->channel, dar->id, dar->lun);
|
|
if (sdev) {
|
|
if (!(dar->add))
|
|
scsi_remove_device(sdev);
|
|
} else if (dar->add) {
|
|
error =
|
|
scsi_add_device(dar->shost, dar->channel, dar->id,
|
|
dar->lun);
|
|
if (error)
|
|
LOGERR("Failed scsi_add_device: host_no=%d[chan=%d:id=%d:lun=%d]\n",
|
|
dar->shost->host_no, dar->channel, dar->id,
|
|
dar->lun);
|
|
} else
|
|
LOGERR("Failed scsi_device_lookup:[chan=%d:id=%d:lun=%d]\n",
|
|
dar->channel, dar->id, dar->lun);
|
|
kfree(dar);
|
|
}
|
|
|
|
/*****************************************************/
|
|
/* DARWorkQ Handler Thread */
|
|
/*****************************************************/
|
|
static void
|
|
doDiskAddRemove(struct work_struct *work)
|
|
{
|
|
struct diskaddremove *dar;
|
|
struct diskaddremove *tmphead;
|
|
int i = 0;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&DARWorkQLock, flags);
|
|
tmphead = DARWorkQHead;
|
|
DARWorkQHead = NULL;
|
|
DARWorkQSched = 0;
|
|
spin_unlock_irqrestore(&DARWorkQLock, flags);
|
|
while (tmphead) {
|
|
dar = tmphead;
|
|
tmphead = dar->next;
|
|
SendDiskAddRemove(dar);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/*****************************************************/
|
|
/* Routine to add entry to DARWorkQ */
|
|
/*****************************************************/
|
|
static void
|
|
process_disk_notify(struct Scsi_Host *shost, struct uiscmdrsp *cmdrsp)
|
|
{
|
|
struct diskaddremove *dar;
|
|
unsigned long flags;
|
|
|
|
dar = kzalloc(sizeof(struct diskaddremove), GFP_ATOMIC);
|
|
if (dar) {
|
|
dar->add = cmdrsp->disknotify.add;
|
|
dar->shost = shost;
|
|
dar->channel = cmdrsp->disknotify.channel;
|
|
dar->id = cmdrsp->disknotify.id;
|
|
dar->lun = cmdrsp->disknotify.lun;
|
|
QUEUE_DISKADDREMOVE(dar);
|
|
} else {
|
|
LOGERR("kmalloc failed for dar. host_no=%d[chan=%d:id=%d:lun=%d]\n",
|
|
shost->host_no, cmdrsp->disknotify.channel,
|
|
cmdrsp->disknotify.id, cmdrsp->disknotify.lun);
|
|
}
|
|
}
|
|
|
|
/*****************************************************/
|
|
/* Probe Remove Functions */
|
|
/*****************************************************/
|
|
static irqreturn_t
|
|
virthba_ISR(int irq, void *dev_id)
|
|
{
|
|
struct virthba_info *virthbainfo = (struct virthba_info *) dev_id;
|
|
struct channel_header __iomem *pChannelHeader;
|
|
struct signal_queue_header __iomem *pqhdr;
|
|
u64 mask;
|
|
unsigned long long rc1;
|
|
|
|
if (virthbainfo == NULL)
|
|
return IRQ_NONE;
|
|
virthbainfo->interrupts_rcvd++;
|
|
pChannelHeader = virthbainfo->chinfo.queueinfo->chan;
|
|
if (((readq(&pChannelHeader->features)
|
|
& ULTRA_IO_IOVM_IS_OK_WITH_DRIVER_DISABLING_INTS) != 0)
|
|
&& ((readq(&pChannelHeader->features) &
|
|
ULTRA_IO_DRIVER_DISABLES_INTS) !=
|
|
0)) {
|
|
virthbainfo->interrupts_disabled++;
|
|
mask = ~ULTRA_CHANNEL_ENABLE_INTS;
|
|
rc1 = uisqueue_interlocked_and(virthbainfo->flags_addr, mask);
|
|
}
|
|
if (spar_signalqueue_empty(pChannelHeader, IOCHAN_FROM_IOPART)) {
|
|
virthbainfo->interrupts_notme++;
|
|
return IRQ_NONE;
|
|
}
|
|
pqhdr = (struct signal_queue_header __iomem *)
|
|
((char __iomem *) pChannelHeader +
|
|
readq(&pChannelHeader->ch_space_offset)) + IOCHAN_FROM_IOPART;
|
|
writeq(readq(&pqhdr->num_irq_received) + 1,
|
|
&pqhdr->num_irq_received);
|
|
atomic_set(&virthbainfo->interrupt_rcvd, 1);
|
|
wake_up_interruptible(&virthbainfo->rsp_queue);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int
|
|
virthba_probe(struct virtpci_dev *virtpcidev, const struct pci_device_id *id)
|
|
{
|
|
int error;
|
|
struct Scsi_Host *scsihost;
|
|
struct virthba_info *virthbainfo;
|
|
int rsp;
|
|
int i;
|
|
irq_handler_t handler = virthba_ISR;
|
|
struct channel_header __iomem *pChannelHeader;
|
|
struct signal_queue_header __iomem *pqhdr;
|
|
u64 mask;
|
|
|
|
LOGVER("entering virthba_probe...\n");
|
|
LOGVER("virtpcidev bus_no<<%d>>devNo<<%d>>", virtpcidev->bus_no,
|
|
virtpcidev->device_no);
|
|
|
|
LOGINF("entering virthba_probe...\n");
|
|
LOGINF("virtpcidev bus_no<<%d>>devNo<<%d>>", virtpcidev->bus_no,
|
|
virtpcidev->device_no);
|
|
POSTCODE_LINUX_2(VHBA_PROBE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
|
|
/* call scsi_host_alloc to register a scsi host adapter
|
|
* instance - this virthba that has just been created is an
|
|
* instance of a scsi host adapter. This scsi_host_alloc
|
|
* function allocates a new Scsi_Host struct & performs basic
|
|
* initializatoin. The host is not published to the scsi
|
|
* midlayer until scsi_add_host is called.
|
|
*/
|
|
DBGINF("calling scsi_host_alloc.\n");
|
|
|
|
/* arg 2 passed in length of extra space we want allocated
|
|
* with scsi_host struct for our own use scsi_host_alloc
|
|
* assign host_no
|
|
*/
|
|
scsihost = scsi_host_alloc(&virthba_driver_template,
|
|
sizeof(struct virthba_info));
|
|
if (scsihost == NULL)
|
|
return -ENODEV;
|
|
|
|
DBGINF("scsihost: 0x%p, scsihost->this_id: %d, host_no: %d.\n",
|
|
scsihost, scsihost->this_id, scsihost->host_no);
|
|
|
|
scsihost->this_id = UIS_MAGIC_VHBA;
|
|
/* linux treats max-channel differently than max-id & max-lun.
|
|
* In the latter cases, those two values result in 0 to max-1
|
|
* (inclusive) being scanned. But in the case of channels, the
|
|
* scan is 0 to max (inclusive); so we will subtract one from
|
|
* the max-channel value.
|
|
*/
|
|
LOGINF("virtpcidev->scsi.max.max_channel=%u, max_id=%u, max_lun=%u, cmd_per_lun=%u, max_io_size=%u\n",
|
|
(unsigned) virtpcidev->scsi.max.max_channel - 1,
|
|
(unsigned) virtpcidev->scsi.max.max_id,
|
|
(unsigned) virtpcidev->scsi.max.max_lun,
|
|
(unsigned) virtpcidev->scsi.max.cmd_per_lun,
|
|
(unsigned) virtpcidev->scsi.max.max_io_size);
|
|
scsihost->max_channel = (unsigned) virtpcidev->scsi.max.max_channel;
|
|
scsihost->max_id = (unsigned) virtpcidev->scsi.max.max_id;
|
|
scsihost->max_lun = (unsigned) virtpcidev->scsi.max.max_lun;
|
|
scsihost->cmd_per_lun = (unsigned) virtpcidev->scsi.max.cmd_per_lun;
|
|
scsihost->max_sectors =
|
|
(unsigned short) (virtpcidev->scsi.max.max_io_size >> 9);
|
|
scsihost->sg_tablesize =
|
|
(unsigned short) (virtpcidev->scsi.max.max_io_size / PAGE_SIZE);
|
|
if (scsihost->sg_tablesize > MAX_PHYS_INFO)
|
|
scsihost->sg_tablesize = MAX_PHYS_INFO;
|
|
LOGINF("scsihost->max_channel=%u, max_id=%u, max_lun=%llu, cmd_per_lun=%u, max_sectors=%hu, sg_tablesize=%hu\n",
|
|
scsihost->max_channel, scsihost->max_id, scsihost->max_lun,
|
|
scsihost->cmd_per_lun, scsihost->max_sectors,
|
|
scsihost->sg_tablesize);
|
|
LOGINF("scsihost->can_queue=%u, scsihost->cmd_per_lun=%u, max_sectors=%hu, sg_tablesize=%hu\n",
|
|
scsihost->can_queue, scsihost->cmd_per_lun, scsihost->max_sectors,
|
|
scsihost->sg_tablesize);
|
|
|
|
DBGINF("calling scsi_add_host\n");
|
|
|
|
/* this creates "host%d" in sysfs. If 2nd argument is NULL,
|
|
* then this generic /sys/devices/platform/host? device is
|
|
* created and /sys/scsi_host/host? ->
|
|
* /sys/devices/platform/host? If 2nd argument is not NULL,
|
|
* then this generic /sys/devices/<path>/host? is created and
|
|
* host? points to that device instead.
|
|
*/
|
|
error = scsi_add_host(scsihost, &virtpcidev->generic_dev);
|
|
if (error) {
|
|
LOGERR("scsi_add_host ****FAILED 0x%x TBD - RECOVER\n", error);
|
|
POSTCODE_LINUX_2(VHBA_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
|
|
/* decr refcount on scsihost which was incremented by
|
|
* scsi_add_host so the scsi_host gets deleted
|
|
*/
|
|
scsi_host_put(scsihost);
|
|
return -ENODEV;
|
|
}
|
|
|
|
virthbainfo = (struct virthba_info *) scsihost->hostdata;
|
|
memset(virthbainfo, 0, sizeof(struct virthba_info));
|
|
for (i = 0; i < VIRTHBASOPENMAX; i++) {
|
|
if (VirtHbasOpen[i].virthbainfo == NULL) {
|
|
VirtHbasOpen[i].virthbainfo = virthbainfo;
|
|
break;
|
|
}
|
|
}
|
|
virthbainfo->interrupt_vector = -1;
|
|
virthbainfo->chinfo.queueinfo = &virtpcidev->queueinfo;
|
|
virthbainfo->virtpcidev = virtpcidev;
|
|
spin_lock_init(&virthbainfo->chinfo.insertlock);
|
|
|
|
DBGINF("generic_dev: 0x%p, queueinfo: 0x%p.\n",
|
|
&virtpcidev->generic_dev, &virtpcidev->queueinfo);
|
|
|
|
init_waitqueue_head(&virthbainfo->rsp_queue);
|
|
spin_lock_init(&virthbainfo->privlock);
|
|
memset(&virthbainfo->pending, 0, sizeof(virthbainfo->pending));
|
|
virthbainfo->serverdown = false;
|
|
virthbainfo->serverchangingstate = false;
|
|
|
|
virthbainfo->intr = virtpcidev->intr;
|
|
/* save of host within virthba_info */
|
|
virthbainfo->scsihost = scsihost;
|
|
|
|
/* save of host within virtpci_dev */
|
|
virtpcidev->scsi.scsihost = scsihost;
|
|
|
|
/* Setup workqueue for serverdown messages */
|
|
INIT_WORK(&virthbainfo->serverdown_completion,
|
|
virthba_serverdown_complete);
|
|
|
|
writeq(readq(&virthbainfo->chinfo.queueinfo->chan->features) |
|
|
ULTRA_IO_CHANNEL_IS_POLLING,
|
|
&virthbainfo->chinfo.queueinfo->chan->features);
|
|
/* start thread that will receive scsicmnd responses */
|
|
DBGINF("starting rsp thread -- queueinfo: 0x%p, threadinfo: 0x%p.\n",
|
|
virthbainfo->chinfo.queueinfo, &virthbainfo->chinfo.threadinfo);
|
|
|
|
pChannelHeader = virthbainfo->chinfo.queueinfo->chan;
|
|
pqhdr = (struct signal_queue_header __iomem *)
|
|
((char __iomem *)pChannelHeader +
|
|
readq(&pChannelHeader->ch_space_offset)) + IOCHAN_FROM_IOPART;
|
|
virthbainfo->flags_addr = &pqhdr->features;
|
|
|
|
if (!uisthread_start(&virthbainfo->chinfo.threadinfo,
|
|
process_incoming_rsps,
|
|
virthbainfo, "vhba_incoming")) {
|
|
LOGERR("uisthread_start rsp ****FAILED\n");
|
|
/* decr refcount on scsihost which was incremented by
|
|
* scsi_add_host so the scsi_host gets deleted
|
|
*/
|
|
POSTCODE_LINUX_2(VHBA_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
|
|
scsi_host_put(scsihost);
|
|
return -ENODEV;
|
|
}
|
|
LOGINF("sendInterruptHandle=0x%16llX",
|
|
virthbainfo->intr.send_irq_handle);
|
|
LOGINF("recvInterruptHandle=0x%16llX",
|
|
virthbainfo->intr.recv_irq_handle);
|
|
LOGINF("recvInterruptVector=0x%8X",
|
|
virthbainfo->intr.recv_irq_vector);
|
|
LOGINF("recvInterruptShared=0x%2X",
|
|
virthbainfo->intr.recv_irq_shared);
|
|
LOGINF("scsihost.hostt->name=%s", scsihost->hostt->name);
|
|
virthbainfo->interrupt_vector =
|
|
virthbainfo->intr.recv_irq_handle & INTERRUPT_VECTOR_MASK;
|
|
rsp = request_irq(virthbainfo->interrupt_vector, handler, IRQF_SHARED,
|
|
scsihost->hostt->name, virthbainfo);
|
|
if (rsp != 0) {
|
|
LOGERR("request_irq(%d) uislib_virthba_ISR request failed with rsp=%d\n",
|
|
virthbainfo->interrupt_vector, rsp);
|
|
virthbainfo->interrupt_vector = -1;
|
|
POSTCODE_LINUX_2(VHBA_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR);
|
|
} else {
|
|
u64 __iomem *Features_addr =
|
|
&virthbainfo->chinfo.queueinfo->chan->features;
|
|
LOGERR("request_irq(%d) uislib_virthba_ISR request succeeded\n",
|
|
virthbainfo->interrupt_vector);
|
|
mask = ~(ULTRA_IO_CHANNEL_IS_POLLING |
|
|
ULTRA_IO_DRIVER_DISABLES_INTS);
|
|
uisqueue_interlocked_and(Features_addr, mask);
|
|
mask = ULTRA_IO_DRIVER_ENABLES_INTS;
|
|
uisqueue_interlocked_or(Features_addr, mask);
|
|
rsltq_wait_usecs = 4000000;
|
|
}
|
|
|
|
DBGINF("calling scsi_scan_host.\n");
|
|
scsi_scan_host(scsihost);
|
|
DBGINF("return from scsi_scan_host.\n");
|
|
|
|
LOGINF("virthba added scsihost:0x%p\n", scsihost);
|
|
POSTCODE_LINUX_2(VHBA_PROBE_EXIT_PC, POSTCODE_SEVERITY_INFO);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
virthba_remove(struct virtpci_dev *virtpcidev)
|
|
{
|
|
struct virthba_info *virthbainfo;
|
|
struct Scsi_Host *scsihost =
|
|
(struct Scsi_Host *) virtpcidev->scsi.scsihost;
|
|
|
|
LOGINF("virtpcidev bus_no<<%d>>devNo<<%d>>", virtpcidev->bus_no,
|
|
virtpcidev->device_no);
|
|
virthbainfo = (struct virthba_info *) scsihost->hostdata;
|
|
if (virthbainfo->interrupt_vector != -1)
|
|
free_irq(virthbainfo->interrupt_vector, virthbainfo);
|
|
LOGINF("Removing virtpcidev: 0x%p, virthbainfo: 0x%p\n", virtpcidev,
|
|
virthbainfo);
|
|
|
|
DBGINF("removing scsihost: 0x%p, scsihost->this_id: %d\n", scsihost,
|
|
scsihost->this_id);
|
|
scsi_remove_host(scsihost);
|
|
|
|
DBGINF("stopping thread.\n");
|
|
uisthread_stop(&virthbainfo->chinfo.threadinfo);
|
|
|
|
DBGINF("calling scsi_host_put\n");
|
|
|
|
/* decr refcount on scsihost which was incremented by
|
|
* scsi_add_host so the scsi_host gets deleted
|
|
*/
|
|
scsi_host_put(scsihost);
|
|
LOGINF("virthba removed scsi_host.\n");
|
|
}
|
|
|
|
static int
|
|
forward_vdiskmgmt_command(enum vdisk_mgmt_types vdiskcmdtype,
|
|
struct Scsi_Host *scsihost,
|
|
struct uisscsi_dest *vdest)
|
|
{
|
|
struct uiscmdrsp *cmdrsp;
|
|
struct virthba_info *virthbainfo =
|
|
(struct virthba_info *) scsihost->hostdata;
|
|
int notifyresult = 0xffff;
|
|
wait_queue_head_t notifyevent;
|
|
|
|
LOGINF("vDiskMgmt:%d %d:%d:%d\n", vdiskcmdtype,
|
|
vdest->channel, vdest->id, vdest->lun);
|
|
|
|
if (virthbainfo->serverdown || virthbainfo->serverchangingstate) {
|
|
DBGINF("Server is down/changing state. Returning Failure.\n");
|
|
return FAILED;
|
|
}
|
|
|
|
cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC);
|
|
if (cmdrsp == NULL) {
|
|
LOGERR("kmalloc of cmdrsp failed.\n");
|
|
return FAILED; /* reject */
|
|
}
|
|
|
|
init_waitqueue_head(¬ifyevent);
|
|
|
|
/* issue VDISK_MGMT_CMD
|
|
* set type to command - as opposed to task mgmt
|
|
*/
|
|
cmdrsp->cmdtype = CMD_VDISKMGMT_TYPE;
|
|
/* specify the event that has to be triggered when this cmd is
|
|
* complete
|
|
*/
|
|
cmdrsp->vdiskmgmt.notify = (void *) ¬ifyevent;
|
|
cmdrsp->vdiskmgmt.notifyresult = (void *) ¬ifyresult;
|
|
|
|
/* save destination */
|
|
cmdrsp->vdiskmgmt.vdisktype = vdiskcmdtype;
|
|
cmdrsp->vdiskmgmt.vdest.channel = vdest->channel;
|
|
cmdrsp->vdiskmgmt.vdest.id = vdest->id;
|
|
cmdrsp->vdiskmgmt.vdest.lun = vdest->lun;
|
|
cmdrsp->vdiskmgmt.scsicmd =
|
|
(void *) (uintptr_t)
|
|
add_scsipending_entry_with_wait(virthbainfo, CMD_VDISKMGMT_TYPE,
|
|
(void *) cmdrsp);
|
|
|
|
uisqueue_put_cmdrsp_with_lock_client(virthbainfo->chinfo.queueinfo,
|
|
cmdrsp, IOCHAN_TO_IOPART,
|
|
&virthbainfo->chinfo.insertlock,
|
|
DONT_ISSUE_INTERRUPT, (u64) NULL,
|
|
OK_TO_WAIT, "vhba");
|
|
LOGINF("VdiskMgmt waiting on event notifyevent=0x%p\n",
|
|
cmdrsp->scsitaskmgmt.notify);
|
|
wait_event(notifyevent, notifyresult != 0xffff);
|
|
LOGINF("VdiskMgmt complete; result:%d\n", cmdrsp->vdiskmgmt.result);
|
|
kfree(cmdrsp);
|
|
return SUCCESS;
|
|
}
|
|
|
|
/*****************************************************/
|
|
/* Scsi Host support functions */
|
|
/*****************************************************/
|
|
|
|
static int
|
|
forward_taskmgmt_command(enum task_mgmt_types tasktype,
|
|
struct scsi_device *scsidev)
|
|
{
|
|
struct uiscmdrsp *cmdrsp;
|
|
struct virthba_info *virthbainfo =
|
|
(struct virthba_info *) scsidev->host->hostdata;
|
|
int notifyresult = 0xffff;
|
|
wait_queue_head_t notifyevent;
|
|
|
|
LOGINF("TaskMgmt:%d %d:%d:%llu\n", tasktype,
|
|
scsidev->channel, scsidev->id, scsidev->lun);
|
|
|
|
if (virthbainfo->serverdown || virthbainfo->serverchangingstate) {
|
|
DBGINF("Server is down/changing state. Returning Failure.\n");
|
|
return FAILED;
|
|
}
|
|
|
|
cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC);
|
|
if (cmdrsp == NULL) {
|
|
LOGERR("kmalloc of cmdrsp failed.\n");
|
|
return FAILED; /* reject */
|
|
}
|
|
|
|
init_waitqueue_head(¬ifyevent);
|
|
|
|
/* issue TASK_MGMT_ABORT_TASK */
|
|
/* set type to command - as opposed to task mgmt */
|
|
cmdrsp->cmdtype = CMD_SCSITASKMGMT_TYPE;
|
|
/* specify the event that has to be triggered when this */
|
|
/* cmd is complete */
|
|
cmdrsp->scsitaskmgmt.notify = (void *) ¬ifyevent;
|
|
cmdrsp->scsitaskmgmt.notifyresult = (void *) ¬ifyresult;
|
|
|
|
/* save destination */
|
|
cmdrsp->scsitaskmgmt.tasktype = tasktype;
|
|
cmdrsp->scsitaskmgmt.vdest.channel = scsidev->channel;
|
|
cmdrsp->scsitaskmgmt.vdest.id = scsidev->id;
|
|
cmdrsp->scsitaskmgmt.vdest.lun = scsidev->lun;
|
|
cmdrsp->scsitaskmgmt.scsicmd =
|
|
(void *) (uintptr_t)
|
|
add_scsipending_entry_with_wait(virthbainfo,
|
|
CMD_SCSITASKMGMT_TYPE,
|
|
(void *) cmdrsp);
|
|
|
|
uisqueue_put_cmdrsp_with_lock_client(virthbainfo->chinfo.queueinfo,
|
|
cmdrsp, IOCHAN_TO_IOPART,
|
|
&virthbainfo->chinfo.insertlock,
|
|
DONT_ISSUE_INTERRUPT, (u64) NULL,
|
|
OK_TO_WAIT, "vhba");
|
|
LOGINF("TaskMgmt waiting on event notifyevent=0x%p\n",
|
|
cmdrsp->scsitaskmgmt.notify);
|
|
wait_event(notifyevent, notifyresult != 0xffff);
|
|
LOGINF("TaskMgmt complete; result:%d\n", cmdrsp->scsitaskmgmt.result);
|
|
kfree(cmdrsp);
|
|
return SUCCESS;
|
|
}
|
|
|
|
/* The abort handler returns SUCCESS if it has succeeded to make LLDD
|
|
* and all related hardware forget about the scmd.
|
|
*/
|
|
static int
|
|
virthba_abort_handler(struct scsi_cmnd *scsicmd)
|
|
{
|
|
/* issue TASK_MGMT_ABORT_TASK */
|
|
struct scsi_device *scsidev;
|
|
struct virtdisk_info *vdisk;
|
|
|
|
scsidev = scsicmd->device;
|
|
for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head;
|
|
vdisk->next; vdisk = vdisk->next) {
|
|
if ((scsidev->channel == vdisk->channel)
|
|
&& (scsidev->id == vdisk->id)
|
|
&& (scsidev->lun == vdisk->lun)) {
|
|
if (atomic_read(&vdisk->error_count) <
|
|
VIRTHBA_ERROR_COUNT) {
|
|
atomic_inc(&vdisk->error_count);
|
|
POSTCODE_LINUX_2(VHBA_COMMAND_HANDLER_PC,
|
|
POSTCODE_SEVERITY_INFO);
|
|
} else
|
|
atomic_set(&vdisk->ios_threshold,
|
|
IOS_ERROR_THRESHOLD);
|
|
}
|
|
}
|
|
return forward_taskmgmt_command(TASK_MGMT_ABORT_TASK, scsicmd->device);
|
|
}
|
|
|
|
static int
|
|
virthba_bus_reset_handler(struct scsi_cmnd *scsicmd)
|
|
{
|
|
/* issue TASK_MGMT_TARGET_RESET for each target on the bus */
|
|
struct scsi_device *scsidev;
|
|
struct virtdisk_info *vdisk;
|
|
|
|
scsidev = scsicmd->device;
|
|
for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head;
|
|
vdisk->next; vdisk = vdisk->next) {
|
|
if ((scsidev->channel == vdisk->channel)
|
|
&& (scsidev->id == vdisk->id)
|
|
&& (scsidev->lun == vdisk->lun)) {
|
|
if (atomic_read(&vdisk->error_count) <
|
|
VIRTHBA_ERROR_COUNT) {
|
|
atomic_inc(&vdisk->error_count);
|
|
POSTCODE_LINUX_2(VHBA_COMMAND_HANDLER_PC,
|
|
POSTCODE_SEVERITY_INFO);
|
|
} else
|
|
atomic_set(&vdisk->ios_threshold,
|
|
IOS_ERROR_THRESHOLD);
|
|
}
|
|
}
|
|
return forward_taskmgmt_command(TASK_MGMT_BUS_RESET, scsicmd->device);
|
|
}
|
|
|
|
static int
|
|
virthba_device_reset_handler(struct scsi_cmnd *scsicmd)
|
|
{
|
|
/* issue TASK_MGMT_LUN_RESET */
|
|
struct scsi_device *scsidev;
|
|
struct virtdisk_info *vdisk;
|
|
|
|
scsidev = scsicmd->device;
|
|
for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head;
|
|
vdisk->next; vdisk = vdisk->next) {
|
|
if ((scsidev->channel == vdisk->channel)
|
|
&& (scsidev->id == vdisk->id)
|
|
&& (scsidev->lun == vdisk->lun)) {
|
|
if (atomic_read(&vdisk->error_count) <
|
|
VIRTHBA_ERROR_COUNT) {
|
|
atomic_inc(&vdisk->error_count);
|
|
POSTCODE_LINUX_2(VHBA_COMMAND_HANDLER_PC,
|
|
POSTCODE_SEVERITY_INFO);
|
|
} else
|
|
atomic_set(&vdisk->ios_threshold,
|
|
IOS_ERROR_THRESHOLD);
|
|
}
|
|
}
|
|
return forward_taskmgmt_command(TASK_MGMT_LUN_RESET, scsicmd->device);
|
|
}
|
|
|
|
static int
|
|
virthba_host_reset_handler(struct scsi_cmnd *scsicmd)
|
|
{
|
|
/* issue TASK_MGMT_TARGET_RESET for each target on each bus for host */
|
|
LOGERR("virthba_host_reset_handler Not yet implemented\n");
|
|
return SUCCESS;
|
|
}
|
|
|
|
static char virthba_get_info_str[256];
|
|
|
|
static const char *
|
|
virthba_get_info(struct Scsi_Host *shp)
|
|
{
|
|
/* Return version string */
|
|
sprintf(virthba_get_info_str, "virthba, version %s\n", VIRTHBA_VERSION);
|
|
return virthba_get_info_str;
|
|
}
|
|
|
|
static int
|
|
virthba_ioctl(struct scsi_device *dev, int cmd, void __user *arg)
|
|
{
|
|
DBGINF("In virthba_ioctl: ioctl: cmd=0x%x\n", cmd);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* This returns SCSI_MLQUEUE_DEVICE_BUSY if the signal queue to IOpart
|
|
* is full.
|
|
*/
|
|
static int
|
|
virthba_queue_command_lck(struct scsi_cmnd *scsicmd,
|
|
void (*virthba_cmnd_done)(struct scsi_cmnd *))
|
|
{
|
|
struct scsi_device *scsidev = scsicmd->device;
|
|
int insert_location;
|
|
unsigned char op;
|
|
unsigned char *cdb = scsicmd->cmnd;
|
|
struct Scsi_Host *scsihost = scsidev->host;
|
|
struct uiscmdrsp *cmdrsp;
|
|
unsigned int i;
|
|
struct virthba_info *virthbainfo =
|
|
(struct virthba_info *) scsihost->hostdata;
|
|
struct scatterlist *sg = NULL;
|
|
struct scatterlist *sgl = NULL;
|
|
int sg_failed = 0;
|
|
|
|
if (virthbainfo->serverdown || virthbainfo->serverchangingstate) {
|
|
DBGINF("Server is down/changing state. Returning SCSI_MLQUEUE_DEVICE_BUSY.\n");
|
|
return SCSI_MLQUEUE_DEVICE_BUSY;
|
|
}
|
|
|
|
cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC);
|
|
if (cmdrsp == NULL) {
|
|
LOGERR("kmalloc of cmdrsp failed.\n");
|
|
return 1; /* reject the command */
|
|
}
|
|
|
|
/* now saving everything we need from scsi_cmd into cmdrsp
|
|
* before we queue cmdrsp set type to command - as opposed to
|
|
* task mgmt
|
|
*/
|
|
cmdrsp->cmdtype = CMD_SCSI_TYPE;
|
|
/* save the pending insertion location. Deletion from pending
|
|
* will return the scsicmd pointer for completion
|
|
*/
|
|
insert_location =
|
|
add_scsipending_entry(virthbainfo, CMD_SCSI_TYPE, (void *) scsicmd);
|
|
if (insert_location != -1) {
|
|
cmdrsp->scsi.scsicmd = (void *) (uintptr_t) insert_location;
|
|
} else {
|
|
LOGERR("Queue is full. Returning busy.\n");
|
|
kfree(cmdrsp);
|
|
return SCSI_MLQUEUE_DEVICE_BUSY;
|
|
}
|
|
/* save done function that we have call when cmd is complete */
|
|
scsicmd->scsi_done = virthba_cmnd_done;
|
|
/* save destination */
|
|
cmdrsp->scsi.vdest.channel = scsidev->channel;
|
|
cmdrsp->scsi.vdest.id = scsidev->id;
|
|
cmdrsp->scsi.vdest.lun = scsidev->lun;
|
|
/* save datadir */
|
|
cmdrsp->scsi.data_dir = scsicmd->sc_data_direction;
|
|
memcpy(cmdrsp->scsi.cmnd, cdb, MAX_CMND_SIZE);
|
|
|
|
cmdrsp->scsi.bufflen = scsi_bufflen(scsicmd);
|
|
|
|
/* keep track of the max buffer length so far. */
|
|
if (cmdrsp->scsi.bufflen > MaxBuffLen)
|
|
MaxBuffLen = cmdrsp->scsi.bufflen;
|
|
|
|
if (scsi_sg_count(scsicmd) > MAX_PHYS_INFO) {
|
|
LOGERR("scsicmd use_sg:%d greater than MAX:%d\n",
|
|
scsi_sg_count(scsicmd), MAX_PHYS_INFO);
|
|
del_scsipending_entry(virthbainfo, (uintptr_t) insert_location);
|
|
kfree(cmdrsp);
|
|
return 1; /* reject the command */
|
|
}
|
|
|
|
/* This is what we USED to do when we assumed we were running */
|
|
/* uissd & virthba on the same Linux system. */
|
|
/* cmdrsp->scsi.buffer = scsicmd->request_buffer; */
|
|
/* The following code does NOT make that assumption. */
|
|
/* convert buffer to phys information */
|
|
if (scsi_sg_count(scsicmd) == 0) {
|
|
if (scsi_bufflen(scsicmd) > 0) {
|
|
LOGERR("**** FAILED No scatter list for bufflen > 0\n");
|
|
BUG_ON(scsi_sg_count(scsicmd) == 0);
|
|
}
|
|
DBGINF("No sg; buffer:0x%p bufflen:%d\n",
|
|
scsi_sglist(scsicmd), scsi_bufflen(scsicmd));
|
|
} else {
|
|
/* buffer is scatterlist - copy it out */
|
|
sgl = scsi_sglist(scsicmd);
|
|
|
|
for_each_sg(sgl, sg, scsi_sg_count(scsicmd), i) {
|
|
|
|
cmdrsp->scsi.gpi_list[i].address = sg_phys(sg);
|
|
cmdrsp->scsi.gpi_list[i].length = sg->length;
|
|
if ((i != 0) && (sg->offset != 0))
|
|
LOGINF("Offset on a sg_entry other than zero =<<%d>>.\n",
|
|
sg->offset);
|
|
}
|
|
|
|
if (sg_failed) {
|
|
LOGERR("Start sg_list dump (entries %d, bufflen %d)...\n",
|
|
scsi_sg_count(scsicmd), cmdrsp->scsi.bufflen);
|
|
for_each_sg(sgl, sg, scsi_sg_count(scsicmd), i) {
|
|
LOGERR(" Entry(%d): page->[0x%p], phys->[0x%Lx], off(%d), len(%d)\n",
|
|
i, sg_page(sg),
|
|
(unsigned long long) sg_phys(sg),
|
|
sg->offset, sg->length);
|
|
}
|
|
LOGERR("Done sg_list dump.\n");
|
|
/* BUG(); ***** For now, let it fail in uissd
|
|
* if it is a problem, as it might just
|
|
* work
|
|
*/
|
|
}
|
|
|
|
cmdrsp->scsi.guest_phys_entries = scsi_sg_count(scsicmd);
|
|
}
|
|
|
|
op = cdb[0];
|
|
i = uisqueue_put_cmdrsp_with_lock_client(virthbainfo->chinfo.queueinfo,
|
|
cmdrsp, IOCHAN_TO_IOPART,
|
|
&virthbainfo->chinfo.
|
|
insertlock,
|
|
DONT_ISSUE_INTERRUPT,
|
|
(u64) NULL, DONT_WAIT, "vhba");
|
|
if (i == 0) {
|
|
/* queue must be full - and we said don't wait - return busy */
|
|
LOGERR("uisqueue_put_cmdrsp_with_lock ****FAILED\n");
|
|
kfree(cmdrsp);
|
|
del_scsipending_entry(virthbainfo, (uintptr_t) insert_location);
|
|
return SCSI_MLQUEUE_DEVICE_BUSY;
|
|
}
|
|
|
|
/* we're done with cmdrsp space - data from it has been copied
|
|
* into channel - free it now.
|
|
*/
|
|
kfree(cmdrsp);
|
|
return 0; /* non-zero implies host/device is busy */
|
|
}
|
|
|
|
static int
|
|
virthba_slave_alloc(struct scsi_device *scsidev)
|
|
{
|
|
/* this called by the midlayer before scan for new devices -
|
|
* LLD can alloc any struct & do init if needed.
|
|
*/
|
|
struct virtdisk_info *vdisk;
|
|
struct virtdisk_info *tmpvdisk;
|
|
struct virthba_info *virthbainfo;
|
|
struct Scsi_Host *scsihost = (struct Scsi_Host *) scsidev->host;
|
|
|
|
virthbainfo = (struct virthba_info *) scsihost->hostdata;
|
|
if (!virthbainfo) {
|
|
LOGERR("Could not find virthba_info for scsihost\n");
|
|
return 0; /* even though we errored, treat as success */
|
|
}
|
|
for (vdisk = &virthbainfo->head; vdisk->next; vdisk = vdisk->next) {
|
|
if (vdisk->next->valid &&
|
|
(vdisk->next->channel == scsidev->channel) &&
|
|
(vdisk->next->id == scsidev->id) &&
|
|
(vdisk->next->lun == scsidev->lun))
|
|
return 0;
|
|
}
|
|
tmpvdisk = kzalloc(sizeof(struct virtdisk_info), GFP_ATOMIC);
|
|
if (!tmpvdisk) { /* error allocating */
|
|
LOGERR("Could not allocate memory for disk\n");
|
|
return 0;
|
|
}
|
|
|
|
tmpvdisk->channel = scsidev->channel;
|
|
tmpvdisk->id = scsidev->id;
|
|
tmpvdisk->lun = scsidev->lun;
|
|
tmpvdisk->valid = 1;
|
|
vdisk->next = tmpvdisk;
|
|
return 0; /* success */
|
|
}
|
|
|
|
static int
|
|
virthba_slave_configure(struct scsi_device *scsidev)
|
|
{
|
|
return 0; /* success */
|
|
}
|
|
|
|
static void
|
|
virthba_slave_destroy(struct scsi_device *scsidev)
|
|
{
|
|
/* midlevel calls this after device has been quiesced and
|
|
* before it is to be deleted.
|
|
*/
|
|
struct virtdisk_info *vdisk, *delvdisk;
|
|
struct virthba_info *virthbainfo;
|
|
struct Scsi_Host *scsihost = (struct Scsi_Host *) scsidev->host;
|
|
|
|
virthbainfo = (struct virthba_info *) scsihost->hostdata;
|
|
if (!virthbainfo)
|
|
LOGERR("Could not find virthba_info for scsihost\n");
|
|
for (vdisk = &virthbainfo->head; vdisk->next; vdisk = vdisk->next) {
|
|
if (vdisk->next->valid &&
|
|
(vdisk->next->channel == scsidev->channel) &&
|
|
(vdisk->next->id == scsidev->id) &&
|
|
(vdisk->next->lun == scsidev->lun)) {
|
|
delvdisk = vdisk->next;
|
|
vdisk->next = vdisk->next->next;
|
|
kfree(delvdisk);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*****************************************************/
|
|
/* Scsi Cmnd support thread */
|
|
/*****************************************************/
|
|
|
|
static void
|
|
do_scsi_linuxstat(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd)
|
|
{
|
|
struct virtdisk_info *vdisk;
|
|
struct scsi_device *scsidev;
|
|
struct sense_data *sd;
|
|
|
|
scsidev = scsicmd->device;
|
|
memcpy(scsicmd->sense_buffer, cmdrsp->scsi.sensebuf, MAX_SENSE_SIZE);
|
|
sd = (struct sense_data *) scsicmd->sense_buffer;
|
|
|
|
/* Do not log errors for disk-not-present inquiries */
|
|
if ((cmdrsp->scsi.cmnd[0] == INQUIRY) &&
|
|
(host_byte(cmdrsp->scsi.linuxstat) == DID_NO_CONNECT) &&
|
|
(cmdrsp->scsi.addlstat == ADDL_SEL_TIMEOUT))
|
|
return;
|
|
|
|
/* Okay see what our error_count is here.... */
|
|
for (vdisk = &((struct virthba_info *) scsidev->host->hostdata)->head;
|
|
vdisk->next; vdisk = vdisk->next) {
|
|
if ((scsidev->channel != vdisk->channel)
|
|
|| (scsidev->id != vdisk->id)
|
|
|| (scsidev->lun != vdisk->lun))
|
|
continue;
|
|
|
|
if (atomic_read(&vdisk->error_count) < VIRTHBA_ERROR_COUNT) {
|
|
atomic_inc(&vdisk->error_count);
|
|
LOGERR("SCSICMD ****FAILED scsicmd:0x%p op:0x%x <%d:%d:%d:%llu> 0x%x-0x%x-0x%x-0x%x-0x%x.\n",
|
|
scsicmd, cmdrsp->scsi.cmnd[0],
|
|
scsidev->host->host_no, scsidev->id,
|
|
scsidev->channel, scsidev->lun,
|
|
cmdrsp->scsi.linuxstat, sd->valid, sd->sense_key,
|
|
sd->additional_sense_code,
|
|
sd->additional_sense_code_qualifier);
|
|
if (atomic_read(&vdisk->error_count) ==
|
|
VIRTHBA_ERROR_COUNT) {
|
|
LOGERR("Throtling SCSICMD errors disk <%d:%d:%d:%llu>\n",
|
|
scsidev->host->host_no, scsidev->id,
|
|
scsidev->channel, scsidev->lun);
|
|
}
|
|
atomic_set(&vdisk->ios_threshold, IOS_ERROR_THRESHOLD);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
do_scsi_nolinuxstat(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd)
|
|
{
|
|
struct scsi_device *scsidev;
|
|
unsigned char buf[36];
|
|
struct scatterlist *sg;
|
|
unsigned int i;
|
|
char *thispage;
|
|
char *thispage_orig;
|
|
int bufind = 0;
|
|
struct virtdisk_info *vdisk;
|
|
|
|
scsidev = scsicmd->device;
|
|
if ((cmdrsp->scsi.cmnd[0] == INQUIRY)
|
|
&& (cmdrsp->scsi.bufflen >= MIN_INQUIRY_RESULT_LEN)) {
|
|
if (cmdrsp->scsi.no_disk_result == 0)
|
|
return;
|
|
|
|
/* Linux scsi code is weird; it wants
|
|
* a device at Lun 0 to issue report
|
|
* luns, but we don't want a disk
|
|
* there so we'll present a processor
|
|
* there. */
|
|
SET_NO_DISK_INQUIRY_RESULT(buf, cmdrsp->scsi.bufflen,
|
|
scsidev->lun,
|
|
DEV_DISK_CAPABLE_NOT_PRESENT,
|
|
DEV_NOT_CAPABLE);
|
|
|
|
if (scsi_sg_count(scsicmd) == 0) {
|
|
if (scsi_bufflen(scsicmd) > 0) {
|
|
LOGERR("**** FAILED No scatter list for bufflen > 0\n");
|
|
BUG_ON(scsi_sg_count(scsicmd) ==
|
|
0);
|
|
}
|
|
memcpy(scsi_sglist(scsicmd), buf,
|
|
cmdrsp->scsi.bufflen);
|
|
return;
|
|
}
|
|
|
|
sg = scsi_sglist(scsicmd);
|
|
for (i = 0; i < scsi_sg_count(scsicmd); i++) {
|
|
DBGVER("copying OUT OF buf into 0x%p %d\n",
|
|
sg_page(sg + i), sg[i].length);
|
|
thispage_orig = kmap_atomic(sg_page(sg + i));
|
|
thispage = (void *) ((unsigned long)thispage_orig |
|
|
sg[i].offset);
|
|
memcpy(thispage, buf + bufind, sg[i].length);
|
|
kunmap_atomic(thispage_orig);
|
|
bufind += sg[i].length;
|
|
}
|
|
} else {
|
|
|
|
vdisk = &((struct virthba_info *)scsidev->host->hostdata)->head;
|
|
for ( ; vdisk->next; vdisk = vdisk->next) {
|
|
if ((scsidev->channel != vdisk->channel)
|
|
|| (scsidev->id != vdisk->id)
|
|
|| (scsidev->lun != vdisk->lun))
|
|
continue;
|
|
|
|
if (atomic_read(&vdisk->ios_threshold) > 0) {
|
|
atomic_dec(&vdisk->ios_threshold);
|
|
if (atomic_read(&vdisk->ios_threshold) == 0) {
|
|
LOGERR("Resetting error count for disk\n");
|
|
atomic_set(&vdisk->error_count, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
complete_scsi_command(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd)
|
|
{
|
|
DBGINF("cmdrsp: 0x%p, scsistat:0x%x.\n", cmdrsp, cmdrsp->scsi.scsistat);
|
|
|
|
/* take what we need out of cmdrsp and complete the scsicmd */
|
|
scsicmd->result = cmdrsp->scsi.linuxstat;
|
|
if (cmdrsp->scsi.linuxstat)
|
|
do_scsi_linuxstat(cmdrsp, scsicmd);
|
|
else
|
|
do_scsi_nolinuxstat(cmdrsp, scsicmd);
|
|
|
|
if (scsicmd->scsi_done) {
|
|
DBGVER("Scsi_DONE\n");
|
|
scsicmd->scsi_done(scsicmd);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
complete_vdiskmgmt_command(struct uiscmdrsp *cmdrsp)
|
|
{
|
|
/* copy the result of the taskmgmt and */
|
|
/* wake up the error handler that is waiting for this */
|
|
*(int *) cmdrsp->vdiskmgmt.notifyresult = cmdrsp->vdiskmgmt.result;
|
|
wake_up_all((wait_queue_head_t *) cmdrsp->vdiskmgmt.notify);
|
|
LOGINF("set notify result to %d\n", cmdrsp->vdiskmgmt.result);
|
|
}
|
|
|
|
static inline void
|
|
complete_taskmgmt_command(struct uiscmdrsp *cmdrsp)
|
|
{
|
|
/* copy the result of the taskmgmt and */
|
|
/* wake up the error handler that is waiting for this */
|
|
*(int *) cmdrsp->scsitaskmgmt.notifyresult =
|
|
cmdrsp->scsitaskmgmt.result;
|
|
wake_up_all((wait_queue_head_t *) cmdrsp->scsitaskmgmt.notify);
|
|
LOGINF("set notify result to %d\n", cmdrsp->scsitaskmgmt.result);
|
|
}
|
|
|
|
static void
|
|
drain_queue(struct virthba_info *virthbainfo, struct chaninfo *dc,
|
|
struct uiscmdrsp *cmdrsp)
|
|
{
|
|
unsigned long flags;
|
|
int qrslt = 0;
|
|
struct scsi_cmnd *scsicmd;
|
|
struct Scsi_Host *shost = virthbainfo->scsihost;
|
|
|
|
while (1) {
|
|
spin_lock_irqsave(&virthbainfo->chinfo.insertlock, flags);
|
|
if (!spar_channel_client_acquire_os(dc->queueinfo->chan,
|
|
"vhba")) {
|
|
spin_unlock_irqrestore(&virthbainfo->chinfo.insertlock,
|
|
flags);
|
|
virthbainfo->acquire_failed_cnt++;
|
|
break;
|
|
}
|
|
qrslt = uisqueue_get_cmdrsp(dc->queueinfo, cmdrsp,
|
|
IOCHAN_FROM_IOPART);
|
|
spar_channel_client_release_os(dc->queueinfo->chan, "vhba");
|
|
spin_unlock_irqrestore(&virthbainfo->chinfo.insertlock, flags);
|
|
if (qrslt == 0)
|
|
break;
|
|
if (cmdrsp->cmdtype == CMD_SCSI_TYPE) {
|
|
/* scsicmd location is returned by the
|
|
* deletion
|
|
*/
|
|
scsicmd = del_scsipending_entry(virthbainfo,
|
|
(uintptr_t) cmdrsp->scsi.scsicmd);
|
|
if (!scsicmd)
|
|
break;
|
|
/* complete the orig cmd */
|
|
complete_scsi_command(cmdrsp, scsicmd);
|
|
} else if (cmdrsp->cmdtype == CMD_SCSITASKMGMT_TYPE) {
|
|
if (!del_scsipending_entry(virthbainfo,
|
|
(uintptr_t) cmdrsp->scsitaskmgmt.scsicmd))
|
|
break;
|
|
complete_taskmgmt_command(cmdrsp);
|
|
} else if (cmdrsp->cmdtype == CMD_NOTIFYGUEST_TYPE) {
|
|
/* The vHba pointer has no meaning in
|
|
* a Client/Guest Partition. Let's be
|
|
* safe and set it to NULL now. Do
|
|
* not use it here! */
|
|
cmdrsp->disknotify.v_hba = NULL;
|
|
process_disk_notify(shost, cmdrsp);
|
|
} else if (cmdrsp->cmdtype == CMD_VDISKMGMT_TYPE) {
|
|
if (!del_scsipending_entry(virthbainfo,
|
|
(uintptr_t) cmdrsp->vdiskmgmt.scsicmd))
|
|
break;
|
|
complete_vdiskmgmt_command(cmdrsp);
|
|
} else
|
|
LOGERR("Invalid cmdtype %d\n", cmdrsp->cmdtype);
|
|
/* cmdrsp is now available for reuse */
|
|
}
|
|
}
|
|
|
|
/* main function for the thread that waits for scsi commands to arrive
|
|
* in a specified queue
|
|
*/
|
|
static int
|
|
process_incoming_rsps(void *v)
|
|
{
|
|
struct virthba_info *virthbainfo = v;
|
|
struct chaninfo *dc = &virthbainfo->chinfo;
|
|
struct uiscmdrsp *cmdrsp = NULL;
|
|
const int SZ = sizeof(struct uiscmdrsp);
|
|
u64 mask;
|
|
unsigned long long rc1;
|
|
|
|
UIS_DAEMONIZE("vhba_incoming");
|
|
/* alloc once and reuse */
|
|
cmdrsp = kmalloc(SZ, GFP_ATOMIC);
|
|
if (cmdrsp == NULL) {
|
|
LOGERR("process_incoming_rsps ****FAILED to malloc - thread exiting\n");
|
|
complete_and_exit(&dc->threadinfo.has_stopped, 0);
|
|
return 0;
|
|
}
|
|
mask = ULTRA_CHANNEL_ENABLE_INTS;
|
|
while (1) {
|
|
wait_event_interruptible_timeout(virthbainfo->rsp_queue,
|
|
(atomic_read(&virthbainfo->interrupt_rcvd) == 1),
|
|
usecs_to_jiffies(rsltq_wait_usecs));
|
|
atomic_set(&virthbainfo->interrupt_rcvd, 0);
|
|
/* drain queue */
|
|
drain_queue(virthbainfo, dc, cmdrsp);
|
|
rc1 = uisqueue_interlocked_or(virthbainfo->flags_addr, mask);
|
|
if (dc->threadinfo.should_stop)
|
|
break;
|
|
}
|
|
|
|
kfree(cmdrsp);
|
|
|
|
DBGINF("exiting processing incoming rsps.\n");
|
|
complete_and_exit(&dc->threadinfo.has_stopped, 0);
|
|
}
|
|
|
|
/*****************************************************/
|
|
/* Debugfs filesystem functions */
|
|
/*****************************************************/
|
|
|
|
static ssize_t info_debugfs_read(struct file *file,
|
|
char __user *buf, size_t len, loff_t *offset)
|
|
{
|
|
ssize_t bytes_read = 0;
|
|
int str_pos = 0;
|
|
u64 phys_flags_addr;
|
|
int i;
|
|
struct virthba_info *virthbainfo;
|
|
char *vbuf;
|
|
|
|
if (len > MAX_BUF)
|
|
len = MAX_BUF;
|
|
vbuf = kzalloc(len, GFP_KERNEL);
|
|
if (!vbuf)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < VIRTHBASOPENMAX; i++) {
|
|
if (VirtHbasOpen[i].virthbainfo == NULL)
|
|
continue;
|
|
|
|
virthbainfo = VirtHbasOpen[i].virthbainfo;
|
|
|
|
str_pos += scnprintf(vbuf + str_pos,
|
|
len - str_pos, "MaxBuffLen:%u\n", MaxBuffLen);
|
|
|
|
str_pos += scnprintf(vbuf + str_pos, len - str_pos,
|
|
"\nvirthba result queue poll wait:%d usecs.\n",
|
|
rsltq_wait_usecs);
|
|
str_pos += scnprintf(vbuf + str_pos, len - str_pos,
|
|
"\ninterrupts_rcvd = %llu, interrupts_disabled = %llu\n",
|
|
virthbainfo->interrupts_rcvd,
|
|
virthbainfo->interrupts_disabled);
|
|
str_pos += scnprintf(vbuf + str_pos,
|
|
len - str_pos, "\ninterrupts_notme = %llu,\n",
|
|
virthbainfo->interrupts_notme);
|
|
phys_flags_addr = virt_to_phys((__force void *)
|
|
virthbainfo->flags_addr);
|
|
str_pos += scnprintf(vbuf + str_pos, len - str_pos,
|
|
"flags_addr = %p, phys_flags_addr=0x%016llx, FeatureFlags=%llu\n",
|
|
virthbainfo->flags_addr, phys_flags_addr,
|
|
(__le64)readq(virthbainfo->flags_addr));
|
|
str_pos += scnprintf(vbuf + str_pos,
|
|
len - str_pos, "acquire_failed_cnt:%llu\n",
|
|
virthbainfo->acquire_failed_cnt);
|
|
str_pos += scnprintf(vbuf + str_pos, len - str_pos, "\n");
|
|
}
|
|
|
|
bytes_read = simple_read_from_buffer(buf, len, offset, vbuf, str_pos);
|
|
kfree(vbuf);
|
|
return bytes_read;
|
|
}
|
|
|
|
static ssize_t enable_ints_write(struct file *file,
|
|
const char __user *buffer, size_t count, loff_t *ppos)
|
|
{
|
|
char buf[4];
|
|
int i, new_value;
|
|
struct virthba_info *virthbainfo;
|
|
|
|
u64 __iomem *Features_addr;
|
|
u64 mask;
|
|
|
|
if (count >= ARRAY_SIZE(buf))
|
|
return -EINVAL;
|
|
|
|
buf[count] = '\0';
|
|
if (copy_from_user(buf, buffer, count)) {
|
|
LOGERR("copy_from_user failed. buf<<%.*s>> count<<%lu>>\n",
|
|
(int) count, buf, count);
|
|
return -EFAULT;
|
|
}
|
|
|
|
i = kstrtoint(buf, 10 , &new_value);
|
|
|
|
if (i != 0) {
|
|
LOGERR("Failed to scan value for enable_ints, buf<<%.*s>>",
|
|
(int) count, buf);
|
|
return -EFAULT;
|
|
}
|
|
|
|
/* set all counts to new_value usually 0 */
|
|
for (i = 0; i < VIRTHBASOPENMAX; i++) {
|
|
if (VirtHbasOpen[i].virthbainfo != NULL) {
|
|
virthbainfo = VirtHbasOpen[i].virthbainfo;
|
|
Features_addr =
|
|
&virthbainfo->chinfo.queueinfo->chan->features;
|
|
if (new_value == 1) {
|
|
mask = ~(ULTRA_IO_CHANNEL_IS_POLLING |
|
|
ULTRA_IO_DRIVER_DISABLES_INTS);
|
|
uisqueue_interlocked_and(Features_addr, mask);
|
|
mask = ULTRA_IO_DRIVER_ENABLES_INTS;
|
|
uisqueue_interlocked_or(Features_addr, mask);
|
|
rsltq_wait_usecs = 4000000;
|
|
} else {
|
|
mask = ~(ULTRA_IO_DRIVER_ENABLES_INTS |
|
|
ULTRA_IO_DRIVER_DISABLES_INTS);
|
|
uisqueue_interlocked_and(Features_addr, mask);
|
|
mask = ULTRA_IO_CHANNEL_IS_POLLING;
|
|
uisqueue_interlocked_or(Features_addr, mask);
|
|
rsltq_wait_usecs = 4000;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* As per VirtpciFunc returns 1 for success and 0 for failure */
|
|
static int
|
|
virthba_serverup(struct virtpci_dev *virtpcidev)
|
|
{
|
|
struct virthba_info *virthbainfo =
|
|
(struct virthba_info *) ((struct Scsi_Host *) virtpcidev->scsi.
|
|
scsihost)->hostdata;
|
|
|
|
DBGINF("virtpcidev bus_no<<%d>>devNo<<%d>>", virtpcidev->bus_no,
|
|
virtpcidev->device_no);
|
|
|
|
if (!virthbainfo->serverdown) {
|
|
DBGINF("Server up message received while server is already up.\n");
|
|
return 1;
|
|
}
|
|
if (virthbainfo->serverchangingstate) {
|
|
LOGERR("Server already processing change state message\n");
|
|
return 0;
|
|
}
|
|
|
|
virthbainfo->serverchangingstate = true;
|
|
/* Must transition channel to ATTACHED state BEFORE we
|
|
* can start using the device again
|
|
*/
|
|
SPAR_CHANNEL_CLIENT_TRANSITION(virthbainfo->chinfo.queueinfo->chan,
|
|
dev_name(&virtpcidev->generic_dev),
|
|
CHANNELCLI_ATTACHED, NULL);
|
|
|
|
/* Start Processing the IOVM Response Queue Again */
|
|
if (!uisthread_start(&virthbainfo->chinfo.threadinfo,
|
|
process_incoming_rsps,
|
|
virthbainfo, "vhba_incoming")) {
|
|
LOGERR("uisthread_start rsp ****FAILED\n");
|
|
return 0;
|
|
}
|
|
virthbainfo->serverdown = false;
|
|
virthbainfo->serverchangingstate = false;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
virthba_serverdown_complete(struct work_struct *work)
|
|
{
|
|
struct virthba_info *virthbainfo;
|
|
struct virtpci_dev *virtpcidev;
|
|
int i;
|
|
struct scsipending *pendingdel = NULL;
|
|
struct scsi_cmnd *scsicmd = NULL;
|
|
struct uiscmdrsp *cmdrsp;
|
|
unsigned long flags;
|
|
|
|
virthbainfo = container_of(work, struct virthba_info,
|
|
serverdown_completion);
|
|
|
|
/* Stop Using the IOVM Response Queue (queue should be drained
|
|
* by the end)
|
|
*/
|
|
uisthread_stop(&virthbainfo->chinfo.threadinfo);
|
|
|
|
/* Fail Commands that weren't completed */
|
|
spin_lock_irqsave(&virthbainfo->privlock, flags);
|
|
for (i = 0; i < MAX_PENDING_REQUESTS; i++) {
|
|
pendingdel = &(virthbainfo->pending[i]);
|
|
switch (pendingdel->cmdtype) {
|
|
case CMD_SCSI_TYPE:
|
|
scsicmd = (struct scsi_cmnd *) pendingdel->sent;
|
|
scsicmd->result = (DID_RESET << 16);
|
|
if (scsicmd->scsi_done)
|
|
scsicmd->scsi_done(scsicmd);
|
|
break;
|
|
case CMD_SCSITASKMGMT_TYPE:
|
|
cmdrsp = (struct uiscmdrsp *) pendingdel->sent;
|
|
DBGINF("cmdrsp=0x%x, notify=0x%x\n", cmdrsp,
|
|
cmdrsp->scsitaskmgmt.notify);
|
|
*(int *) cmdrsp->scsitaskmgmt.notifyresult =
|
|
TASK_MGMT_FAILED;
|
|
wake_up_all((wait_queue_head_t *)
|
|
cmdrsp->scsitaskmgmt.notify);
|
|
break;
|
|
case CMD_VDISKMGMT_TYPE:
|
|
cmdrsp = (struct uiscmdrsp *) pendingdel->sent;
|
|
*(int *) cmdrsp->vdiskmgmt.notifyresult =
|
|
VDISK_MGMT_FAILED;
|
|
wake_up_all((wait_queue_head_t *)
|
|
cmdrsp->vdiskmgmt.notify);
|
|
break;
|
|
default:
|
|
if (pendingdel->sent != NULL)
|
|
LOGERR("Unknown command type: 0x%x. Only freeing list structure.\n",
|
|
pendingdel->cmdtype);
|
|
}
|
|
pendingdel->cmdtype = 0;
|
|
pendingdel->sent = NULL;
|
|
}
|
|
spin_unlock_irqrestore(&virthbainfo->privlock, flags);
|
|
|
|
virtpcidev = virthbainfo->virtpcidev;
|
|
|
|
DBGINF("virtpcidev bus_no<<%d>>devNo<<%d>>", virtpcidev->bus_no,
|
|
virtpcidev->device_no);
|
|
virthbainfo->serverdown = true;
|
|
virthbainfo->serverchangingstate = false;
|
|
/* Return the ServerDown response to Command */
|
|
visorchipset_device_pause_response(virtpcidev->bus_no,
|
|
virtpcidev->device_no, 0);
|
|
}
|
|
|
|
/* As per VirtpciFunc returns 1 for success and 0 for failure */
|
|
static int
|
|
virthba_serverdown(struct virtpci_dev *virtpcidev, u32 state)
|
|
{
|
|
struct virthba_info *virthbainfo =
|
|
(struct virthba_info *) ((struct Scsi_Host *) virtpcidev->scsi.
|
|
scsihost)->hostdata;
|
|
|
|
DBGINF("virthba_serverdown");
|
|
DBGINF("virtpcidev bus_no<<%d>>devNo<<%d>>", virtpcidev->bus_no,
|
|
virtpcidev->device_no);
|
|
|
|
if (!virthbainfo->serverdown && !virthbainfo->serverchangingstate) {
|
|
virthbainfo->serverchangingstate = true;
|
|
queue_work(virthba_serverdown_workqueue,
|
|
&virthbainfo->serverdown_completion);
|
|
} else if (virthbainfo->serverchangingstate) {
|
|
LOGERR("Server already processing change state message\n");
|
|
return 0;
|
|
} else
|
|
LOGERR("Server already down, but another server down message received.");
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*****************************************************/
|
|
/* Module Init & Exit functions */
|
|
/*****************************************************/
|
|
|
|
static int __init
|
|
virthba_parse_line(char *str)
|
|
{
|
|
DBGINF("In virthba_parse_line %s\n", str);
|
|
return 1;
|
|
}
|
|
|
|
static void __init
|
|
virthba_parse_options(char *line)
|
|
{
|
|
char *next = line;
|
|
|
|
POSTCODE_LINUX_2(VHBA_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
|
|
if (line == NULL || !*line)
|
|
return;
|
|
while ((line = next) != NULL) {
|
|
next = strchr(line, ' ');
|
|
if (next != NULL)
|
|
*next++ = 0;
|
|
if (!virthba_parse_line(line))
|
|
DBGINF("Unknown option '%s'\n", line);
|
|
}
|
|
|
|
POSTCODE_LINUX_2(VHBA_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO);
|
|
}
|
|
|
|
static int __init
|
|
virthba_mod_init(void)
|
|
{
|
|
int error;
|
|
int i;
|
|
|
|
if (!unisys_spar_platform)
|
|
return -ENODEV;
|
|
|
|
LOGINF("Entering virthba_mod_init...\n");
|
|
|
|
POSTCODE_LINUX_2(VHBA_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO);
|
|
virthba_parse_options(virthba_options);
|
|
|
|
error = virtpci_register_driver(&virthba_driver);
|
|
if (error < 0) {
|
|
LOGERR("register ****FAILED 0x%x\n", error);
|
|
POSTCODE_LINUX_3(VHBA_CREATE_FAILURE_PC, error,
|
|
POSTCODE_SEVERITY_ERR);
|
|
} else {
|
|
|
|
/* create the debugfs directories and entries */
|
|
virthba_debugfs_dir = debugfs_create_dir("virthba", NULL);
|
|
debugfs_create_file("info", S_IRUSR, virthba_debugfs_dir,
|
|
NULL, &debugfs_info_fops);
|
|
debugfs_create_u32("rqwait_usecs", S_IRUSR | S_IWUSR,
|
|
virthba_debugfs_dir, &rsltq_wait_usecs);
|
|
debugfs_create_file("enable_ints", S_IWUSR,
|
|
virthba_debugfs_dir, NULL,
|
|
&debugfs_enable_ints_fops);
|
|
/* Initialize DARWorkQ */
|
|
INIT_WORK(&DARWorkQ, doDiskAddRemove);
|
|
spin_lock_init(&DARWorkQLock);
|
|
|
|
/* clear out array */
|
|
for (i = 0; i < VIRTHBASOPENMAX; i++)
|
|
VirtHbasOpen[i].virthbainfo = NULL;
|
|
/* Initialize the serverdown workqueue */
|
|
virthba_serverdown_workqueue =
|
|
create_singlethread_workqueue("virthba_serverdown");
|
|
if (virthba_serverdown_workqueue == NULL) {
|
|
LOGERR("**** FAILED virthba_serverdown_workqueue creation\n");
|
|
POSTCODE_LINUX_2(VHBA_CREATE_FAILURE_PC,
|
|
POSTCODE_SEVERITY_ERR);
|
|
error = -1;
|
|
}
|
|
}
|
|
|
|
POSTCODE_LINUX_2(VHBA_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO);
|
|
LOGINF("Leaving virthba_mod_init\n");
|
|
return error;
|
|
}
|
|
|
|
static ssize_t
|
|
virthba_acquire_lun(struct device *cdev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct uisscsi_dest vdest;
|
|
struct Scsi_Host *shost = class_to_shost(cdev);
|
|
int i;
|
|
|
|
i = sscanf(buf, "%d-%d-%d", &vdest.channel, &vdest.id, &vdest.lun);
|
|
if (i != 3)
|
|
return i;
|
|
|
|
return forward_vdiskmgmt_command(VDISK_MGMT_ACQUIRE, shost, &vdest);
|
|
}
|
|
|
|
static ssize_t
|
|
virthba_release_lun(struct device *cdev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct uisscsi_dest vdest;
|
|
struct Scsi_Host *shost = class_to_shost(cdev);
|
|
int i;
|
|
|
|
i = sscanf(buf, "%d-%d-%d", &vdest.channel, &vdest.id, &vdest.lun);
|
|
if (i != 3)
|
|
return i;
|
|
|
|
return forward_vdiskmgmt_command(VDISK_MGMT_RELEASE, shost, &vdest);
|
|
}
|
|
|
|
#define CLASS_DEVICE_ATTR(_name, _mode, _show, _store) \
|
|
struct device_attribute class_device_attr_##_name = \
|
|
__ATTR(_name, _mode, _show, _store)
|
|
|
|
static CLASS_DEVICE_ATTR(acquire_lun, S_IWUSR, NULL, virthba_acquire_lun);
|
|
static CLASS_DEVICE_ATTR(release_lun, S_IWUSR, NULL, virthba_release_lun);
|
|
|
|
static DEVICE_ATTRIBUTE *virthba_shost_attrs[] = {
|
|
&class_device_attr_acquire_lun,
|
|
&class_device_attr_release_lun,
|
|
NULL
|
|
};
|
|
|
|
static void __exit
|
|
virthba_mod_exit(void)
|
|
{
|
|
LOGINF("entering virthba_mod_exit...\n");
|
|
|
|
virtpci_unregister_driver(&virthba_driver);
|
|
/* unregister is going to call virthba_remove */
|
|
/* destroy serverdown completion workqueue */
|
|
if (virthba_serverdown_workqueue) {
|
|
destroy_workqueue(virthba_serverdown_workqueue);
|
|
virthba_serverdown_workqueue = NULL;
|
|
}
|
|
|
|
debugfs_remove_recursive(virthba_debugfs_dir);
|
|
LOGINF("Leaving virthba_mod_exit\n");
|
|
|
|
}
|
|
|
|
/* specify function to be run at module insertion time */
|
|
module_init(virthba_mod_init);
|
|
|
|
/* specify function to be run when module is removed */
|
|
module_exit(virthba_mod_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Usha Srinivasan");
|
|
MODULE_ALIAS("uisvirthba");
|
|
/* this is extracted during depmod and kept in modules.dep */
|
|
/* module parameter */
|
|
module_param(virthba_options, charp, S_IRUGO);
|