diff options
Diffstat (limited to 'kernel/drivers/staging/unisys/virthba/virthba.c')
-rw-r--r-- | kernel/drivers/staging/unisys/virthba/virthba.c | 1572 |
1 files changed, 1572 insertions, 0 deletions
diff --git a/kernel/drivers/staging/unisys/virthba/virthba.c b/kernel/drivers/staging/unisys/virthba/virthba.c new file mode 100644 index 000000000..d9001cca0 --- /dev/null +++ b/kernel/drivers/staging/unisys/virthba/virthba.c @@ -0,0 +1,1572 @@ +/* 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 */ + +#include <linux/kernel.h> +#ifdef CONFIG_MODVERSIONS +#include <config/modversions.h> +#endif + +#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 do_disk_add_remove(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 max_buff_len; + +/* 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 dar_work_queue */ +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 virthbas_open[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) { + insert_location = (insert_location + 1) % MAX_PENDING_REQUESTS; + if (insert_location == (int)vhbainfo->nextinsert) { + 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) { + 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) { + spin_lock_irqsave(&vhbainfo->privlock, flags); + sent = vhbainfo->pending[del].sent; + + vhbainfo->pending[del].cmdtype = 0; + vhbainfo->pending[del].sent = NULL; + spin_unlock_irqrestore(&vhbainfo->privlock, flags); + } + + return sent; +} + +/* dar_work_queue (Disk Add/Remove) */ +static struct work_struct dar_work_queue; +static struct diskaddremove *dar_work_queue_head; +static spinlock_t dar_work_queue_lock; +static unsigned short dar_work_queue_sched; +#define QUEUE_DISKADDREMOVE(dar) { \ + 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); \ +} + +static inline void +send_disk_add_remove(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); + } + kfree(dar); +} + +/*****************************************************/ +/* dar_work_queue Handler Thread */ +/*****************************************************/ +static void +do_disk_add_remove(struct work_struct *work) +{ + struct diskaddremove *dar; + struct diskaddremove *tmphead; + int i = 0; + unsigned long flags; + + spin_lock_irqsave(&dar_work_queue_lock, flags); + tmphead = dar_work_queue_head; + dar_work_queue_head = NULL; + dar_work_queue_sched = 0; + spin_unlock_irqrestore(&dar_work_queue_lock, flags); + while (tmphead) { + dar = tmphead; + tmphead = dar->next; + send_disk_add_remove(dar); + i++; + } +} + +/*****************************************************/ +/* Routine to add entry to dar_work_queue */ +/*****************************************************/ +static void +process_disk_notify(struct Scsi_Host *shost, struct uiscmdrsp *cmdrsp) +{ + struct diskaddremove *dar; + unsigned long flags; + + 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_DISKADDREMOVE(dar); + } +} + +/*****************************************************/ +/* 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 *channel_header; + struct signal_queue_header __iomem *pqhdr; + u64 mask; + unsigned long long rc1; + + if (!virthbainfo) + return IRQ_NONE; + virthbainfo->interrupts_rcvd++; + channel_header = virthbainfo->chinfo.queueinfo->chan; + if (((readq(&channel_header->features) + & ULTRA_IO_IOVM_IS_OK_WITH_DRIVER_DISABLING_INTS) != 0) && + ((readq(&channel_header->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(channel_header, IOCHAN_FROM_IOPART)) { + virthbainfo->interrupts_notme++; + return IRQ_NONE; + } + pqhdr = (struct signal_queue_header __iomem *) + ((char __iomem *)channel_header + + readq(&channel_header->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 *channel_header; + struct signal_queue_header __iomem *pqhdr; + u64 mask; + + 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 + * initialization. The host is not published to the scsi + * midlayer until scsi_add_host is called. + */ + + /* 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) + return -ENODEV; + + 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. + */ + 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; + + /* 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) { + 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 (!virthbas_open[i].virthbainfo) { + virthbas_open[i].virthbainfo = virthbainfo; + break; + } + } + virthbainfo->interrupt_vector = -1; + virthbainfo->chinfo.queueinfo = &virtpcidev->queueinfo; + virthbainfo->virtpcidev = virtpcidev; + spin_lock_init(&virthbainfo->chinfo.insertlock); + + 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 */ + + channel_header = virthbainfo->chinfo.queueinfo->chan; + pqhdr = (struct signal_queue_header __iomem *) + ((char __iomem *)channel_header + + readq(&channel_header->ch_space_offset)) + IOCHAN_FROM_IOPART; + virthbainfo->flags_addr = &pqhdr->features; + + if (!uisthread_start(&virthbainfo->chinfo.threadinfo, + process_incoming_rsps, + virthbainfo, "vhba_incoming")) { + /* 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; + } + 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) { + virthbainfo->interrupt_vector = -1; + POSTCODE_LINUX_2(VHBA_PROBE_FAILURE_PC, POSTCODE_SEVERITY_ERR); + } else { + u64 __iomem *features_addr = + &virthbainfo->chinfo.queueinfo->chan->features; + 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; + } + + scsi_scan_host(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; + + virthbainfo = (struct virthba_info *)scsihost->hostdata; + if (virthbainfo->interrupt_vector != -1) + free_irq(virthbainfo->interrupt_vector, virthbainfo); + + scsi_remove_host(scsihost); + + uisthread_stop(&virthbainfo->chinfo.threadinfo); + + /* decr refcount on scsihost which was incremented by + * scsi_add_host so the scsi_host gets deleted + */ + scsi_host_put(scsihost); +} + +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; + + if (virthbainfo->serverdown || virthbainfo->serverchangingstate) + return FAILED; + + cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (!cmdrsp) + 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"); + wait_event(notifyevent, notifyresult != 0xffff); + 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; + + if (virthbainfo->serverdown || virthbainfo->serverchangingstate) + return FAILED; + + cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (!cmdrsp) + 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"); + wait_event(notifyevent, notifyresult != 0xffff); + 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 */ + 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) +{ + 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) + return SCSI_MLQUEUE_DEVICE_BUSY; + cmdrsp = kzalloc(SIZEOF_CMDRSP, GFP_ATOMIC); + if (!cmdrsp) + 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 { + 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 > max_buff_len) + max_buff_len = cmdrsp->scsi.bufflen; + + if (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) { + BUG_ON(scsi_sg_count(scsicmd) == 0); + } + } 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 (sg_failed) { + /* 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 */ + 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) + 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(*tmpvdisk), GFP_ATOMIC); + if (!tmpvdisk) + 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; + 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); + 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) { + 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++) { + 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) { + atomic_set(&vdisk->error_count, 0); + } + } + } + } +} + +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); + + if (scsicmd->scsi_done) + 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); +} + +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); +} + +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); + } + /* 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) { + complete_and_exit(&dc->threadinfo.has_stopped, 0); + return 0; + } + mask = ULTRA_CHANNEL_ENABLE_INTS; + while (1) { + if (kthread_should_stop()) + break; + 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); + } + + kfree(cmdrsp); + + 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 (!virthbas_open[i].virthbainfo) + continue; + + virthbainfo = virthbas_open[i].virthbainfo; + + str_pos += scnprintf(vbuf + str_pos, + len - str_pos, "max_buff_len:%u\n", + max_buff_len); + + 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)) + return -EFAULT; + + i = kstrtoint(buf, 10, &new_value); + + if (i != 0) + return -EFAULT; + + /* set all counts to new_value usually 0 */ + for (i = 0; i < VIRTHBASOPENMAX; i++) { + if (virthbas_open[i].virthbainfo) { + virthbainfo = virthbas_open[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; + + if (!virthbainfo->serverdown) + return 1; + + if (virthbainfo->serverchangingstate) + 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")) { + 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; + 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(&virthbainfo->privlock, flags); + + virtpcidev = virthbainfo->virtpcidev; + + 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) +{ + int stat = 1; + + struct virthba_info *virthbainfo = + (struct virthba_info *)((struct Scsi_Host *)virtpcidev->scsi. + scsihost)->hostdata; + + if (!virthbainfo->serverdown && !virthbainfo->serverchangingstate) { + virthbainfo->serverchangingstate = true; + queue_work(virthba_serverdown_workqueue, + &virthbainfo->serverdown_completion); + } else if (virthbainfo->serverchangingstate) { + stat = 0; + } + + return stat; +} + +/*****************************************************/ +/* Module Init & Exit functions */ +/*****************************************************/ + +static int __init +virthba_parse_line(char *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 || !*line) + return; + while ((line = next)) { + next = strchr(line, ' '); + if (next) + *next++ = 0; + virthba_parse_line(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; + + POSTCODE_LINUX_2(VHBA_CREATE_ENTRY_PC, POSTCODE_SEVERITY_INFO); + virthba_parse_options(virthba_options); + + error = virtpci_register_driver(&virthba_driver); + if (error < 0) { + 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 dar_work_queue */ + INIT_WORK(&dar_work_queue, do_disk_add_remove); + spin_lock_init(&dar_work_queue_lock); + + /* clear out array */ + for (i = 0; i < VIRTHBASOPENMAX; i++) + virthbas_open[i].virthbainfo = NULL; + /* Initialize the serverdown workqueue */ + virthba_serverdown_workqueue = + create_singlethread_workqueue("virthba_serverdown"); + if (!virthba_serverdown_workqueue) { + POSTCODE_LINUX_2(VHBA_CREATE_FAILURE_PC, + POSTCODE_SEVERITY_ERR); + error = -1; + } + } + + POSTCODE_LINUX_2(VHBA_CREATE_EXIT_PC, POSTCODE_SEVERITY_INFO); + 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) +{ + 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); +} + +/* 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); |