[PATCH] staging: unisys: Add s-Par visorhba

Benjamin Romer benjamin.romer at unisys.com
Mon Jul 13 18:38:23 UTC 2015


From: David Kershner <david.kershner at unisys.com>

This driver create a host bus adapter device when s-Par sends a
device create message to create a storage adapter on the visorbus.
When the message is received by visorbus, the visorhba_probe function
is called and the hba device is created and managed by the visorhba
driver.

Signed-off-by: Erik Arfvidson <erik.arfvidson at unisys.com>
Signed-off-by: Benjamin Romer <benjamin.romer at unisys.com>
Signed-off-by: David Kershner <david.kershner at unisys.com>
---
 drivers/staging/unisys/Kconfig                  |    1 +
 drivers/staging/unisys/Makefile                 |    1 +
 drivers/staging/unisys/include/iochannel.h      |    4 +
 drivers/staging/unisys/visorhba/Kconfig         |   14 +
 drivers/staging/unisys/visorhba/Makefile        |   10 +
 drivers/staging/unisys/visorhba/visorhba_main.c | 1372 +++++++++++++++++++++++
 6 files changed, 1402 insertions(+)
 create mode 100644 drivers/staging/unisys/visorhba/Kconfig
 create mode 100644 drivers/staging/unisys/visorhba/Makefile
 create mode 100644 drivers/staging/unisys/visorhba/visorhba_main.c

diff --git a/drivers/staging/unisys/Kconfig b/drivers/staging/unisys/Kconfig
index ca850a8..5cbb791 100644
--- a/drivers/staging/unisys/Kconfig
+++ b/drivers/staging/unisys/Kconfig
@@ -14,5 +14,6 @@ if UNISYSSPAR
 source "drivers/staging/unisys/visorbus/Kconfig"
 source "drivers/staging/unisys/visornic/Kconfig"
 source "drivers/staging/unisys/visorhid/Kconfig"
+source "drivers/staging/unisys/visorhba/Kconfig"
 
 endif # UNISYSSPAR
diff --git a/drivers/staging/unisys/Makefile b/drivers/staging/unisys/Makefile
index d071094..79c9036 100644
--- a/drivers/staging/unisys/Makefile
+++ b/drivers/staging/unisys/Makefile
@@ -4,3 +4,4 @@
 obj-$(CONFIG_UNISYS_VISORBUS)		+= visorbus/
 obj-$(CONFIG_UNISYS_VISORNIC)		+= visornic/
 obj-$(CONFIG_UNISYS_VISORHID)          += visorhid/
+obj-$(CONFIG_UNISYS_VISORHBA)		+= visorhba/
diff --git a/drivers/staging/unisys/include/iochannel.h b/drivers/staging/unisys/include/iochannel.h
index a559812..1cca0fb 100644
--- a/drivers/staging/unisys/include/iochannel.h
+++ b/drivers/staging/unisys/include/iochannel.h
@@ -147,6 +147,10 @@ struct phys_info {
 	u16 pi_len;
 } __packed;
 
+#define MIN_NUMSIGNALS 64
+
+/* structs with pragma pack  */
+
 struct guest_phys_info {
 	u64 address;
 	u64 length;
diff --git a/drivers/staging/unisys/visorhba/Kconfig b/drivers/staging/unisys/visorhba/Kconfig
new file mode 100644
index 0000000..241d803
--- /dev/null
+++ b/drivers/staging/unisys/visorhba/Kconfig
@@ -0,0 +1,14 @@
+#
+# Unisys visorhba configuration
+#
+
+config UNISYS_VISORHBA
+	tristate "Unisys visorhba driver"
+	depends on UNISYSSPAR && UNISYS_VISORBUS && SCSI
+	---help---
+	The Unisys visorhba driver provides support for s-Par HBA
+	devices exposed on the s-Par visorbus. When a message is sent
+	to visorbus to create a HBA device, the probe function of
+	visorhba is called to create the scsi device.
+	If you say Y here, you will enable the Unisys visorhba driver.
+
diff --git a/drivers/staging/unisys/visorhba/Makefile b/drivers/staging/unisys/visorhba/Makefile
new file mode 100644
index 0000000..a8a8e0e
--- /dev/null
+++ b/drivers/staging/unisys/visorhba/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for Unisys channel
+#
+
+obj-$(CONFIG_UNISYS_VISORHBA)	+= visorhba.o
+
+visorhba-y := visorhba_main.o
+
+ccflags-y += -Idrivers/staging/unisys/include
+
diff --git a/drivers/staging/unisys/visorhba/visorhba_main.c b/drivers/staging/unisys/visorhba/visorhba_main.c
new file mode 100644
index 0000000..04fe110
--- /dev/null
+++ b/drivers/staging/unisys/visorhba/visorhba_main.c
@@ -0,0 +1,1372 @@
+/* Copyright (c) 2012 - 2015 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.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/skbuff.h>
+#include <linux/kthread.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+
+#include "visorbus.h"
+#include "iochannel.h"
+
+#define VERSION "1.0.0.0"
+
+/* The Send and Receive Buffers of the IO Queue may both be full */
+#define MAX_PENDING_REQUESTS (MIN_NUMSIGNALS * 2)
+
+#define VISORHBA_ERROR_COUNT 30
+#define IOS_ERROR_THRESHOLD 1000
+
+#define MAXDEVICES 1024
+#define VISORHBAOPENMAX 1
+
+/* MAX_BUF = 6 lines x 10 MAXVHBA x 80 characters
+ *         = 4800 bytes ~ 2^13 = 8192 bytes
+ */
+#define MAX_BUF 8192
+#define MAX_NAME_LENGTH 99
+
+static LIST_HEAD(list_all_devices);
+static DEFINE_SPINLOCK(lock_all_devices);
+static spinlock_t dev_num_pool_lock;	/* Lock to modify dev_num_pool */
+static void *dev_num_pool;		/* pool to grab device numbers from */
+
+static int visorhba_queue_command_lck(struct scsi_cmnd *scsicmd,
+				      void (*visorhba_cmnd_done)
+					    (struct scsi_cmnd *));
+#ifdef DEF_SCSI_QCMD
+static DEF_SCSI_QCMD(visorhba_queue_command)
+#else
+#define visorhba_queue_command visorhba_queue_command_lck
+#endif
+static int visorhba_probe(struct visor_device *dev);
+static void visorhba_remove(struct visor_device *dev);
+static int visorhba_pause(struct visor_device *dev,
+			  visorbus_state_complete_func complete_func);
+static int visorhba_resume(struct visor_device *dev,
+			   visorbus_state_complete_func complete_func);
+
+static ssize_t info_debugfs_read(struct file *file, char __user *buf,
+				 size_t len, loff_t *offset);
+static struct dentry *visorhba_debugfs_dir;
+static const struct file_operations debugfs_info_fops = {
+	.read = info_debugfs_read,
+};
+
+/* GUIDS for HBA channel type supported by this driver */
+static struct visor_channeltype_descriptor visorhba_channel_types[] = {
+	/* Note that the only channel type we expect to be reported by the
+	 * bus driver is the SPAR_VHBA channel.
+	 */
+	{ SPAR_VHBA_CHANNEL_PROTOCOL_UUID, "sparvhba" },
+	{ NULL_UUID_LE, NULL }
+};
+
+static struct workqueue_struct *visorhba_serverdown_workqueue;
+
+/* This is used to tell the visor bus driver which types of visor devices
+ * we support, and what functions to call when a visor device that we support
+ * is attached or removed.
+ */
+static struct visor_driver visorhba_driver = {
+	.name = "visorhba",
+	.version = VERSION,
+	.vertag = NULL,
+	.owner = THIS_MODULE,
+	.channel_types = visorhba_channel_types,
+	.probe = visorhba_probe,
+	.remove = visorhba_remove,
+	.pause = visorhba_pause,
+	.resume = visorhba_resume,
+	.channel_interrupt = NULL,
+};
+
+struct visor_thread_info {
+	struct task_struct *task;
+	struct completion has_stopped;
+	int id;
+};
+
+struct visordisk_info {
+	u32 valid;
+	u32 channel, id, lun;	/* Disk Path */
+	atomic_t ios_threshold;
+	atomic_t error_count;
+	struct visordisk_info *next;
+};
+
+struct scsipending {
+	char cmdtype;		/* Type of pointer that is being stored */
+	void *sent;		/* The Data being tracked */
+};
+
+/* Work Data for dar_work_queue */
+struct diskaddremove {
+	u8 add;			/* 0-remove, 1-add */
+	struct Scsi_Host *shost; /* Scsi Host for this visorhba instance */
+	u32 channel, id, lun;	/* Disk Path */
+	struct diskaddremove *next;
+};
+
+/* Each scsi_host has a host_data area that contains this struct. */
+struct visorhba_devdata {
+	struct Scsi_Host *scsihost;
+	struct visor_device *dev;
+	struct list_head dev_info_list;
+	/* Tracks the requests that have been forwarded to
+	 * the IOVM and haven't returned yet
+	 */
+	struct scsipending pending[MAX_PENDING_REQUESTS];
+	/* Start search for next pending free slot here */
+	unsigned int nextinsert;
+	spinlock_t privlock; /* lock to protect data in devdata */
+	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 visordisk_info head;
+	unsigned int max_buff_len;
+	int devnum;
+	char name[MAX_NAME_LENGTH];
+	struct kref kref;
+	struct visor_thread_info threadinfo;
+	struct list_head list_all;
+	int thread_wait_ms;
+};
+
+struct visorhba_devices_open {
+	struct visorhba_devdata *devdata;
+};
+
+static struct visorhba_devices_open visorhbas_open[VISORHBAOPENMAX];
+
+/**
+ *	visor_thread_start - starts a thread for the device
+ *	@thrinfo: The thread to start
+ *	@threadfn: Function the thread starts
+ *	@thrcontext: Context to pass to the thread, i.e. devdata
+ *	@name: string describing name of thread
+ *
+ *	Starts a thread for the device.
+ *
+ *	Return 0 on success;
+ */
+static int visor_thread_start(struct visor_thread_info *thrinfo,
+			      int (*threadfn)(void *),
+			      void *thrcontext, char *name)
+{
+	/* used to stop the thread */
+	init_completion(&thrinfo->has_stopped);
+	thrinfo->task = kthread_run(threadfn, thrcontext, name);
+	if (IS_ERR(thrinfo->task)) {
+		thrinfo->id = 0;
+		return -1;
+	}
+	thrinfo->id = thrinfo->task->pid;
+	return 0;
+}
+
+/**
+ *	visor_thread_stop = stop a thread for the device
+ *	@thrinfo: The thread to stop
+ *
+ *	Stop the thread and wait for completion for a minute
+ *	Returns void.
+ */
+static void visor_thread_stop(struct visor_thread_info *thrinfo)
+{
+	if (!thrinfo->id)
+		return;
+	kthread_stop(thrinfo->task);
+	/* give up if the thread has NOT died in 1 second */
+	if (wait_for_completion_timeout(&thrinfo->has_stopped,
+					msecs_to_jiffies(1000)))
+		thrinfo->id = 0;
+}
+
+/**
+ *	add_scsipending_entry - save off io command that is pending in
+ *				Service Partition
+ *	@devdata: Pointer to devdata
+ *	@cmdtype: Specifies the type of command pending
+ *	@new:	The command to be saved
+ *
+ *	Saves off the io command that is being handled by the Service
+ *	Partition so that it can be handled when it completes.
+ *	Returns insert_location where entry was added,
+ *	SCSI_MLQUEUE_DEVICE_BUSYif it can't
+ */
+static int add_scsipending_entry(struct visorhba_devdata *devdata,
+				 char cmdtype, void *new)
+{
+	unsigned long flags;
+	int insert_location;
+
+	spin_lock_irqsave(&devdata->privlock, flags);
+	insert_location = devdata->nextinsert;
+	while (devdata->pending[insert_location].sent) {
+		insert_location = (insert_location + 1) % MAX_PENDING_REQUESTS;
+		if (insert_location == (int)devdata->nextinsert) {
+			spin_unlock_irqrestore(&devdata->privlock, flags);
+			return SCSI_MLQUEUE_DEVICE_BUSY;
+		}
+	}
+
+	devdata->pending[insert_location].cmdtype = cmdtype;
+	devdata->pending[insert_location].sent = new;
+	devdata->nextinsert = (insert_location + 1) % MAX_PENDING_REQUESTS;
+	spin_unlock_irqrestore(&devdata->privlock, flags);
+
+	return insert_location;
+}
+
+/**
+ *	del_scsipending_enty - removes an entry from the pending array
+ *	@devdata: Device holding the pending array
+ *	@del: Entry to remove
+ *
+ *	Removes the entry pointed at by del and returns it.
+ *	Returns the scsipending entry pointed at
+ */
+static void *del_scsipending_ent(struct visorhba_devdata *devdata,
+				 uintptr_t del)
+{
+	unsigned long flags;
+	void *sent = NULL;
+
+	if (del < MAX_PENDING_REQUESTS) {
+		spin_lock_irqsave(&devdata->privlock, flags);
+		sent = devdata->pending[del].sent;
+
+		devdata->pending[del].cmdtype = 0;
+		devdata->pending[del].sent = NULL;
+		spin_unlock_irqrestore(&devdata->privlock, flags);
+	}
+
+	return sent;
+}
+
+/**
+ *	forward_taskmgmt_command - send taskmegmt command to the Service
+ *				   Partition
+ *	@tasktype: Type of taskmgmt command
+ *	@scsidev: Scsidev that issued command
+ *
+ *	Create a cmdrsp packet and send it to the Serivce Partition
+ *	that will service this request.
+ *	Returns whether the command was queued successfully or not.
+ */
+static int forward_taskmgmt_command(enum task_mgmt_types tasktype,
+				    struct scsi_device *scsidev)
+{
+	struct uiscmdrsp *cmdrsp;
+	struct visorhba_devdata *devdata =
+		(struct visorhba_devdata *)scsidev->host->hostdata;
+	int notifyresult = 0xffff;
+	wait_queue_head_t notifyevent;
+	int scsicmd_id = 0;
+	int err = 0;
+
+	if (devdata->serverdown || devdata->serverchangingstate)
+		return -EINVAL;
+
+	cmdrsp = kzalloc(sizeof(*cmdrsp), GFP_ATOMIC);
+	if (!cmdrsp)
+		return -ENOMEM;
+
+	init_waitqueue_head(&notifyevent);
+
+	/* issue TASK_MGMT_ABORT_TASK */
+	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;
+	scsicmd_id = add_scsipending_entry(devdata, CMD_SCSITASKMGMT_TYPE,
+					   (void *)cmdrsp);
+	if (scsicmd_id < 0) {
+		err = scsicmd_id;
+		goto cleanup;
+	}
+	cmdrsp->scsitaskmgmt.scsicmd = (void *)(uintptr_t)scsicmd_id;
+
+	if (!visorchannel_signalinsert(devdata->dev->visorchannel,
+				       IOCHAN_TO_IOPART,
+				       cmdrsp)) {
+		err = SCSI_MLQUEUE_DEVICE_BUSY;
+		goto cleanup;
+	}
+
+	wait_event(notifyevent, notifyresult != 0xffff);
+	kfree(cmdrsp);
+	return SUCCESS;
+
+cleanup:
+	kfree(cmdrsp);
+	return err;
+}
+
+/**
+ *	visorhba_abort_handler - Send TASK_MGMT_ABORT_TASK
+ *	@scsicmd: The scsicmd that needs aborted
+ *
+ *	Returns SUCCESS, waits forever if can't insert
+ *
+ */
+static int visorhba_abort_handler(struct scsi_cmnd *scsicmd)
+{
+	/* issue TASK_MGMT_ABORT_TASK */
+	struct scsi_device *scsidev;
+	struct visordisk_info *vdisk;
+	struct visorhba_devdata *devdata;
+
+	scsidev = scsicmd->device;
+	devdata = (struct visorhba_devdata *)scsidev->host->hostdata;
+	for (vdisk = &devdata->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) <
+			    VISORHBA_ERROR_COUNT)
+				atomic_inc(&vdisk->error_count);
+			else
+				atomic_set(&vdisk->ios_threshold,
+					   IOS_ERROR_THRESHOLD);
+		}
+	}
+	return forward_taskmgmt_command(TASK_MGMT_ABORT_TASK, scsicmd->device);
+}
+
+/**
+ *	visorhba_device_reset_handler - Send TASK_MGMT_LUN_RESET
+ *	@scsicmd: The scsicmd that needs aborted
+ *
+ *	Returns SUCCESS, waits forever if can't insert
+ */
+static int visorhba_device_reset_handler(struct scsi_cmnd *scsicmd)
+{
+	/* issue TASK_MGMT_LUN_RESET */
+	struct scsi_device *scsidev;
+	struct visordisk_info *vdisk;
+	struct visorhba_devdata *devdata;
+
+	scsidev = scsicmd->device;
+	devdata = (struct visorhba_devdata *)scsidev->host->hostdata;
+	for (vdisk = &devdata->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) <
+			    VISORHBA_ERROR_COUNT) {
+				atomic_inc(&vdisk->error_count);
+			} else {
+				atomic_set(&vdisk->ios_threshold,
+					   IOS_ERROR_THRESHOLD);
+			}
+		}
+	}
+	return forward_taskmgmt_command(TASK_MGMT_LUN_RESET, scsicmd->device);
+}
+
+/**
+ *	visorhba_bus_reset_handler - Send TASK_MGMT_TARGET_RESET for each
+ *				     target on the bus
+ *	@scsicmd: The scsicmd that needs aborted
+ *
+ *	Returns SUCCESS
+ */
+static int visorhba_bus_reset_handler(struct scsi_cmnd *scsicmd)
+{
+	struct scsi_device *scsidev;
+	struct visordisk_info *vdisk;
+	struct visorhba_devdata *devdata;
+
+	scsidev = scsicmd->device;
+	devdata = (struct visorhba_devdata *)scsidev->host->hostdata;
+	for (vdisk = &devdata->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) <
+			    VISORHBA_ERROR_COUNT)
+				atomic_inc(&vdisk->error_count);
+			else
+				atomic_set(&vdisk->ios_threshold,
+					   IOS_ERROR_THRESHOLD);
+		}
+	}
+	return forward_taskmgmt_command(TASK_MGMT_BUS_RESET, scsicmd->device);
+}
+
+/**
+ *	visorhba_host_reset_handler - Not supported
+ *	@scsicmd: The scsicmd that needs aborted
+ *
+ *	Not supported, return SUCCESS
+ *	Returns SUCCESS
+ */
+static int
+visorhba_host_reset_handler(struct scsi_cmnd *scsicmd)
+{
+	/* issue TASK_MGMT_TARGET_RESET for each target on each bus for host */
+	return SUCCESS;
+}
+
+/**
+ *	visorhba_get_info
+ *	@shp: Scsi host that is requesting information
+ *
+ *	Returns string with info for visorhba, now just version
+ */
+static const char *visorhba_get_info(struct Scsi_Host *shp)
+{
+	/* Return version string */
+	return "Version " VERSION "\n";
+}
+
+/**
+ *	visorhba_queue_command_lck -- queues command to the Service Partition
+ *	@scsicmd: Command to be queued
+ *	@vsiorhba_cmnd_done: Done command to call when scsicmd is returned
+ *
+ *	Queues to scsicmd to the ServicePartition after converting it to a
+ *	uiscmdrsp structure.
+ *
+ *	Returns success if queued to the Service Partition, otherwise
+ *	failure.
+ */
+static int
+visorhba_queue_command_lck(struct scsi_cmnd *scsicmd,
+			   void (*visorhba_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 visorhba_devdata *devdata =
+		(struct visorhba_devdata *)scsihost->hostdata;
+	struct scatterlist *sg = NULL;
+	struct scatterlist *sglist = NULL;
+	int err = 0;
+
+	if (devdata->serverdown || devdata->serverchangingstate)
+		return SCSI_MLQUEUE_DEVICE_BUSY;
+
+	cmdrsp = kzalloc(sizeof(*cmdrsp), GFP_ATOMIC);
+	if (!cmdrsp)
+		return -ENOMEM;
+
+	/* 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(devdata, CMD_SCSI_TYPE, (void *)scsicmd);
+	if (insert_location != -1) {
+		cmdrsp->scsi.scsicmd = (void *)(uintptr_t)insert_location;
+	} else {
+		kfree(cmdrsp);
+		return SCSI_MLQUEUE_DEVICE_BUSY;
+	}
+	/* save done function that we have call when cmd is complete */
+	scsicmd->scsi_done = visorhba_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;
+	if (!memcpy(cmdrsp->scsi.cmnd, cdb, MAX_CMND_SIZE)) {
+		err = -ENOMEM;
+		goto fail;
+	}
+
+	cmdrsp->scsi.bufflen = scsi_bufflen(scsicmd);
+
+	/* keep track of the max buffer length so far. */
+	if (cmdrsp->scsi.bufflen > devdata->max_buff_len)
+		devdata->max_buff_len = cmdrsp->scsi.bufflen;
+
+	if (scsi_sg_count(scsicmd) > MAX_PHYS_INFO) {
+		err = -EINVAL;
+		goto fail;
+	}
+
+	/* convert buffer to phys information */
+	if ((scsi_sg_count(scsicmd) == 0) && (scsi_bufflen(scsicmd) > 0)) {
+		BUG_ON(scsi_sg_count(scsicmd) == 0);
+	} else {
+		/* buffer is scatterlist - copy it out */
+		sglist = scsi_sglist(scsicmd);
+
+		for_each_sg(sglist, sg, scsi_sg_count(scsicmd), i) {
+			cmdrsp->scsi.gpi_list[i].address = sg_phys(sg);
+			cmdrsp->scsi.gpi_list[i].length = sg->length;
+		}
+		cmdrsp->scsi.guest_phys_entries = scsi_sg_count(scsicmd);
+	}
+
+	op = cdb[0];
+	if (!visorchannel_signalinsert(devdata->dev->visorchannel,
+				       IOCHAN_TO_IOPART,
+				       cmdrsp)) {
+		/* queue must be full and we aren't going to wait */
+		err = SCSI_MLQUEUE_DEVICE_BUSY;
+		goto fail;
+	}
+	kfree(cmdrsp);
+	return 0;
+fail:
+	del_scsipending_ent(devdata, (uintptr_t)insert_location);
+	kfree(cmdrsp);
+	return err;
+}
+
+/**
+ *	visorhba_slave_alloc - called when new disk is discovered
+ *	@scsidev: New disk
+ *
+ *	Create a new visordisk_info structure and add it to our
+ *	list of vdisks.
+ *
+ *	Returns success when created, otherwise error.
+ */
+static int visorhba_slave_alloc(struct scsi_device *scsidev)
+{
+	/* this is called by the midlayer before scan for new devices --
+	 * LLD can alloc any struct & do init if needed.
+	 */
+	struct visordisk_info *vdisk;
+	struct visordisk_info *tmpvdisk;
+	struct visorhba_devdata *devdata;
+	struct Scsi_Host *scsihost = (struct Scsi_Host *)scsidev->host;
+
+	devdata = (struct visorhba_devdata *)scsihost->hostdata;
+	if (!devdata)
+		return 0; /* even though we errored, treat as success */
+
+	for (vdisk = &devdata->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; /* already allocated return success */
+	}
+	tmpvdisk = kzalloc(sizeof(*tmpvdisk), GFP_ATOMIC);
+	if (!tmpvdisk)
+		return -ENOMEM;
+
+	tmpvdisk->channel = scsidev->channel;
+	tmpvdisk->id = scsidev->id;
+	tmpvdisk->lun = scsidev->lun;
+	vdisk->next = tmpvdisk;
+	return 0;
+}
+
+/**
+ *	visorhba_slave_destroy - disk is going away
+ *	@scsidev: scsi device going away
+ *
+ *	Disk is going away, clean up resources.
+ *	Returns void.
+ */
+static void visorhba_slave_destroy(struct scsi_device *scsidev)
+{
+	/* midlevel calls this after device has been quiesced and
+	 * before it is to be deleted.
+	 */
+	struct visordisk_info *vdisk, *delvdisk;
+	struct visorhba_devdata *devdata;
+	struct Scsi_Host *scsihost = (struct Scsi_Host *)scsidev->host;
+
+	devdata = (struct visorhba_devdata *)scsihost->hostdata;
+	for (vdisk = &devdata->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 = delvdisk->next;
+			kfree(delvdisk);
+			return;
+		}
+	}
+}
+
+static struct scsi_host_template visorhba_driver_template = {
+	.name = "Unisys Visor HBA",
+	.info = visorhba_get_info,
+	.queuecommand = visorhba_queue_command,
+	.eh_abort_handler = visorhba_abort_handler,
+	.eh_device_reset_handler = visorhba_device_reset_handler,
+	.eh_bus_reset_handler = visorhba_bus_reset_handler,
+	.eh_host_reset_handler = visorhba_host_reset_handler,
+	.shost_attrs = NULL,
+#define visorhba_MAX_CMNDS 128
+	.can_queue = visorhba_MAX_CMNDS,
+	.sg_tablesize = 64,
+	.this_id = -1,
+	.slave_alloc = visorhba_slave_alloc,
+	.slave_destroy = visorhba_slave_destroy,
+	.use_clustering = ENABLE_CLUSTERING,
+};
+
+/**
+ *	info_debugfs_read - debugfs interface to dump visorhba states
+ *	@file: Debug file
+ *	@buf: buffer to send back to user
+ *	@len: len that can be written to buf
+ *	@offset: offset into buf
+ *
+ *	Dumps information about the visorhba driver and devices
+ *	TODO: Make this per vhba
+ *	Returns bytes_read
+ */
+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 visorhba_devdata *devdata;
+	char *vbuf;
+
+	if (len > MAX_BUF)
+		len = MAX_BUF;
+	vbuf = kzalloc(len, GFP_KERNEL);
+	if (!vbuf)
+		return -ENOMEM;
+
+	for (i = 0; i < VISORHBAOPENMAX; i++) {
+		if (!visorhbas_open[i].devdata)
+			continue;
+
+		devdata = visorhbas_open[i].devdata;
+
+		str_pos += scnprintf(vbuf + str_pos,
+				len - str_pos, "max_buff_len:%u\n",
+				devdata->max_buff_len);
+
+		str_pos += scnprintf(vbuf + str_pos, len - str_pos,
+				"\ninterrupts_rcvd = %llu, interrupts_disabled = %llu\n",
+				devdata->interrupts_rcvd,
+				devdata->interrupts_disabled);
+		str_pos += scnprintf(vbuf + str_pos,
+				len - str_pos, "\ninterrupts_notme = %llu,\n",
+				devdata->interrupts_notme);
+		phys_flags_addr = virt_to_phys((__force  void *)
+					       devdata->flags_addr);
+		str_pos += scnprintf(vbuf + str_pos, len - str_pos,
+				"flags_addr = %p, phys_flags_addr=0x%016llx, FeatureFlags=%llu\n",
+				devdata->flags_addr, phys_flags_addr,
+				(__le64)readq(devdata->flags_addr));
+		str_pos += scnprintf(vbuf + str_pos,
+			len - str_pos, "acquire_failed_cnt:%llu\n",
+			devdata->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;
+}
+
+/**
+ *	devdata_initialize - Initialize a visorhba_devdata structure
+ *	@devdata: private data device created by visorhba
+ *	@dev: visor_device created by visorbus
+ *
+ *	Initializes fields in the visorhba_devdata structure
+ *	Returns a pointer to the devdata structure on SUCCESS otherwise NULL
+ */
+static struct visorhba_devdata *
+devdata_initialize(struct visorhba_devdata *devdata, struct visor_device *dev)
+{
+	int devnum = -1;
+
+	if (!devdata)
+		return NULL;
+	spin_lock(&dev_num_pool_lock);
+	devnum = find_first_zero_bit(dev_num_pool, MAXDEVICES);
+	set_bit(devnum, dev_num_pool);
+	spin_unlock(&dev_num_pool_lock);
+	if (devnum == MAXDEVICES)
+		devnum = -1;
+	if (devnum < 0)
+		return NULL;
+	devdata->devnum = devnum;
+	devdata->dev = dev;
+	strncpy(devdata->name, dev_name(&dev->device), sizeof(devdata->name));
+	kref_init(&devdata->kref);
+	spin_lock(&lock_all_devices);
+	list_add_tail(&devdata->list_all, &list_all_devices);
+	spin_unlock(&lock_all_devices);
+	return devdata;
+}
+
+/**
+ *	devdata_release - called when we are done with devdata
+ *	@mykref: kref reference to visorhba_devdata
+ *
+ *	Free up devnum, remove us from the list of active devdatas
+ *	Returns void.
+ */
+static void devdata_release(struct kref *mykref)
+{
+	struct visorhba_devdata *devdata;
+
+	devdata = container_of(mykref, struct visorhba_devdata, kref);
+	spin_lock(&dev_num_pool_lock);
+	clear_bit(devdata->devnum, dev_num_pool);
+	spin_unlock(&dev_num_pool_lock);
+
+	spin_lock(&lock_all_devices);
+	list_del(&devdata->list_all);
+	spin_unlock(&lock_all_devices);
+}
+
+/**
+ *	visorhba_serverdown_complete - Called when we are done cleaning up
+ *				       from serverdown
+ *	@work: work structure for this serverdown request
+ *
+ *	Called when we are done cleanning up from serverdown, stop processing
+ *	queue, fail pending IOs.
+ *	Returns void when finished cleaning up
+ */
+static void visorhba_serverdown_complete(struct work_struct *work)
+{
+	struct visorhba_devdata *devdata;
+	int i;
+	struct scsipending *pendingdel = NULL;
+	struct scsi_cmnd *scsicmd = NULL;
+	struct uiscmdrsp *cmdrsp;
+	unsigned long flags;
+
+	devdata = container_of(work, struct visorhba_devdata,
+			       serverdown_completion);
+
+	/* Stop using the IOVM response queue (queue should be drained
+	 * by the end)
+	 */
+	visor_thread_stop(&devdata->threadinfo);
+
+	/* Fail commands that weren't completed */
+	spin_lock_irqsave(&devdata->privlock, flags);
+	for (i = 0; i < MAX_PENDING_REQUESTS; i++) {
+		pendingdel = &devdata->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;
+			wake_up_all((wait_queue_head_t *)
+				    cmdrsp->scsitaskmgmt.notify);
+			*(int *)cmdrsp->scsitaskmgmt.notifyresult =
+				TASK_MGMT_FAILED;
+			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:
+			break;
+		}
+		pendingdel->cmdtype = 0;
+		pendingdel->sent = NULL;
+	}
+	spin_unlock_irqrestore(&devdata->privlock, flags);
+
+	devdata->serverdown = true;
+	devdata->serverchangingstate = false;
+}
+
+/**
+ *	visorhba_serverdown - Got notified that the IOVM is down
+ *	@devdata: visorhba that is being serviced by downed IOVM.
+ *
+ *	Something happened to the IOVM, return immediately and
+ *	schedule work cleanup work.
+ *	Return SUCCESS or EINVAL
+ */
+static int visorhba_serverdown(struct visorhba_devdata *devdata)
+{
+	if (!devdata->serverdown && !devdata->serverchangingstate) {
+		devdata->serverchangingstate = true;
+		queue_work(visorhba_serverdown_workqueue,
+			   &devdata->serverdown_completion);
+	} else if (devdata->serverchangingstate) {
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/**
+ *	do_scsi_linuxstat - scsi command returned linuxstat
+ *	@cmdrsp: response from IOVM
+ *	@scsicmd: Command issued.
+ *
+ *	Don't log errors for disk-not-present inquiries
+ *	Returns void
+ */
+static void
+do_scsi_linuxstat(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd)
+{
+	struct visorhba_devdata *devdata;
+	struct visordisk_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.... */
+	devdata = (struct visorhba_devdata *)scsidev->host->hostdata;
+	for (vdisk = &devdata->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) < VISORHBA_ERROR_COUNT) {
+			atomic_inc(&vdisk->error_count);
+			atomic_set(&vdisk->ios_threshold, IOS_ERROR_THRESHOLD);
+		}
+	}
+}
+
+/**
+ *	do_scsi_nolinuxstat - scsi command didn't have linuxstat
+ *	@cmdrsp: response from IOVM
+ *	@scsicmd: Command issued.
+ *
+ *	Handle response when no linuxstat was returned
+ *	Returns void
+ */
+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 *this_page;
+	char *this_page_orig;
+	int bufind = 0;
+	struct visordisk_info *vdisk;
+	struct visorhba_devdata *devdata;
+
+	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 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)
+				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++) {
+			this_page_orig = kmap_atomic(sg_page(sg + i));
+			this_page = (void *)((unsigned long)this_page_orig |
+					     sg[i].offset);
+			memcpy(this_page, buf + bufind, sg[i].length);
+			kunmap_atomic(this_page_orig);
+		}
+	} else {
+		devdata = (struct visorhba_devdata *)scsidev->host->hostdata;
+		vdisk = &devdata->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)
+					atomic_set(&vdisk->error_count, 0);
+			}
+		}
+	}
+}
+
+/**
+ *	complete_scsi_command - complete a scsi command
+ *	@uiscmdrsp: Response from Service Partition
+ *	@scsicmd: The scsi command
+ *
+ *	Response returned by the Service Partition, finish it and send
+ *	completion to the scsi midlayer.
+ *	Returns void.
+ */
+static void
+complete_scsi_command(struct uiscmdrsp *cmdrsp, struct scsi_cmnd *scsicmd)
+{
+	/* 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);
+
+	scsicmd->scsi_done(scsicmd);
+}
+
+/* DELETE VDISK TASK MGMT COMMANDS */
+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);
+}
+
+/**
+ *	complete_taskmgmt_command - complete task management
+ *	@cmdrsp: Response from the IOVM
+ *
+ *	Service Partition returned the result of the task management
+ *	command. Wake up anyone waiting for it.
+ *	Returns void
+ */
+static inline void complete_taskmgmt_command(struct uiscmdrsp *cmdrsp)
+{
+	/* copy the result of the taskgmgt 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->scsitaskmgmt.notify);
+}
+
+static struct work_struct dar_work_queue;
+static struct diskaddremove *dar_work_queue_head;
+static spinlock_t dar_work_queue_lock; /* Lock to protet dar_work_queue_head */
+static unsigned short dar_work_queue_sched;
+
+/**
+ *	queue_disk_add_remove - IOSP has sent us a add/remove request
+ *	@dar: disk add/remove request
+ *
+ *	Queue the work needed to add/remove a disk.
+ *	Returns void
+ */
+static inline void queue_disk_add_remove(struct diskaddremove *dar)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dar_work_queue_lock, flags);
+	if (!dar_work_queue_head) {
+		dar_work_queue_head = dar;
+		dar->next = NULL;
+	} else {
+		dar->next = dar_work_queue_head;
+		dar_work_queue_head = dar;
+	}
+	if (!dar_work_queue_sched) {
+		schedule_work(&dar_work_queue);
+		dar_work_queue_sched = 1;
+	}
+	spin_unlock_irqrestore(&dar_work_queue_lock, flags);
+}
+
+/**
+ *	process_disk_notify - IOSP has sent a process disk notify event
+ *	@shost: Scsi hot
+ *	@cmdrsp: Response from the IOSP
+ *
+ *	Queue it to the work queue.
+ *	Return void.
+ */
+static void process_disk_notify(struct Scsi_Host *shost,
+				struct uiscmdrsp *cmdrsp)
+{
+	struct diskaddremove *dar;
+
+	dar = kzalloc(sizeof(*dar), 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_disk_add_remove(dar);
+	}
+}
+
+/**
+ *	drain_queue - pull responses out of iochannel
+ *	@cmdrsp: Response from the IOSP
+ *	@devdata: device that owns this iochannel
+ *
+ *	Pulls responses out of the iochannel and process the responses.
+ *	Restuns void
+ */
+static void
+drain_queue(struct uiscmdrsp *cmdrsp, struct visorhba_devdata *devdata)
+{
+	struct scsi_cmnd *scsicmd;
+	struct Scsi_Host *shost = devdata->scsihost;
+
+	while (1) {
+		if (!visorchannel_signalremove(devdata->dev->visorchannel,
+					       IOCHAN_FROM_IOPART,
+					       cmdrsp))
+			break; /* queue empty */
+
+		if (cmdrsp->cmdtype == CMD_SCSI_TYPE) {
+			/* scsicmd location is returned by the
+			 * deletion
+			 */
+			scsicmd = del_scsipending_ent(devdata,
+						      (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_ent(devdata, (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
+			 * 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_ent(devdata,
+						 (uintptr_t)
+						 cmdrsp->vdiskmgmt.scsicmd))
+				break;
+			complete_vdiskmgmt_command(cmdrsp);
+		}
+		/* cmdrsp is now available for resuse */
+	}
+}
+
+/**
+ *	process_incoming_rsps - Process responses from IOSP
+ *	@v: void pointer to visorhba_devdata
+ *
+ *	Main function for the thread that processes the responses
+ *	from the IO Service Partition. When the queue is empty, wait
+ *	to check to see if it is full again.
+ */
+static int process_incoming_rsps(void *v)
+{
+	struct visorhba_devdata *devdata = v;
+	struct uiscmdrsp *cmdrsp = NULL;
+	const int size = sizeof(*cmdrsp);
+
+	cmdrsp = kmalloc(size, GFP_ATOMIC);
+	if (!cmdrsp)
+		complete_and_exit(&devdata->threadinfo.has_stopped, 0);
+
+	while (1) {
+		if (kthread_should_stop())
+			break;
+		wait_event_interruptible_timeout(
+			devdata->rsp_queue, (atomic_read(
+					     &devdata->interrupt_rcvd) == 1),
+				msecs_to_jiffies(devdata->thread_wait_ms));
+		/* drain queue */
+		drain_queue(cmdrsp, devdata);
+	}
+	kfree(cmdrsp);
+	complete_and_exit(&devdata->threadinfo.has_stopped, 0);
+}
+
+/**
+ *	visorhba_pause - function to handle visorbus pause messages
+ *	@dev: device that is pausing.
+ *	@complete_func: function to call when finished
+ *
+ *	Something has happened to the IO Service Partition that is
+ *	handling this device. Quiet this device and reset commands
+ *	so that the Service Partition can be corrected.
+ *	Returns SUCCESS
+ */
+static int visorhba_pause(struct visor_device *dev,
+			  visorbus_state_complete_func complete_func)
+{
+	struct visorhba_devdata *devdata = dev_get_drvdata(&dev->device);
+
+	visorhba_serverdown(devdata);
+	complete_func(dev, 0);
+	return 0;
+}
+
+/**
+ *	visorhba_resume - function called when the IO Service Partition is back
+ *	@dev: device that is pausing.
+ *	@complete_func: function to call when finished
+ *
+ *	Yay! The IO Service Partition is back, the channel has been wiped
+ *	so lets re-establish connection and start processing responses.
+ *	Returns 0 on success, error on failure.
+ */
+static int visorhba_resume(struct visor_device *dev,
+			   visorbus_state_complete_func complete_func)
+{
+	struct visorhba_devdata *devdata;
+
+	devdata = dev_get_drvdata(&dev->device);
+	if (!devdata)
+		return -EINVAL;
+
+	if (devdata->serverdown && !devdata->serverchangingstate)
+		devdata->serverchangingstate = 1;
+
+	visor_thread_start(&devdata->threadinfo, process_incoming_rsps,
+			   devdata, "vhba_incming");
+
+	devdata->serverdown = false;
+	devdata->serverchangingstate = false;
+
+	return 0;
+}
+
+/**
+ *	visorhba_probe - device has been discovered, do acquire
+ *	@dev: visor_device that was discovered
+ *
+ *	A new HBA was discovered, do the initial connections of it.
+ *	Return 0 on success, otherwise error.
+ */
+static int visorhba_probe(struct visor_device *dev)
+{
+	struct Scsi_Host *scsihost;
+	struct vhba_config_max max;
+	struct visorhba_devdata *devdata = NULL;
+	int i, err, channel_offset;
+	u64 features;
+
+	scsihost = scsi_host_alloc(&visorhba_driver_template,
+				   sizeof(*devdata));
+	if (!scsihost)
+		return -ENODEV;
+
+	channel_offset = offsetof(struct spar_io_channel_protocol,
+				  vhba.max);
+	err = visorbus_read_channel(dev, channel_offset, &max,
+				    sizeof(struct vhba_config_max));
+	if (err < 0)
+		goto fail;
+
+	scsihost->max_id = (unsigned)max.max_id;
+	scsihost->max_lun = (unsigned)max.max_lun;
+	scsihost->cmd_per_lun = (unsigned)max.cmd_per_lun;
+	scsihost->max_sectors =
+	    (unsigned short)(max.max_io_size >> 9);
+	scsihost->sg_tablesize =
+	    (unsigned short)(max.max_io_size / PAGE_SIZE);
+	if (scsihost->sg_tablesize > MAX_PHYS_INFO)
+		scsihost->sg_tablesize = MAX_PHYS_INFO;
+	err = scsi_add_host(scsihost, &dev->device);
+	if (err < 0)
+		goto fail;
+
+	devdata = devdata_initialize((struct visorhba_devdata *)
+				     scsihost->hostdata,
+				     dev);
+	if (!devdata)
+		goto fail_host;
+	for (i = 0; i < VISORHBAOPENMAX; i++) {
+		if (!visorhbas_open[i].devdata) {
+			visorhbas_open[i].devdata = devdata;
+			break;
+		}
+	}
+
+	devdata->dev = dev;
+	dev_set_drvdata(&dev->device, devdata);
+
+	init_waitqueue_head(&devdata->rsp_queue);
+	spin_lock_init(&devdata->privlock);
+	devdata->serverdown = false;
+	devdata->serverchangingstate = false;
+	devdata->scsihost = scsihost;
+
+	INIT_WORK(&devdata->serverdown_completion,
+		  visorhba_serverdown_complete);
+
+	channel_offset = offsetof(struct spar_io_channel_protocol,
+				  channel_header.features);
+	err = visorbus_read_channel(dev, channel_offset, &features, 8);
+	if (err)
+		goto fail_host;
+	features |= ULTRA_IO_CHANNEL_IS_POLLING;
+	err = visorbus_write_channel(dev, channel_offset, &features, 8);
+	if (err)
+		goto fail_host;
+
+	devdata->thread_wait_ms = 2;
+	visor_thread_start(&devdata->threadinfo, process_incoming_rsps,
+			   devdata, "vhba_incoming");
+
+	scsi_scan_host(scsihost);
+
+	return 0;
+fail_host:
+	scsi_remove_host(scsihost);
+
+fail:
+	scsi_host_put(scsihost);
+	return err;
+}
+
+/**
+ *	visorhba_remove - remove a visorhba device
+ *	@dev: Device to remove
+ *
+ *	Removes the visorhba device.
+ *	Returns void.
+ */
+static void visorhba_remove(struct visor_device *dev)
+{
+	struct visorhba_devdata *devdata = dev_get_drvdata(&dev->device);
+	struct Scsi_Host *scsihost = NULL;
+
+	if (!devdata)
+		return;
+
+	scsihost = devdata->scsihost;
+	visor_thread_stop(&devdata->threadinfo);
+	scsi_remove_host(scsihost);
+	scsi_host_put(scsihost);
+
+	dev_set_drvdata(&dev->device, NULL);
+	/* host_side_disappeared(devdata); */
+	kref_put(&devdata->kref, devdata_release);
+}
+
+/**
+ *	visorhba_init		- driver init routine
+ *
+ *	Initialize the visorhba driver and register it with visorbus
+ *	to handle s-Par virtual host bus adapter.
+ */
+static int visorhba_init(void)
+{
+	struct dentry *ret;
+	int rc = -ENOMEM;
+
+	spin_lock_init(&dev_num_pool_lock);
+	dev_num_pool = kzalloc(BITS_TO_LONGS(MAXDEVICES), GFP_KERNEL);
+	if (!dev_num_pool)
+		return rc;
+
+	visorhba_debugfs_dir = debugfs_create_dir("visorhba", NULL);
+	if (!visorhba_debugfs_dir)
+		goto cleanup_dev_num_pool;
+
+	ret = debugfs_create_file("info", S_IRUSR, visorhba_debugfs_dir, NULL,
+				  &debugfs_info_fops);
+
+	if (!ret) {
+		rc = -EIO;
+		goto cleanup_debugfs;
+	}
+
+	rc = visorbus_register_visor_driver(&visorhba_driver);
+	if (rc)
+		goto cleanup_debugfs;
+
+	return rc;
+
+cleanup_debugfs:
+	debugfs_remove_recursive(visorhba_debugfs_dir);
+
+cleanup_dev_num_pool:
+	kfree(dev_num_pool);
+	dev_num_pool = NULL;
+	return rc;
+}
+
+/**
+ *	visorhba_cleanup	- driver exit routine
+ *
+ *	Unregister driver from the bus and free up memory.
+ */
+static void visorhba_exit(void)
+{
+	visorbus_unregister_visor_driver(&visorhba_driver);
+	debugfs_remove_recursive(visorhba_debugfs_dir);
+	kfree(dev_num_pool);
+	dev_num_pool = NULL;
+}
+
+module_init(visorhba_init);
+module_exit(visorhba_exit);
+
+MODULE_AUTHOR("Unisys");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("sPAR hba driver for sparlinux: ver " VERSION);
+MODULE_VERSION(VERSION);
-- 
2.1.4



More information about the devel mailing list