diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-10-09 00:00:47 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-10-09 00:00:47 +0000 |
commit | d6434e1042f3b0a6dfe1b1f615af369486f9b1fa (patch) | |
tree | e2be02f33984c48ec019c654051d27964e42c441 /drivers/scsi/ips.c | |
parent | 609d1e803baf519487233b765eb487f9ec227a18 (diff) |
Merge with 2.3.19.
Diffstat (limited to 'drivers/scsi/ips.c')
-rw-r--r-- | drivers/scsi/ips.c | 3936 |
1 files changed, 3936 insertions, 0 deletions
diff --git a/drivers/scsi/ips.c b/drivers/scsi/ips.c new file mode 100644 index 000000000..450fc370e --- /dev/null +++ b/drivers/scsi/ips.c @@ -0,0 +1,3936 @@ +/*****************************************************************************/ +/* ips.c -- driver for the IBM ServeRAID controller */ +/* */ +/* Written By: Keith Mitchell, IBM Corporation */ +/* */ +/* Copyright (C) 1999 IBM Corporation */ +/* */ +/* 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. See the */ +/* GNU General Public License for more details. */ +/* */ +/* NO WARRANTY */ +/* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR */ +/* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT */ +/* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, */ +/* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is */ +/* solely responsible for determining the appropriateness of using and */ +/* distributing the Program and assumes all risks associated with its */ +/* exercise of rights under this Agreement, including but not limited to */ +/* the risks and costs of program errors, damage to or loss of data, */ +/* programs or equipment, and unavailability or interruption of operations. */ +/* */ +/* DISCLAIMER OF LIABILITY */ +/* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY */ +/* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL */ +/* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND */ +/* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR */ +/* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE */ +/* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED */ +/* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/* */ +/* Bugs/Comments/Suggestions should be mailed to: */ +/* ipslinux@us.ibm.com */ +/* */ +/*****************************************************************************/ + +/*****************************************************************************/ +/* Change Log */ +/* */ +/* 0.99.02 - Breakup commands that are bigger than 8 * the stripe size */ +/* 0.99.03 - Make interrupt routine handle all completed request on the */ +/* adapter not just the first one */ +/* - Make sure passthru commands get woken up if we run out of */ +/* SCBs */ +/* - Send all of the commands on the queue at once rather than */ +/* one at a time since the card will support it. */ +/* 0.99.04 - Fix race condition in the passthru mechanism -- this required */ +/* the interface to the utilities to change */ +/* - Fix error recovery code */ +/* 0.99.05 - Fix an oops when we get certain passthru commands */ +/* 1.00.00 - Initial Public Release */ +/* Functionally equivalent to 0.99.05 */ +/* */ +/*****************************************************************************/ + +/* + * Conditional Compilation directives for this driver: + * + * NO_IPS_RESET - Don't reset the controller (no matter what) + * IPS_DEBUG - More verbose error messages + * IPS_PCI_PROBE_DEBUG - Print out more detail on the PCI probe + * + */ + +#if defined (MODULE) + #include <linux/module.h> +#endif /* MODULE */ + +#include <asm/io.h> +#include <asm/byteorder.h> +#include <linux/stddef.h> +#include <linux/version.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> + +#include <linux/blk.h> +#include <linux/types.h> + +#ifndef NO_IPS_CMDLINE +#include <scsi/sg.h> +#endif + +#include "sd.h" +#include "scsi.h" +#include "hosts.h" +#include "ips.h" + +#include <linux/stat.h> +#include <linux/config.h> +#include <linux/spinlock.h> +#include <linux/smp.h> + +/* + * DRIVER_VER + */ +#define IPS_VERSION_HIGH "1.00" /* MUST be 4 chars */ +#define IPS_VERSION_LOW ".00 " /* MUST be 4 chars */ + +struct proc_dir_entry proc_scsi_ips = { +#if !defined(PROC_SCSI_IPS) + 0, /* Use dynamic inode allocation */ +#else + PROC_SCSI_IPS, +#endif + 3, "ips", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#if LINUX_VERSION_CODE < LinuxVersionCode(2,1,93) + #include <linux/bios32.h> +#endif + +#if !defined(__i386__) + #error "This driver has only been tested on the x86 platform" +#endif + +#if LINUX_VERSION_CODE < LinuxVersionCode(2,2,0) + #error "This driver only works with kernel 2.2.0 and later" +#endif + +#if !defined(NO_IPS_CMDLINE) && ((SG_BIG_BUFF < 8192) || !defined(SG_BIG_BUFF)) + #error "To use the command-line interface you need to define SG_BIG_BUFF" +#endif + +#if IPS_DEBUG >= 12 + #define DBG(s) printk(KERN_NOTICE s "\n"); MDELAY(2*ONE_SEC) +#elif IPS_DEBUG >= 11 + #define DBG(s) printk(KERN_NOTICE s "\n") +#else + #define DBG(s) +#endif + +/* + * global variables + */ +static const char * ips_name = "ips"; +static struct Scsi_Host * ips_sh[IPS_MAX_ADAPTERS]; /* Array of host controller structures */ +static ips_ha_t * ips_ha[IPS_MAX_ADAPTERS]; /* Array of HA structures */ +static unsigned int ips_num_controllers = 0; +static int ips_cmd_timeout = 60; +static int ips_reset_timeout = 60 * 5; + +#define MAX_ADAPTER_NAME 6 + +static char ips_adapter_name[][30] = { + "ServeRAID", + "ServeRAID II", + "ServeRAID on motherboard", + "ServeRAID on motherboard", + "ServeRAID 3H", + "ServeRAID 3L" +}; + +/* + * Function prototypes + */ +int ips_detect(Scsi_Host_Template *); +int ips_release(struct Scsi_Host *); +int ips_abort(Scsi_Cmnd *); +int ips_reset(Scsi_Cmnd *, unsigned int); +int ips_eh_abort(Scsi_Cmnd *); +int ips_eh_reset(Scsi_Cmnd *); +int ips_queue(Scsi_Cmnd *, void (*) (Scsi_Cmnd *)); +int ips_biosparam(Disk *, kdev_t, int *); +const char * ips_info(struct Scsi_Host *); +void do_ipsintr(int, void *, struct pt_regs *); +static int ips_hainit(ips_ha_t *); +static int ips_map_status(ips_scb_t *, ips_stat_t *); +static int ips_send(ips_ha_t *, ips_scb_t *, scb_callback); +static int ips_send_wait(ips_ha_t *, ips_scb_t *, int); +static int ips_send_cmd(ips_ha_t *, ips_scb_t *); +static int ips_chkstatus(ips_ha_t *); +static int ips_online(ips_ha_t *, ips_scb_t *); +static int ips_inquiry(ips_ha_t *, ips_scb_t *); +static int ips_rdcap(ips_ha_t *, ips_scb_t *); +static int ips_msense(ips_ha_t *, ips_scb_t *); +static int ips_reqsen(ips_ha_t *, ips_scb_t *); +static int ips_allocatescbs(ips_ha_t *); +static int ips_reset_adapter(ips_ha_t *); +static int ips_statupd(ips_ha_t *); +static int ips_issue(ips_ha_t *, ips_scb_t *); +static int ips_isintr(ips_ha_t *); +static int ips_wait(ips_ha_t *, int, int); +static int ips_write_driver_status(ips_ha_t *); +static int ips_read_adapter_status(ips_ha_t *); +static int ips_read_subsystem_parameters(ips_ha_t *); +static int ips_read_config(ips_ha_t *); +static int ips_clear_adapter(ips_ha_t *); +static int ips_readwrite_page5(ips_ha_t *, int); +static void ips_intr(ips_ha_t *); +static void ips_next(ips_ha_t *); +static void ipsintr_blocking(ips_ha_t *, struct ips_scb *); +static void ipsintr_done(ips_ha_t *, struct ips_scb *); +static void ips_done(ips_ha_t *, ips_scb_t *); +static void ips_free(ips_ha_t *); +static void ips_init_scb(ips_ha_t *, ips_scb_t *); +static void ips_freescb(ips_ha_t *, ips_scb_t *); +static void ips_statinit(ips_ha_t *); +static ips_scb_t * ips_getscb(ips_ha_t *); +static inline void ips_putq_scb_head(ips_scb_queue_t *, ips_scb_t *); +static inline void ips_putq_scb_tail(ips_scb_queue_t *, ips_scb_t *); +static inline ips_scb_t * ips_removeq_scb_head(ips_scb_queue_t *); +static inline ips_scb_t * ips_removeq_scb(ips_scb_queue_t *, ips_scb_t *); +static inline void ips_putq_wait_head(ips_wait_queue_t *, Scsi_Cmnd *); +static inline void ips_putq_wait_tail(ips_wait_queue_t *, Scsi_Cmnd *); +static inline Scsi_Cmnd * ips_removeq_wait_head(ips_wait_queue_t *); +static inline Scsi_Cmnd * ips_removeq_wait(ips_wait_queue_t *, Scsi_Cmnd *); + +#ifndef NO_IPS_CMDLINE +static int ips_is_passthru(Scsi_Cmnd *); +static int ips_make_passthru(ips_ha_t *, Scsi_Cmnd *, ips_scb_t *); +static int ips_usrcmd(ips_ha_t *, ips_passthru_t *, ips_scb_t *); +#endif + +int ips_proc_info(char *, char **, off_t, int, int, int); +static int ips_host_info(ips_ha_t *, char *, off_t, int); +static void copy_mem_info(INFOSTR *, char *, int); +static int copy_info(INFOSTR *, char *, ...); + +/*--------------------------------------------------------------------------*/ +/* Exported Functions */ +/*--------------------------------------------------------------------------*/ + +/****************************************************************************/ +/* */ +/* Routine Name: ips_detect */ +/* */ +/* Routine Description: */ +/* */ +/* Detect and initialize the driver */ +/* */ +/****************************************************************************/ +int +ips_detect(Scsi_Host_Template *SHT) { + struct Scsi_Host *sh; + ips_ha_t *ha; + u32 io_addr; + u16 planer; + u8 bus; + u8 func; + u8 irq; + int index; + struct pci_dev *dev = NULL; + + DBG("ips_detect"); + + SHT->proc_info = ips_proc_info; + SHT->proc_dir = &proc_scsi_ips; + +#if defined(CONFIG_PCI) + + /* initalize number of controllers */ + ips_num_controllers = 0; + + if (!pci_present()) + return (0); + + for (index = 0; index < IPS_MAX_ADAPTERS; index++) { + + if (!(dev = pci_find_device(IPS_VENDORID, IPS_DEVICEID, dev))) + break; + + /* stuff that we get in dev */ + irq = dev->irq; + bus = dev->bus->number; + func = dev->devfn; + io_addr = dev->resource[0].start; + + /* get planer status */ + if (pci_read_config_word(dev, 0x04, &planer)) { + printk(KERN_WARNING "(%s%d) can't get planer status.\n", + ips_name, index); + + continue; + } + + /* check I/O address */ + if ((dev->resource[0].flags & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_IO) + continue; + + /* check to see if an onboard planer controller is disabled */ + if (!(planer & 0x000C)) { + + #ifdef IPS_PCI_PROBE_DEBUG + printk(KERN_NOTICE "(%s%d) detect, Onboard ServeRAID disabled by BIOS\n", + ips_name, index); + #endif + + continue; + } + + #ifdef IPS_PCI_PROBE_DEBUG + printk(KERN_NOTICE "(%s%d) detect bus %d, func %x, irq %d, io %x\n", + ips_name, index, bus, func, irq, io_addr); + #endif + + /* found a controller */ + sh = scsi_register(SHT, sizeof(ips_ha_t)); + + if (sh == NULL) { + printk(KERN_WARNING "(%s%d) Unable to register controller with SCSI subsystem - skipping controller\n", + ips_name, index); + + continue; + } + + ha = HA(sh); + memset(ha, 0, sizeof(ips_ha_t)); + + /* Initialize spin lock */ + spin_lock_init(&ha->scb_lock); + spin_lock_init(&ha->copp_lock); + + ips_sh[ips_num_controllers] = sh; + ips_ha[ips_num_controllers] = ha; + ips_num_controllers++; + ha->active = 1; + + ha->enq = kmalloc(sizeof(ENQCMD), GFP_KERNEL|GFP_DMA); + + if (!ha->enq) { + printk(KERN_WARNING "(%s%d) Unable to allocate host inquiry structure - skipping contoller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + ha->adapt = kmalloc(sizeof(ADAPTER_AREA), GFP_KERNEL|GFP_DMA); + + if (!ha->adapt) { + printk(KERN_WARNING "(%s%d) Unable to allocate host adapt structure - skipping controller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + ha->conf = kmalloc(sizeof(CONFCMD), GFP_KERNEL|GFP_DMA); + + if (!ha->conf) { + printk(KERN_WARNING "(%s%d) Unable to allocate host conf structure - skipping controller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + ha->nvram = kmalloc(sizeof(NVRAM_PAGE5), GFP_KERNEL|GFP_DMA); + + if (!ha->nvram) { + printk(KERN_WARNING "(%s%d) Unable to allocate host nvram structure - skipping controller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + ha->subsys = kmalloc(sizeof(SUBSYS_PARAM), GFP_KERNEL|GFP_DMA); + + if (!ha->subsys) { + printk(KERN_WARNING "(%s%d) Unable to allocate host subsystem structure - skipping controller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + ha->dummy = kmalloc(sizeof(BASIC_IO_CMD), GFP_KERNEL|GFP_DMA); + + if (!ha->dummy) { + printk(KERN_WARNING "(%s%d) Unable to allocate host dummy structure - skipping controller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + /* Store away needed values for later use */ + sh->io_port = io_addr; + sh->n_io_port = 255; + sh->unique_id = io_addr; + sh->irq = irq; + sh->select_queue_depths = NULL; + sh->sg_tablesize = sh->hostt->sg_tablesize; + sh->can_queue = sh->hostt->can_queue; + sh->cmd_per_lun = sh->hostt->cmd_per_lun; + sh->unchecked_isa_dma = sh->hostt->unchecked_isa_dma; + sh->use_clustering = sh->hostt->use_clustering; + sh->wish_block = FALSE; + + /* Store info in HA structure */ + ha->io_addr = io_addr; + ha->irq = irq; + ha->host_num = index; + + /* install the interrupt handler */ + if (request_irq(irq, do_ipsintr, SA_SHIRQ, ips_name, ha)) { + printk(KERN_WARNING "(%s%d) unable to install interrupt handler - skipping controller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + /* + * Allocate a temporary SCB for initialization + */ + ha->scbs = (ips_scb_t *) kmalloc(sizeof(ips_scb_t), GFP_KERNEL|GFP_DMA); + if (!ha->scbs) { + /* couldn't allocate a temp SCB */ + printk(KERN_WARNING "(%s%d) unable to allocate CCBs - skipping contoller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + memset(ha->scbs, 0, sizeof(ips_scb_t)); + ha->scbs->sg_list = (SG_LIST *) kmalloc(sizeof(SG_LIST) * MAX_SG_ELEMENTS, GFP_KERNEL|GFP_DMA); + if (!ha->scbs->sg_list) { + /* couldn't allocate a temp SCB S/G list */ + printk(KERN_WARNING "(%s%d) unable to allocate CCBs - skipping contoller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + ha->max_cmds = 1; + + if (!ips_hainit(ha)) { + printk(KERN_WARNING "(%s%d) unable to initialize controller - skipping\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + /* + * Free the temporary SCB + */ + kfree(ha->scbs->sg_list); + kfree(ha->scbs); + ha->scbs = NULL; + + /* allocate CCBs */ + if (!ips_allocatescbs(ha)) { + printk(KERN_WARNING "(%s%d) unable to allocate CCBs - skipping contoller\n", + ips_name, index); + + ha->active = 0; + + continue; + } + + /* finish setting values */ + sh->max_id = ha->ntargets; + sh->max_lun = ha->nlun; + sh->max_channel = ha->nbus; + sh->can_queue = ha->max_cmds-1; + } /* end for */ + + return (ips_num_controllers); + +#else + + /* No PCI -- No ServeRAID */ + return (0); +#endif /* CONFIG_PCI */ +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_release */ +/* */ +/* Routine Description: */ +/* */ +/* Remove a driver */ +/* */ +/****************************************************************************/ +int +ips_release(struct Scsi_Host *sh) { + ips_scb_t *scb; + ips_ha_t *ha; + int i; + + DBG("ips_release"); + + for (i = 0; i < IPS_MAX_ADAPTERS && ips_sh[i] != sh; i++); + + if (i == IPS_MAX_ADAPTERS) + panic("(%s) release, invalid Scsi_Host pointer.\n", + ips_name); + + ha = HA(sh); + + if (!ha) + return (FALSE); + + /* flush the cache on the controller */ + scb = &ha->scbs[ha->max_cmds-1]; + + ips_init_scb(ha, scb); + + scb->timeout = ips_cmd_timeout; + scb->cdb[0] = FLUSH_CACHE; + + scb->cmd.flush_cache.op_code = FLUSH_CACHE; + scb->cmd.flush_cache.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.flush_cache.state = NORM_STATE; + scb->cmd.flush_cache.reserved = 0; + scb->cmd.flush_cache.reserved2 = 0; + scb->cmd.flush_cache.reserved3 = 0; + scb->cmd.flush_cache.reserved4 = 0; + + printk("(%s%d) Flushing Cache.\n", ips_name, ha->host_num); + + /* send command */ + if (ips_send_wait(ha, scb, ips_cmd_timeout) == IPS_FAILURE) + printk("(%s%d) Incomplete Flush.\n", ips_name, ha->host_num); + + printk("(%s%d) Flushing Complete.\n", ips_name, ha->host_num); + + ips_sh[i] = NULL; + ips_ha[i] = NULL; + + /* free extra memory */ + ips_free(ha); + + /* free IRQ */ + free_irq(ha->irq, ha); + + /* unregister with SCSI sub system */ + scsi_unregister(sh); + + return (FALSE); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_eh_abort */ +/* */ +/* Routine Description: */ +/* */ +/* Abort a command (using the new error code stuff) */ +/* */ +/****************************************************************************/ +int +ips_eh_abort(Scsi_Cmnd *SC) { + ips_ha_t *ha; + + DBG("ips_eh_abort"); + + if (!SC) + return (FAILED); + + ha = (ips_ha_t *) SC->host->hostdata; + + if (!ha) + return (FAILED); + + if (!ha->active) + return (FAILED); + + if (SC->serial_number != SC->serial_number_at_timeout) { + /* HMM, looks like a bogus command */ +#if IPS_DEBUG >= 1 + printk(KERN_NOTICE "Abort called with bogus scsi command\n"); +#endif + + return (FAILED); + } + + if (test_and_set_bit(IPS_IN_ABORT, &ha->flags)) + return (FAILED); + + /* See if the command is on the wait queue */ + if (ips_removeq_wait(&ha->scb_waitlist, SC) || + ips_removeq_wait(&ha->copp_waitlist, SC)) { + /* command not sent yet */ + clear_bit(IPS_IN_ABORT, &ha->flags); + + return (SUCCESS); + } else { + /* command must have already been sent */ + clear_bit(IPS_IN_ABORT, &ha->flags); + + return (FAILED); + } +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_abort */ +/* */ +/* Routine Description: */ +/* */ +/* Abort a command */ +/* */ +/****************************************************************************/ +int +ips_abort(Scsi_Cmnd *SC) { + ips_ha_t *ha; + + DBG("ips_abort"); + + if (!SC) + return (SCSI_ABORT_SNOOZE); + + ha = (ips_ha_t *) SC->host->hostdata; + + if (!ha) + return (SCSI_ABORT_SNOOZE); + + if (!ha->active) + return (SCSI_ABORT_SNOOZE); + + if (SC->serial_number != SC->serial_number_at_timeout) { + /* HMM, looks like a bogus command */ +#if IPS_DEBUG >= 1 + printk(KERN_NOTICE "Abort called with bogus scsi command\n"); +#endif + + return (SCSI_ABORT_NOT_RUNNING); + } + + if (test_and_set_bit(IPS_IN_ABORT, &ha->flags)) + return (SCSI_ABORT_SNOOZE); + + /* See if the command is on the wait queue */ + if (ips_removeq_wait(&ha->scb_waitlist, SC) || + ips_removeq_wait(&ha->copp_waitlist, SC)) { + /* command not sent yet */ + clear_bit(IPS_IN_ABORT, &ha->flags); + + return (SCSI_ABORT_PENDING); + } else { + /* command must have already been sent */ + clear_bit(IPS_IN_ABORT, &ha->flags); + + return (SCSI_ABORT_SNOOZE); + } +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_eh_reset */ +/* */ +/* Routine Description: */ +/* */ +/* Reset the controller (with new eh error code) */ +/* */ +/****************************************************************************/ +int +ips_eh_reset(Scsi_Cmnd *SC) { + ips_ha_t *ha; + ips_scb_t *scb; + + DBG("ips_eh_reset"); + +#ifdef NO_IPS_RESET + return (FAILED); +#else + + if (!SC) { + +#if IPS_DEBUG >= 1 + printk(KERN_NOTICE "Reset called with NULL scsi command\n"); +#endif + + return (FAILED); + } + + ha = (ips_ha_t *) SC->host->hostdata; + + if (!ha) { + +#if IPS_DEBUG >= 1 + printk(KERN_NOTICE "Reset called with NULL ha struct\n"); +#endif + + return (FAILED); + } + + if (!ha->active) + return (FAILED); + + if (test_and_set_bit(IPS_IN_RESET, &ha->flags)) + return (FAILED); + + /* See if the command is on the waiting queue */ + if (ips_removeq_wait(&ha->scb_waitlist, SC) || + ips_removeq_wait(&ha->copp_waitlist, SC)) { + /* command not sent yet */ + clear_bit(IPS_IN_ABORT, &ha->flags); + + return (SUCCESS); + } + + /* + * command must have already been sent + * reset the controller + */ + if (!ips_reset_adapter(ha)) { + clear_bit(IPS_IN_RESET, &ha->flags); + + return (FAILED); + } + + if (!ips_clear_adapter(ha)) { + clear_bit(IPS_IN_RESET, &ha->flags); + + return (FAILED); + } + + /* Now fail all of the active commands */ +#if IPS_DEBUG >= 1 + printk(KERN_WARNING "(%s%d) Failing active commands\n", + ips_name, ha->host_num); +#endif + while ((scb = ips_removeq_scb_head(&ha->scb_activelist))) { + scb->scsi_cmd->result = DID_RESET << 16; + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + ips_freescb(ha, scb); + } + + /* Reset the number of active IOCTLs */ + ha->num_ioctl = 0; + + clear_bit(IPS_IN_RESET, &ha->flags); + + if (!test_bit(IPS_IN_INTR, &ha->flags)) { + /* + * Only execute the next command when + * we are not being called from the + * interrupt handler. The interrupt + * handler wants to do this and since + * interrupts are turned off here.... + */ + ips_next(ha); + } + + return (SUCCESS); + +#endif /* NO_IPS_RESET */ + +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_reset */ +/* */ +/* Routine Description: */ +/* */ +/* Reset the controller */ +/* */ +/****************************************************************************/ +int +ips_reset(Scsi_Cmnd *SC, unsigned int flags) { + ips_ha_t *ha; + ips_scb_t *scb; + + DBG("ips_reset"); + +#ifdef NO_IPS_RESET + return (SCSI_RESET_SNOOZE); +#else + + if (!SC) { + +#if IPS_DEBUG >= 1 + printk(KERN_NOTICE "Reset called with NULL scsi command\n"); +#endif + + return (SCSI_RESET_SNOOZE); + } + + ha = (ips_ha_t *) SC->host->hostdata; + + if (!ha) { + +#if IPS_DEBUG >= 1 + printk(KERN_NOTICE "Reset called with NULL ha struct\n"); +#endif + + return (SCSI_RESET_SNOOZE); + } + + if (!ha->active) + return (SCSI_RESET_SNOOZE); + + if (test_and_set_bit(IPS_IN_RESET, &ha->flags)) + return (SCSI_RESET_SNOOZE); + + /* See if the command is on the waiting queue */ + if (ips_removeq_wait(&ha->scb_waitlist, SC) || + ips_removeq_wait(&ha->copp_waitlist, SC)) { + /* command not sent yet */ + clear_bit(IPS_IN_ABORT, &ha->flags); + + return (SCSI_RESET_SNOOZE); + } + + /* reset the controller */ + if (!ips_reset_adapter(ha)) { + clear_bit(IPS_IN_RESET, &ha->flags); + + return (SCSI_RESET_ERROR); + } + + if (!ips_clear_adapter(ha)) { + clear_bit(IPS_IN_RESET, &ha->flags); + + return (SCSI_RESET_ERROR); + } + + /* Now fail all of the active commands */ +#if IPS_DEBUG >= 1 + printk(KERN_WARNING "(%s%d) Failing active commands\n", + ips_name, ha->host_num); +#endif + while ((scb = ips_removeq_scb_head(&ha->scb_activelist))) { + scb->scsi_cmd->result = DID_RESET << 16; + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + ips_freescb(ha, scb); + } + + /* Reset the number of active IOCTLs */ + ha->num_ioctl = 0; + + clear_bit(IPS_IN_RESET, &ha->flags); + + if (!test_bit(IPS_IN_INTR, &ha->flags)) { + /* + * Only execute the next command when + * we are not being called from the + * interrupt handler. The interrupt + * handler wants to do this and since + * interrupts are turned off here.... + */ + ips_next(ha); + } + + return (SCSI_RESET_SUCCESS); + +#endif /* NO_IPS_RESET */ + +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_queue */ +/* */ +/* Routine Description: */ +/* */ +/* Send a command to the controller */ +/* */ +/****************************************************************************/ +int +ips_queue(Scsi_Cmnd *SC, void (*done) (Scsi_Cmnd *)) { + ips_ha_t *ha; + + DBG("ips_queue"); + + ha = (ips_ha_t *) SC->host->hostdata; + + if (!ha) + return (1); + + if (!ha->active) + return (1); + +#ifndef NO_IPS_CMDLINE + if (ips_is_passthru(SC)) { + if (ha->copp_waitlist.count == IPS_MAX_IOCTL_QUEUE) { + SC->result = DID_BUS_BUSY << 16; + done(SC); + + return (0); + } + } else { +#endif + if (ha->scb_waitlist.count == IPS_MAX_QUEUE) { + SC->result = DID_BUS_BUSY << 16; + done(SC); + + return (0); + } + +#ifndef NO_IPS_CMDLINE + } +#endif + + SC->scsi_done = done; + +#if IPS_DEBUG >= 10 + printk(KERN_NOTICE "%s: ips_queue: cmd 0x%X (%d %d %d)\n", + ips_name, + SC->cmnd[0], + SC->channel, + SC->target, + SC->lun); +#if IPS_DEBUG >= 11 + MDELAY(2*ONE_SEC); +#endif +#endif + +#ifndef NO_IPS_CMDLINE + if (ips_is_passthru(SC)) + ips_putq_wait_tail(&ha->copp_waitlist, SC); + else +#endif + ips_putq_wait_tail(&ha->scb_waitlist, SC); + + if ((!test_bit(IPS_IN_INTR, &ha->flags)) && + (!test_bit(IPS_IN_ABORT, &ha->flags)) && + (!test_bit(IPS_IN_RESET, &ha->flags))) + ips_next(ha); + + return (0); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_biosparam */ +/* */ +/* Routine Description: */ +/* */ +/* Set bios geometry for the controller */ +/* */ +/****************************************************************************/ +int +ips_biosparam(Disk *disk, kdev_t dev, int geom[]) { + ips_ha_t *ha; + int heads; + int sectors; + int cylinders; + + DBG("ips_biosparam"); + + ha = (ips_ha_t *) disk->device->host->hostdata; + + if (!ha) + /* ?!?! host adater info invalid */ + return (0); + + if (!ha->active) + return (0); + + if (!ips_read_adapter_status(ha)) + /* ?!?! Enquiry command failed */ + return (0); + + if ((disk->capacity > 0x400000) && + ((ha->enq->ucMiscFlag & 0x8) == 0)) { + heads = NORM_MODE_HEADS; + sectors = NORM_MODE_SECTORS; + } else { + heads = COMP_MODE_HEADS; + sectors = COMP_MODE_SECTORS; + } + + cylinders = disk->capacity / (heads * sectors); + +#if IPS_DEBUG >= 2 + printk(KERN_NOTICE "Geometry: heads: %d, sectors: %d, cylinders: %d\n", + heads, sectors, cylinders); +#endif + + geom[0] = heads; + geom[1] = sectors; + geom[2] = cylinders; + + return (0); +} + +/****************************************************************************/ +/* */ +/* Routine Name: do_ipsintr */ +/* */ +/* Routine Description: */ +/* */ +/* Wrapper for the interrupt handler */ +/* */ +/****************************************************************************/ +void +do_ipsintr(int irq, void *dev_id, struct pt_regs *regs) { + ips_ha_t *ha; + unsigned int cpu_flags; + + DBG("do_ipsintr"); + + ha = (ips_ha_t *) dev_id; + + spin_lock_irqsave(&io_request_lock, cpu_flags); + + if (test_and_set_bit(IPS_IN_INTR, &ha->flags)) { + spin_unlock_irqrestore(&io_request_lock, cpu_flags); + + return ; + } + + if (!ha) { + clear_bit(IPS_IN_INTR, &ha->flags); + spin_unlock_irqrestore(&io_request_lock, cpu_flags); + + return; + } + + if (!ha->active) { + clear_bit(IPS_IN_INTR, &ha->flags); + spin_unlock_irqrestore(&io_request_lock, cpu_flags); + + return; + } + + ips_intr(ha); + + clear_bit(IPS_IN_INTR, &ha->flags); + + spin_unlock_irqrestore(&io_request_lock, cpu_flags); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_intr */ +/* */ +/* Routine Description: */ +/* */ +/* Polling interrupt handler */ +/* */ +/* ASSUMES interrupts are disabled */ +/* */ +/****************************************************************************/ +void +ips_intr(ips_ha_t *ha) { + ips_stat_t *sp; + ips_scb_t *scb; + int status; + + DBG("ips_intr"); + + if (!ha) + return; + + if (!ha->active) + return; + + while (ips_isintr(ha)) { + sp = &ha->sp; + + if ((status = ips_chkstatus(ha)) < 0) { + /* unexpected interrupt - no ccb */ + printk(KERN_WARNING "(%s%d) Spurious interrupt; no ccb.\n", + ips_name, ha->host_num); + continue ; + } + + scb = (ips_scb_t *) sp->scb_addr; + + /* + * use the callback function to finish things up + * NOTE: interrupts are OFF for this + */ + (*scb->callback) (ha, scb); + } + + clear_bit(IPS_IN_INTR, &ha->flags); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_info */ +/* */ +/* Routine Description: */ +/* */ +/* Return info about the driver */ +/* */ +/****************************************************************************/ +const char * +ips_info(struct Scsi_Host *SH) { + static char buffer[256]; + char *bp; + ips_ha_t *ha; + + DBG("ips_info"); + + ha = HA(SH); + + if (!ha) + return (NULL); + + bp = &buffer[0]; + memset(bp, 0, sizeof(buffer)); + + strcpy(bp, "IBM PCI ServeRAID "); + strcat(bp, IPS_VERSION_HIGH); + strcat(bp, IPS_VERSION_LOW); + + if (ha->ad_type > 0 && + ha->ad_type <= MAX_ADAPTER_NAME) { + strcat(bp, " <"); + strcat(bp, ips_adapter_name[ha->ad_type-1]); + strcat(bp, ">"); + } + + return (bp); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_proc_info */ +/* */ +/* Routine Description: */ +/* */ +/* The passthru interface for the driver */ +/* */ +/****************************************************************************/ +int +ips_proc_info(char *buffer, char **start, off_t offset, + int length, int hostno, int func) { + int i; + int ret; + ips_ha_t *ha = NULL; + + DBG("ips_proc_info"); + + /* Find our host structure */ + for (i = 0; i < ips_num_controllers; i++) { + if (ips_sh[i] && ips_sh[i]->host_no == hostno) { + ha = (ips_ha_t *) ips_sh[i]->hostdata; + + break; + } + } + + if (!ha) + return (-EINVAL); + + if (func) { + /* write */ + return (0); + } else { + /* read */ + if (start) + *start = buffer; + + ret = ips_host_info(ha, buffer, offset, length); + + return (ret); + } +} + +/*--------------------------------------------------------------------------*/ +/* Helper Functions */ +/*--------------------------------------------------------------------------*/ + +#ifndef NO_IPS_CMDLINE + +/****************************************************************************/ +/* */ +/* Routine Name: ips_is_passthru */ +/* */ +/* Routine Description: */ +/* */ +/* Determine if the specified SCSI command is really a passthru command */ +/* */ +/****************************************************************************/ +static int +ips_is_passthru(Scsi_Cmnd *SC) { + DBG("ips_is_passthru"); + + if (!SC) + return (0); + + if ((SC->channel == 0) && + (SC->target == IPS_ADAPTER_ID) && + (SC->lun == 0) && + (SC->cmnd[0] == 0x0d) && + (SC->request_bufflen) && + (!SC->use_sg) && + (((char *) SC->request_buffer)[0] == 'C') && + (((char *) SC->request_buffer)[1] == 'O') && + (((char *) SC->request_buffer)[2] == 'P') && + (((char *) SC->request_buffer)[3] == 'P')) { + return (1); + } else { + return (0); + } +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_is_passthru */ +/* */ +/* Routine Description: */ +/* */ +/* Make a passthru command out of the info in the Scsi block */ +/* */ +/****************************************************************************/ +static int +ips_make_passthru(ips_ha_t *ha, Scsi_Cmnd *SC, ips_scb_t *scb) { + ips_passthru_t *pt; + + DBG("ips_make_passthru"); + + if (!SC->request_bufflen || !SC->request_buffer) { + /* no data */ +#if IPS_DEBUG_PT >= 1 + printk(KERN_NOTICE "(%s%d) No passthru structure\n", + ips_name, ha->host_num); +#endif + + return (IPS_FAILURE); + } + + if (SC->request_bufflen < sizeof(ips_passthru_t)) { + /* wrong size */ +#if IPS_DEBUG_PT >= 1 + printk(KERN_NOTICE "(%s%d) Passthru structure wrong size\n", + ips_name, ha->host_num); +#endif + + return (IPS_FAILURE); + } + + if ((((char *) SC->request_buffer)[0] != 'C') || + (((char *) SC->request_buffer)[1] != 'O') || + (((char *) SC->request_buffer)[2] != 'P') || + (((char *) SC->request_buffer)[3] != 'P')) { + /* signature doesn't match */ +#if IPS_DEBUG_PT >= 1 + printk(KERN_NOTICE "(%s%d) Wrong signature on passthru structure.\n", + ips_name, ha->host_num); +#endif + + return (IPS_FAILURE); + } + + pt = (ips_passthru_t *) SC->request_buffer; + scb->scsi_cmd = SC; + + if (SC->request_bufflen < (sizeof(ips_passthru_t) + pt->CmdBSize)) { + /* wrong size */ +#if IPS_DEBUG_PT >= 1 + printk(KERN_NOTICE "(%s%d) Passthru structure wrong size\n", + ips_name, ha->host_num); +#endif + + return (IPS_FAILURE); + } + + switch (pt->CoppCmd) { + case IPS_NUMCTRLS: + memcpy(SC->request_buffer + sizeof(ips_passthru_t), + &ips_num_controllers, sizeof(int)); + SC->result = DID_OK << 16; + + return (IPS_SUCCESS_IMM); + case IPS_CTRLINFO: + memcpy(SC->request_buffer + sizeof(ips_passthru_t), + ha, sizeof(ips_ha_t)); + SC->result = DID_OK << 16; + + return (IPS_SUCCESS_IMM); + case COPPUSRCMD: + if (ips_usrcmd(ha, pt, scb)) + return (IPS_SUCCESS); + else + return (IPS_FAILURE); + break; + } + + return (IPS_FAILURE); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_usrcmd */ +/* */ +/* Routine Description: */ +/* */ +/* Process a user command and make it ready to send */ +/* */ +/****************************************************************************/ +static int +ips_usrcmd(ips_ha_t *ha, ips_passthru_t *pt, ips_scb_t *scb) { + SG_LIST *sg_list; + + DBG("ips_usrcmd"); + + if ((!scb) || (!pt) || (!ha)) + return (0); + + /* Save the S/G list pointer so it doesn't get clobbered */ + sg_list = scb->sg_list; + + /* copy in the CP */ + memcpy(&scb->cmd, &pt->CoppCP.cmd, sizeof(IOCTL_INFO)); + memcpy(&scb->dcdb, &pt->CoppCP.dcdb, sizeof(DCDB_TABLE)); + + /* FIX stuff that might be wrong */ + scb->sg_list = sg_list; + scb->scb_busaddr = VIRT_TO_BUS(scb); + scb->bus = 0; + scb->target_id = 0; + scb->lun = 0; + scb->sg_len = 0; + scb->data_len = 0; + scb->flags = 0; + scb->op_code = 0; + scb->callback = ipsintr_done; + scb->timeout = ips_cmd_timeout; + scb->cmd.basic_io.command_id = IPS_COMMAND_ID(ha, scb); + + /* we don't support DCDB/READ/WRITE Scatter Gather */ + if ((scb->cmd.basic_io.op_code == READ_SCATTER_GATHER) || + (scb->cmd.basic_io.op_code == WRITE_SCATTER_GATHER) || + (scb->cmd.basic_io.op_code == DIRECT_CDB_SCATTER_GATHER)) + return (0); + + if (pt->CmdBSize && pt->CmdBuffer) { + scb->data_busaddr = VIRT_TO_BUS(scb->scsi_cmd->request_buffer + sizeof(ips_passthru_t)); + } else { + scb->data_busaddr = 0L; + } + + if (pt->CmdBSize) { + if (scb->cmd.dcdb.op_code == DIRECT_CDB) { + scb->cmd.dcdb.dcdb_address = VIRT_TO_BUS(&scb->dcdb); + scb->dcdb.buffer_pointer = scb->data_busaddr; + } else { + scb->cmd.basic_io.sg_addr = scb->data_busaddr; + } + } + + /* set timeouts */ + if (pt->TimeOut) { + scb->timeout = pt->TimeOut; + + if (pt->TimeOut <= 10) + scb->dcdb.cmd_attribute |= TIMEOUT_10; + else if (pt->TimeOut <= 60) + scb->dcdb.cmd_attribute |= TIMEOUT_60; + else + scb->dcdb.cmd_attribute |= TIMEOUT_20M; + } + + /* assume error */ + scb->scsi_cmd->result = DID_ERROR << 16; + + /* success */ + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_cleanup_passthru */ +/* */ +/* Routine Description: */ +/* */ +/* Cleanup after a passthru command */ +/* */ +/****************************************************************************/ +static void +ips_cleanup_passthru(ips_scb_t *scb) { + ips_passthru_t *pt; + + DBG("ips_cleanup_passthru"); + + if ((!scb) || (!scb->scsi_cmd) || (!scb->scsi_cmd->request_buffer)) { +#if IPS_DEBUG_PT >= 1 + printk(KERN_NOTICE "IPS couldn't cleanup\n"); +#endif + + return ; + } + + pt = (ips_passthru_t *) scb->scsi_cmd->request_buffer; + + /* Copy data back to the user */ + pt->BasicStatus = scb->basic_status; + pt->ExtendedStatus = scb->extended_status; + + scb->scsi_cmd->result = DID_OK << 16; +} + +#endif + +/****************************************************************************/ +/* */ +/* Routine Name: ips_host_info */ +/* */ +/* Routine Description: */ +/* */ +/* The passthru interface for the driver */ +/* */ +/****************************************************************************/ +static int +ips_host_info(ips_ha_t *ha, char *ptr, off_t offset, int len) { + INFOSTR info; + + DBG("ips_host_info"); + + info.buffer = ptr; + info.length = len; + info.offset = offset; + info.pos = 0; + + copy_info(&info, "\nIBM ServeRAID General Information:\n\n"); + + if ((ha->nvram->signature == NVRAM_PAGE5_SIGNATURE) && + (ha->nvram->adapter_type != 0)) + copy_info(&info, "\tController Type : %s\n", ips_adapter_name[ha->ad_type-1]); + else + copy_info(&info, "\tController Type : Unknown\n"); + + copy_info(&info, "\tIO port address : 0x%lx\n", ha->io_addr); + copy_info(&info, "\tIRQ number : %d\n", ha->irq); + + if (ha->nvram->signature == NVRAM_PAGE5_SIGNATURE) + copy_info(&info, "\tBIOS Version : %c%c%c%c%c%c%c%c\n", + ha->nvram->bios_high[0], ha->nvram->bios_high[1], + ha->nvram->bios_high[2], ha->nvram->bios_high[3], + ha->nvram->bios_low[0], ha->nvram->bios_low[1], + ha->nvram->bios_low[2], ha->nvram->bios_low[3]); + + copy_info(&info, "\tFirmware Version : %c%c%c%c%c%c%c%c\n", + ha->enq->CodeBlkVersion[0], ha->enq->CodeBlkVersion[1], + ha->enq->CodeBlkVersion[2], ha->enq->CodeBlkVersion[3], + ha->enq->CodeBlkVersion[4], ha->enq->CodeBlkVersion[5], + ha->enq->CodeBlkVersion[6], ha->enq->CodeBlkVersion[7]); + + copy_info(&info, "\tBoot Block Version : %c%c%c%c%c%c%c%c\n", + ha->enq->BootBlkVersion[0], ha->enq->BootBlkVersion[1], + ha->enq->BootBlkVersion[2], ha->enq->BootBlkVersion[3], + ha->enq->BootBlkVersion[4], ha->enq->BootBlkVersion[5], + ha->enq->BootBlkVersion[6], ha->enq->BootBlkVersion[7]); + + copy_info(&info, "\tDriver Version : %s%s\n", + IPS_VERSION_HIGH, IPS_VERSION_LOW); + + copy_info(&info, "\tMax Physical Devices : %d\n", + ha->enq->ucMaxPhysicalDevices); + copy_info(&info, "\tMax Active Commands : %d\n", + ha->max_cmds); + copy_info(&info, "\tCurrent Queued Commands : %d\n", + ha->scb_waitlist.count); + copy_info(&info, "\tCurrent Active Commands : %d\n", + ha->scb_activelist.count - ha->num_ioctl); + copy_info(&info, "\tCurrent Queued PT Commands : %d\n", + ha->copp_waitlist.count); + copy_info(&info, "\tCurrent Active PT Commands : %d\n", + ha->num_ioctl); + + copy_info(&info, "\n"); + + return (info.pos > info.offset ? info.pos - info.offset : 0); +} + +/****************************************************************************/ +/* */ +/* Routine Name: copy_mem_info */ +/* */ +/* Routine Description: */ +/* */ +/* Copy data into an INFOSTR structure */ +/* */ +/****************************************************************************/ +static void +copy_mem_info(INFOSTR *info, char *data, int len) { + DBG("copy_mem_info"); + + if (info->pos + len > info->length) + len = info->length - info->pos; + + if (info->pos + len < info->offset) { + info->pos += len; + return; + } + + if (info->pos < info->offset) { + data += (info->offset - info->pos); + len -= (info->offset - info->pos); + } + + if (len > 0) { + memcpy(info->buffer + info->pos, data, len); + info->pos += len; + } +} + +/****************************************************************************/ +/* */ +/* Routine Name: copy_info */ +/* */ +/* Routine Description: */ +/* */ +/* printf style wrapper for an info structure */ +/* */ +/****************************************************************************/ +static int +copy_info(INFOSTR *info, char *fmt, ...) { + va_list args; + char buf[81]; + int len; + + DBG("copy_info"); + + va_start(args, fmt); + len = vsprintf(buf, fmt, args); + va_end(args); + + copy_mem_info(info, buf, len); + + return (len); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_hainit */ +/* */ +/* Routine Description: */ +/* */ +/* Initialize the controller */ +/* */ +/* NOTE: Assumes to be called from with a lock */ +/* */ +/****************************************************************************/ +static int +ips_hainit(ips_ha_t *ha) { + int i; + + DBG("ips_hainit"); + + if (!ha) + return (0); + + /* initialize status queue */ + ips_statinit(ha); + + /* Setup HBA ID's */ + if (!ips_read_config(ha)) { + +#ifndef NO_IPS_RESET + + /* Try to reset the controller and try again */ + if (!ips_reset_adapter(ha)) { + printk(KERN_WARNING "(%s%d) unable to reset controller.\n", + ips_name, ha->host_num); + + return (0); + } + + if (!ips_clear_adapter(ha)) { + printk(KERN_WARNING "(%s%d) unable to initialize controller.\n", + ips_name, ha->host_num); + + return (0); + } + +#endif + + if (!ips_read_config(ha)) { + printk(KERN_WARNING "(%s%d) unable to read config from controller.\n", + ips_name, ha->host_num); + + return (0); + } + } /* end if */ + + /* write driver version */ + if (!ips_write_driver_status(ha)) { + printk(KERN_WARNING "(%s%d) unable to write driver info to controller.\n", + ips_name, ha->host_num); + + return (0); + } + + if (!ips_read_adapter_status(ha)) { + printk(KERN_WARNING "(%s%d) unable to read controller status.\n", + ips_name, ha->host_num); + + return (0); + } + + if (!ips_read_subsystem_parameters(ha)) { + printk(KERN_WARNING "(%s%d) unable to read subsystem parameters.\n", + ips_name, ha->host_num); + + return (0); + } + + /* set limits on SID, LUN, BUS */ + ha->ntargets = MAX_TARGETS + 1; + ha->nlun = 1; + ha->nbus = (ha->enq->ucMaxPhysicalDevices / MAX_TARGETS); + + switch (ha->conf->logical_drive[0].ucStripeSize) { + case 4: + ha->max_xfer = 0x10000; + break; + + case 5: + ha->max_xfer = 0x20000; + break; + + case 6: + ha->max_xfer = 0x40000; + break; + + case 7: + default: + ha->max_xfer = 0x80000; + break; + } + + /* setup max concurrent commands */ + if (ha->subsys->param[4] & 0x1) { + /* Use the new method */ + ha->max_cmds = ha->enq->ucConcurrentCmdCount; + } else { + /* use the old method */ + switch (ha->conf->logical_drive[0].ucStripeSize) { + case 4: + ha->max_cmds = 32; + break; + + case 5: + ha->max_cmds = 16; + break; + + case 6: + ha->max_cmds = 8; + break; + + case 7: + default: + ha->max_cmds = 4; + break; + } + } + + /* set controller IDs */ + ha->ha_id[0] = IPS_ADAPTER_ID; + for (i = 1; i < ha->nbus; i++) + ha->ha_id[i] = ha->conf->init_id[i-1] & 0x1f; + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_next */ +/* */ +/* Routine Description: */ +/* */ +/* Take the next command off the queue and send it to the controller */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static void +ips_next(ips_ha_t *ha) { + ips_scb_t *scb; + Scsi_Cmnd *SC; + int ret; + + DBG("ips_next"); + + if (!ha) + return ; + +#ifndef NO_IPS_CMDLINE + /* + * Send passthru commands + * These have priority over normal I/O + * but shouldn't affect performance too much + * since we limit the number that can be active + * on the card at any one time + */ + while ((ha->num_ioctl < IPS_MAX_IOCTL) && + (ha->copp_waitlist.head) && + (scb = ips_getscb(ha))) { + SC = ips_removeq_wait_head(&ha->copp_waitlist); + + ret = ips_make_passthru(ha, SC, scb); + + switch (ret) { + case IPS_FAILURE: + if (scb->scsi_cmd) { + scb->scsi_cmd->result = DID_ERROR << 16; + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + } + ips_freescb(ha, scb); + break; + case IPS_SUCCESS_IMM: + if (scb->scsi_cmd) + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + ips_freescb(ha, scb); + break; + default: + break; + } /* end case */ + + if (ret != IPS_SUCCESS) + continue; + + ret = ips_send_cmd(ha, scb); + + if (ret == IPS_SUCCESS) { + ips_putq_scb_head(&ha->scb_activelist, scb); + ha->num_ioctl++; + } + + switch(ret) { + case IPS_FAILURE: + if (scb->scsi_cmd) { + scb->scsi_cmd->result = DID_ERROR << 16; + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + } + + ips_freescb(ha, scb); + break; + case IPS_SUCCESS_IMM: + if (scb->scsi_cmd) + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + ips_freescb(ha, scb); + break; + default: + break; + } /* end case */ + } +#endif + + /* + * Send "Normal" I/O commands + */ + while ((ha->scb_waitlist.head) && + (scb = ips_getscb(ha))) { + SC = ips_removeq_wait_head(&ha->scb_waitlist); + + SC->result = DID_OK; + SC->host_scribble = NULL; + + memset(SC->sense_buffer, 0, sizeof(SC->sense_buffer)); + + scb->target_id = SC->target; + scb->lun = SC->lun; + scb->bus = SC->channel; + scb->scsi_cmd = SC; + scb->breakup = 0; + scb->data_len = 0; + scb->callback = ipsintr_done; + scb->timeout = ips_cmd_timeout; + memset(&scb->cmd, 0, 4); + + /* copy in the CDB */ + memcpy(scb->cdb, SC->cmnd, SC->cmd_len); + + /* Now handle the data buffer */ + if (SC->use_sg) { + struct scatterlist *sg; + int i; + + sg = SC->request_buffer; + + for (i = 0; i < SC->use_sg; i++) { + scb->sg_list[i].address = VIRT_TO_BUS(sg[i].address); + scb->sg_list[i].length = sg[i].length; + + if (scb->data_len + sg[i].length > ha->max_xfer) { + /* + * Data Breakup required + */ + scb->breakup = i; + break; + } + + scb->data_len += sg[i].length; + } + + if (!scb->breakup) + scb->sg_len = SC->use_sg; + else + scb->sg_len = scb->breakup; + + scb->dcdb.transfer_length = scb->data_len; + scb->data_busaddr = VIRT_TO_BUS(scb->sg_list); + } else { + if (SC->request_bufflen) { + if (SC->request_bufflen > ha->max_xfer) { + /* + * Data breakup required + */ + scb->breakup = 1; + scb->data_len = ha->max_xfer; + } else { + scb->data_len = SC->request_bufflen; + } + + scb->dcdb.transfer_length = scb->data_len; + scb->data_busaddr = VIRT_TO_BUS(SC->request_buffer); + scb->sg_len = 0; + } else { + scb->data_busaddr = 0L; + scb->sg_len = 0; + scb->data_len = 0; + scb->dcdb.transfer_length = 0; + } + + } + + if ((scb->scsi_cmd->request.cmd == READ) && (SC->request_bufflen)) + scb->dcdb.cmd_attribute |= DATA_IN; + + if ((scb->scsi_cmd->request.cmd == WRITE) && (SC->request_bufflen)) + scb->dcdb.cmd_attribute |= DATA_OUT; + + if (scb->data_len >= IPS_MAX_XFER) { + scb->dcdb.cmd_attribute |= TRANSFER_64K; + scb->dcdb.transfer_length = 0; + } + + ret = ips_send_cmd(ha, scb); + + if (ret == IPS_SUCCESS) + ips_putq_scb_head(&ha->scb_activelist, scb); + + switch(ret) { + case IPS_FAILURE: + if (scb->scsi_cmd) { + scb->scsi_cmd->result = DID_ERROR << 16; + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + } + + ips_freescb(ha, scb); + break; + case IPS_SUCCESS_IMM: + if (scb->scsi_cmd) + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + ips_freescb(ha, scb); + break; + default: + break; + } /* end case */ + } /* end while */ +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_putq_scb_head */ +/* */ +/* Routine Description: */ +/* */ +/* Add an item to the head of the queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline void +ips_putq_scb_head(ips_scb_queue_t *queue, ips_scb_t *item) { + DBG("ips_putq_scb_head"); + + if (!item) + return ; + + item->q_next = queue->head; + queue->head = item; + + if (!queue->tail) + queue->tail = item; + + queue->count++; +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_putq_scb_tail */ +/* */ +/* Routine Description: */ +/* */ +/* Add an item to the tail of the queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline void +ips_putq_scb_tail(ips_scb_queue_t *queue, ips_scb_t *item) { + DBG("ips_putq_scb_tail"); + + if (!item) + return ; + + item->q_next = NULL; + + if (queue->tail) + queue->tail->q_next = item; + + queue->tail = item; + + if (!queue->head) + queue->head = item; + + queue->count++; +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_removeq_scb_head */ +/* */ +/* Routine Description: */ +/* */ +/* Remove the head of the queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline ips_scb_t * +ips_removeq_scb_head(ips_scb_queue_t *queue) { + ips_scb_t *item; + + DBG("ips_removeq_scb_head"); + + item = queue->head; + + if (!item) + return (NULL); + + queue->head = item->q_next; + item->q_next = NULL; + + if (queue->tail == item) + queue->tail = NULL; + + queue->count--; + + return (item); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_removeq_scb */ +/* */ +/* Routine Description: */ +/* */ +/* Remove an item from a queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline ips_scb_t * +ips_removeq_scb(ips_scb_queue_t *queue, ips_scb_t *item) { + ips_scb_t *p; + + DBG("ips_removeq_scb"); + + if (!item) + return (NULL); + + if (item == queue->head) + return (ips_removeq_scb_head(queue)); + + p = queue->head; + + while ((p) && (item != p->q_next)) + p = p->q_next; + + if (p) { + /* found a match */ + p->q_next = item->q_next; + + if (!item->q_next) + queue->tail = p; + + item->q_next = NULL; + queue->count--; + + return (item); + } + + return (NULL); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_putq_wait_head */ +/* */ +/* Routine Description: */ +/* */ +/* Add an item to the head of the queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline void +ips_putq_wait_head(ips_wait_queue_t *queue, Scsi_Cmnd *item) { + DBG("ips_putq_wait_head"); + + if (!item) + return ; + + item->host_scribble = (char *) queue->head; + queue->head = item; + + if (!queue->tail) + queue->tail = item; + + queue->count++; +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_putq_wait_tail */ +/* */ +/* Routine Description: */ +/* */ +/* Add an item to the tail of the queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline void +ips_putq_wait_tail(ips_wait_queue_t *queue, Scsi_Cmnd *item) { + DBG("ips_putq_wait_tail"); + + if (!item) + return ; + + item->host_scribble = NULL; + + if (queue->tail) + queue->tail->host_scribble = (char *)item; + + queue->tail = item; + + if (!queue->head) + queue->head = item; + + queue->count++; +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_removeq_wait_head */ +/* */ +/* Routine Description: */ +/* */ +/* Remove the head of the queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline Scsi_Cmnd * +ips_removeq_wait_head(ips_wait_queue_t *queue) { + Scsi_Cmnd *item; + + DBG("ips_removeq_wait_head"); + + item = queue->head; + + if (!item) + return (NULL); + + queue->head = (Scsi_Cmnd *) item->host_scribble; + item->host_scribble = NULL; + + if (queue->tail == item) + queue->tail = NULL; + + queue->count--; + + return (item); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_removeq_wait */ +/* */ +/* Routine Description: */ +/* */ +/* Remove an item from a queue */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static inline Scsi_Cmnd * +ips_removeq_wait(ips_wait_queue_t *queue, Scsi_Cmnd *item) { + Scsi_Cmnd *p; + + DBG("ips_removeq_wait"); + + if (!item) + return (NULL); + + if (item == queue->head) + return (ips_removeq_wait_head(queue)); + + p = queue->head; + + while ((p) && (item != (Scsi_Cmnd *) p->host_scribble)) + p = (Scsi_Cmnd *) p->host_scribble; + + if (p) { + /* found a match */ + p->host_scribble = item->host_scribble; + + if (!item->host_scribble) + queue->tail = p; + + item->host_scribble = NULL; + queue->count--; + + return (item); + } + + return (NULL); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ipsintr_blocking */ +/* */ +/* Routine Description: */ +/* */ +/* Finalize an interrupt for internal commands */ +/* */ +/****************************************************************************/ +static void +ipsintr_blocking(ips_ha_t *ha, ips_scb_t *scb) { + DBG("ipsintr_blocking"); + + if ((ha->waitflag == TRUE) && + (ha->cmd_in_progress == scb->cdb[0])) { + ha->waitflag = FALSE; + + return ; + } +} + +/****************************************************************************/ +/* */ +/* Routine Name: ipsintr_done */ +/* */ +/* Routine Description: */ +/* */ +/* Finalize an interrupt for non-internal commands */ +/* */ +/****************************************************************************/ +static void +ipsintr_done(ips_ha_t *ha, ips_scb_t *scb) { + DBG("ipsintr_done"); + + if (scb->scsi_cmd == NULL) { + /* unexpected interrupt */ + printk(KERN_WARNING "(%s%d) Spurious interrupt; scsi_cmd not set.\n", + ips_name, ha->host_num); + + return; + } + + ips_done(ha, scb); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_done */ +/* */ +/* Routine Description: */ +/* */ +/* Do housekeeping on completed commands */ +/* */ +/****************************************************************************/ +static void +ips_done(ips_ha_t *ha, ips_scb_t *scb) { + int ret; + + DBG("ips_done"); + + if (!scb) + return ; + +#ifndef NO_IPS_CMDLINE + if ((scb->scsi_cmd) && (ips_is_passthru(scb->scsi_cmd))) { + ips_cleanup_passthru(scb); + ha->num_ioctl--; + } else { +#endif + /* + * Check to see if this command had too much + * data and had to be broke up. If so, queue + * the rest of the data and continue. + */ + if (scb->breakup) { + /* we had a data breakup */ + u16 bk_save; + + bk_save = scb->breakup; + scb->breakup = 0; + + if (scb->scsi_cmd->use_sg) { + /* S/G request */ + struct scatterlist *sg; + int i; + + sg = scb->scsi_cmd->request_buffer; + + scb->data_len = 0; + + for (i = bk_save; i < scb->scsi_cmd->use_sg; i++) { + scb->sg_list[i - bk_save].address = VIRT_TO_BUS(sg[i].address); + scb->sg_list[i - bk_save].length = sg[i].length; + + if (scb->data_len + sg[i].length > ha->max_xfer) { + /* + * Data Breakup required + */ + scb->breakup = i; + break; + } + + scb->data_len += sg[i].length; + } + + if (!scb->breakup) + scb->sg_len = scb->scsi_cmd->use_sg - bk_save; + else + scb->sg_len = scb->breakup - bk_save; + + scb->dcdb.transfer_length = scb->data_len; + scb->data_busaddr = VIRT_TO_BUS(scb->sg_list); + } else { + /* Non S/G Request */ + if (scb->scsi_cmd->request_bufflen - (bk_save * ha->max_xfer)) { + /* Further breakup required */ + scb->data_len = ha->max_xfer; + scb->data_busaddr = VIRT_TO_BUS(scb->scsi_cmd->request_buffer + (bk_save * ha->max_xfer)); + scb->breakup = bk_save + 1; + } else { + scb->data_len = scb->scsi_cmd->request_bufflen - (bk_save * ha->max_xfer); + scb->data_busaddr = VIRT_TO_BUS(scb->scsi_cmd->request_buffer + (bk_save * ha->max_xfer)); + } + + scb->dcdb.transfer_length = scb->data_len; + scb->sg_len = 0; + } + + if ((scb->scsi_cmd->request.cmd == READ) && (scb->data_len)) + scb->dcdb.cmd_attribute |= DATA_IN; + + if ((scb->scsi_cmd->request.cmd == WRITE) && (scb->data_len)) + scb->dcdb.cmd_attribute |= DATA_OUT; + + if (scb->data_len >= IPS_MAX_XFER) { + scb->dcdb.cmd_attribute |= TRANSFER_64K; + scb->dcdb.transfer_length = 0; + } + + ret = ips_send_cmd(ha, scb); + + switch(ret) { + case IPS_FAILURE: + if (scb->scsi_cmd) { + scb->scsi_cmd->result = DID_ERROR << 16; + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + } + + ips_freescb(ha, scb); + break; + case IPS_SUCCESS_IMM: + if (scb->scsi_cmd) { + scb->scsi_cmd->result = DID_ERROR << 16; + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + } + + ips_freescb(ha, scb); + break; + default: + break; + } /* end case */ + + return ; + } +#ifndef NO_IPS_CMDLINE + } /* end if passthru */ +#endif + + /* call back to SCSI layer */ + scb->scsi_cmd->scsi_done(scb->scsi_cmd); + ips_freescb(ha, scb); + + /* do the next command */ + ips_next(ha); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_map_status */ +/* */ +/* Routine Description: */ +/* */ +/* Map ServeRAID error codes to Linux Error Codes */ +/* */ +/****************************************************************************/ +static int +ips_map_status(ips_scb_t *scb, ips_stat_t *sp) { + int errcode; + + DBG("ips_map_status"); + + if (scb->bus) { + /* copy SCSI status and sense data for DCDB commands */ + memcpy(scb->scsi_cmd->sense_buffer, scb->dcdb.sense_info, + sizeof(scb->scsi_cmd->sense_buffer)); + scb->scsi_cmd->result = scb->dcdb.scsi_status; + } else + scb->scsi_cmd->result = 0; + + /* default driver error */ + errcode = DID_ERROR; + + switch (scb->basic_status & GSC_STATUS_MASK) { + case CMD_TIMEOUT: + errcode = DID_TIME_OUT; + break; + + case INVAL_OPCO: + case INVAL_CMD_BLK: + case INVAL_PARM_BLK: + case LOG_DRV_ERROR: + case CMD_CMPLT_WERROR: + break; + + case PHYS_DRV_ERROR: + /* + * For physical drive errors that + * are not on a logical drive should + * be DID_OK. The SCSI errcode will + * show what the real error is. + */ + if (scb->bus) + errcode = DID_OK; + + switch (scb->extended_status) { + case SELECTION_TIMEOUT: + if (scb->bus) { + scb->scsi_cmd->result |= DID_TIME_OUT << 16; + + return (0); + } + break; + case DATA_OVER_UNDER_RUN: + if ((scb->bus) && (scb->dcdb.transfer_length < scb->data_len)) { + if ((scb->scsi_cmd->cmnd[0] == INQUIRY) && + ((((char *) scb->scsi_cmd->buffer)[0] & 0x1f) == TYPE_DISK)) { + /* underflow -- no error */ + /* restrict access to physical DASD */ + errcode = DID_TIME_OUT; + break; + } + + /* normal underflow Occured */ + if (scb->dcdb.transfer_length >= scb->scsi_cmd->underflow) { + scb->scsi_cmd->result |= DID_OK << 16; + + return (0); + } + } + + break; + case EXT_RECOVERY: + /* don't fail recovered errors */ + if (scb->bus) { + scb->scsi_cmd->result |= DID_OK << 16; + + return (0); + } + break; + + case EXT_HOST_RESET: + case EXT_DEVICE_RESET: + errcode = DID_RESET; + break; + + case EXT_CHECK_CONDITION: + break; + } /* end switch */ + } /* end switch */ + + scb->scsi_cmd->result |= (errcode << 16); + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_send */ +/* */ +/* Routine Description: */ +/* */ +/* Wrapper for ips_send_cmd */ +/* */ +/****************************************************************************/ +static int +ips_send(ips_ha_t *ha, ips_scb_t *scb, scb_callback callback) { + int ret; + + DBG("ips_send"); + + scb->callback = callback; + + ret = ips_send_cmd(ha, scb); + + return (ret); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_send_wait */ +/* */ +/* Routine Description: */ +/* */ +/* Send a command to the controller and wait for it to return */ +/* */ +/****************************************************************************/ +static int +ips_send_wait(ips_ha_t *ha, ips_scb_t *scb, int timeout) { + int ret; + + DBG("ips_send_wait"); + + ha->waitflag = TRUE; + ha->cmd_in_progress = scb->cdb[0]; + + ret = ips_send(ha, scb, ipsintr_blocking); + + if ((ret == IPS_FAILURE) || (ret == IPS_SUCCESS_IMM)) + return (ret); + + ret = ips_wait(ha, timeout, IPS_INTR_OFF); + + return (ret); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_send_cmd */ +/* */ +/* Routine Description: */ +/* */ +/* Map SCSI commands to ServeRAID commands for logical drives */ +/* */ +/****************************************************************************/ +static int +ips_send_cmd(ips_ha_t *ha, ips_scb_t *scb) { + int ret; + + DBG("ips_send_cmd"); + + ret = IPS_SUCCESS; + + if (!scb->scsi_cmd) { + /* internal command */ + + if (scb->bus > 0) { + /* ServeRAID commands can't be issued */ + /* to real devices -- fail them */ + if ((ha->waitflag == TRUE) && + (ha->cmd_in_progress == scb->cdb[0])) { + ha->waitflag = FALSE; + } + + return (1); + } +#ifndef NO_IPS_CMDLINE + } else if ((scb->bus == 0) && (!ips_is_passthru(scb->scsi_cmd))) { +#else + } else if (scb->bus == 0) { +#endif + /* command to logical bus -- interpret */ + ret = IPS_SUCCESS_IMM; + + switch (scb->scsi_cmd->cmnd[0]) { + case ALLOW_MEDIUM_REMOVAL: + case REZERO_UNIT: + case ERASE: + case WRITE_FILEMARKS: + case SPACE: + scb->scsi_cmd->result = DID_ERROR << 16; + break; + + case START_STOP: + scb->scsi_cmd->result = DID_OK << 16; + + case TEST_UNIT_READY: + case INQUIRY: + if (scb->target_id == IPS_ADAPTER_ID) { + /* + * Either we have a TUR + * or we have a SCSI inquiry + */ + if (scb->scsi_cmd->cmnd[0] == TEST_UNIT_READY) + scb->scsi_cmd->result = DID_OK << 16; + + if (scb->scsi_cmd->cmnd[0] == INQUIRY) { + IPS_INQUIRYDATA inq; + + memset(&inq, 0, sizeof(IPS_INQUIRYDATA)); + + inq.DeviceType = TYPE_PROCESSOR; + inq.DeviceTypeQualifier = 0; + inq.RemoveableMedia = 0; + inq.Versions = 0x1; /* SCSI I */ + inq.AdditionalLength = 31; + strncpy(inq.VendorId, "IBM ", 8); + strncpy(inq.ProductId, "SERVERAID ", 16); + strncpy(inq.ProductRevisionLevel, "1.00", 4); + + memcpy(scb->scsi_cmd->request_buffer, &inq, scb->scsi_cmd->request_bufflen); + + scb->scsi_cmd->result = DID_OK << 16; + } + } else { + scb->cmd.logical_info.op_code = GET_LOGICAL_DRIVE_INFO; + scb->cmd.logical_info.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.logical_info.buffer_addr = VIRT_TO_BUS(&ha->adapt->logical_drive_info); + scb->cmd.logical_info.reserved = 0; + scb->cmd.logical_info.reserved2 = 0; + ret = IPS_SUCCESS; + } + + break; + + case REQUEST_SENSE: + ips_reqsen(ha, scb); + scb->scsi_cmd->result = DID_OK << 16; + break; + + case READ_6: + case WRITE_6: + if (!scb->sg_len) { + scb->cmd.basic_io.op_code = + (scb->scsi_cmd->cmnd[0] == READ_6) ? IPS_READ : IPS_WRITE; + } else { + scb->cmd.basic_io.op_code = + (scb->scsi_cmd->cmnd[0] == READ_6) ? READ_SCATTER_GATHER : WRITE_SCATTER_GATHER; + } + + scb->cmd.basic_io.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.basic_io.log_drv = scb->target_id; + scb->cmd.basic_io.sg_count = scb->sg_len; + scb->cmd.basic_io.sg_addr = scb->data_busaddr; + + if (scb->cmd.basic_io.lba) + scb->cmd.basic_io.lba += scb->cmd.basic_io.sector_count; + else + scb->cmd.basic_io.lba = (((scb->scsi_cmd->cmnd[1] & 0x1f) << 16) | + (scb->scsi_cmd->cmnd[2] << 8) | + (scb->scsi_cmd->cmnd[3])); + + scb->cmd.basic_io.sector_count = scb->data_len / IPS_BLKSIZE; + + if (scb->cmd.basic_io.sector_count == 0) + scb->cmd.basic_io.sector_count = 256; + + scb->cmd.basic_io.reserved = 0; + ret = IPS_SUCCESS; + break; + + case READ_10: + case WRITE_10: + if (!scb->sg_len) { + scb->cmd.basic_io.op_code = + (scb->scsi_cmd->cmnd[0] == READ_10) ? IPS_READ : IPS_WRITE; + } else { + scb->cmd.basic_io.op_code = + (scb->scsi_cmd->cmnd[0] == READ_10) ? READ_SCATTER_GATHER : WRITE_SCATTER_GATHER; + } + + scb->cmd.basic_io.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.basic_io.log_drv = scb->target_id; + scb->cmd.basic_io.sg_count = scb->sg_len; + scb->cmd.basic_io.sg_addr = scb->data_busaddr; + + if (scb->cmd.basic_io.lba) + scb->cmd.basic_io.lba += scb->cmd.basic_io.sector_count; + else + scb->cmd.basic_io.lba = ((scb->scsi_cmd->cmnd[2] << 24) | + (scb->scsi_cmd->cmnd[3] << 16) | + (scb->scsi_cmd->cmnd[4] << 8) | + scb->scsi_cmd->cmnd[5]); + + scb->cmd.basic_io.sector_count = scb->data_len / IPS_BLKSIZE; + + scb->cmd.basic_io.reserved = 0; + + if (scb->cmd.basic_io.sector_count == 0) { + /* + * This is a null condition + * we don't have to do anything + * so just return + */ + scb->scsi_cmd->result = DID_OK << 16; + } else + ret = IPS_SUCCESS; + + break; + + case RESERVE: + case RELEASE: + scb->scsi_cmd->result = DID_OK << 16; + break; + + case MODE_SENSE: + scb->cmd.basic_io.op_code = ENQUIRY; + scb->cmd.basic_io.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.basic_io.sg_addr = VIRT_TO_BUS(ha->enq); + ret = IPS_SUCCESS; + break; + + case READ_CAPACITY: + scb->cmd.logical_info.op_code = GET_LOGICAL_DRIVE_INFO; + scb->cmd.logical_info.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.logical_info.buffer_addr = VIRT_TO_BUS(&ha->adapt->logical_drive_info); + scb->cmd.logical_info.reserved = 0; + scb->cmd.logical_info.reserved2 = 0; + scb->cmd.logical_info.reserved3 = 0; + ret = IPS_SUCCESS; + break; + + case SEND_DIAGNOSTIC: + case REASSIGN_BLOCKS: + case FORMAT_UNIT: + case SEEK_10: + case VERIFY: + case READ_DEFECT_DATA: + case READ_BUFFER: + case WRITE_BUFFER: + scb->scsi_cmd->result = DID_OK << 16; + break; + + default: + scb->scsi_cmd->result = DID_ERROR << 16; + break; + } /* end switch */ + } /* end if */ + + if (ret == IPS_SUCCESS_IMM) + return (ret); + + /* setup DCDB */ + if (scb->bus > 0) { + if (!scb->sg_len) + scb->cmd.dcdb.op_code = DIRECT_CDB; + else + scb->cmd.dcdb.op_code = DIRECT_CDB_SCATTER_GATHER; + + scb->cmd.dcdb.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.dcdb.dcdb_address = VIRT_TO_BUS(&scb->dcdb); + scb->cmd.dcdb.reserved = 0; + scb->cmd.dcdb.reserved2 = 0; + scb->cmd.dcdb.reserved3 = 0; + + scb->dcdb.device_address = ((scb->bus - 1) << 4) | scb->target_id; + scb->dcdb.cmd_attribute |= DISCONNECT_ALLOWED; + + if (scb->timeout) { + if (scb->timeout <= 10) + scb->dcdb.cmd_attribute |= TIMEOUT_10; + else if (scb->timeout <= 60) + scb->dcdb.cmd_attribute |= TIMEOUT_60; + else + scb->dcdb.cmd_attribute |= TIMEOUT_20M; + } + + if (!(scb->dcdb.cmd_attribute & TIMEOUT_20M)) + scb->dcdb.cmd_attribute |= TIMEOUT_20M; + + scb->dcdb.sense_length = sizeof(scb->scsi_cmd->sense_buffer); + scb->dcdb.buffer_pointer = scb->data_busaddr; + scb->dcdb.sg_count = scb->sg_len; + scb->dcdb.cdb_length = scb->scsi_cmd->cmd_len; + memcpy(scb->dcdb.scsi_cdb, scb->scsi_cmd->cmnd, scb->scsi_cmd->cmd_len); + } + + return (ips_issue(ha, scb)); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_chk_status */ +/* */ +/* Routine Description: */ +/* */ +/* Check the status of commands to logical drives */ +/* */ +/****************************************************************************/ +static int +ips_chkstatus(ips_ha_t *ha) { + ips_scb_t *scb; + ips_stat_t *sp = &ha->sp; + u8 basic_status; + u8 ext_status; + int command_id; + int errcode; + int ret; + + DBG("ips_chkstatus"); + + command_id = ips_statupd(ha); + + if (command_id > (MAX_CMDS-1)) { + printk(KERN_NOTICE "(%s%d) invalid command id received: %d\n", + ips_name, ha->host_num, command_id); + + return (-1); + } + + scb = &ha->scbs[command_id]; + sp->scb_addr = (u32) scb; + sp->residue_len = 0; + scb->basic_status = basic_status = ha->adapt->p_status_tail->basic_status & BASIC_STATUS_MASK; + scb->extended_status = ext_status = ha->adapt->p_status_tail->extended_status; + + /* Remove the item from the active queue */ + ips_removeq_scb(&ha->scb_activelist, scb); + + if (!scb->scsi_cmd) + /* internal commands are handled in do_ipsintr */ + return (0); + +#ifndef NO_IPS_CMDLINE + if ((scb->scsi_cmd) && (ips_is_passthru(scb->scsi_cmd))) + /* passthru - just returns the raw result */ + return (0); +#endif + + errcode = DID_OK; + ret = 0; + + if (((basic_status & GSC_STATUS_MASK) == SSUCCESS) || + ((basic_status & GSC_STATUS_MASK) == RECOVERED_ERROR)) { + + if (scb->bus == 0) { +#if IPS_DEBUG >= 1 + if ((basic_status & GSC_STATUS_MASK) == RECOVERED_ERROR) { + printk(KERN_NOTICE "(%s%d) Recovered Logical Drive Error OpCode: %x, BSB: %x, ESB: %x\n", + ips_name, ha->host_num, + scb->cmd.basic_io.op_code, basic_status, ext_status); + } +#endif + + switch (scb->scsi_cmd->cmnd[0]) { + case ALLOW_MEDIUM_REMOVAL: + case REZERO_UNIT: + case ERASE: + case WRITE_FILEMARKS: + case SPACE: + errcode = DID_ERROR; + ret = 1; + break; + + case START_STOP: + break; + + case TEST_UNIT_READY: + if (!ips_online(ha, scb)) { + errcode = DID_TIME_OUT; + ret = 1; + } + break; + + case INQUIRY: + if (ips_online(ha, scb)) { + ips_inquiry(ha, scb); + } else { + errcode = DID_TIME_OUT; + ret = 1; + } + break; + + case REQUEST_SENSE: + ips_reqsen(ha, scb); + break; + + case READ_6: + case WRITE_6: + case READ_10: + case WRITE_10: + case RESERVE: + case RELEASE: + break; + + case MODE_SENSE: + if (!ips_online(ha, scb) || !ips_msense(ha, scb)) { + errcode = DID_ERROR; + ret = 1; + } + break; + + case READ_CAPACITY: + if (ips_online(ha, scb)) + ips_rdcap(ha, scb); + else { + errcode = DID_TIME_OUT; + ret = 1; + } + break; + + case SEND_DIAGNOSTIC: + case REASSIGN_BLOCKS: + break; + + case FORMAT_UNIT: + errcode = DID_ERROR; + ret = 1; + break; + + case SEEK_10: + case VERIFY: + case READ_DEFECT_DATA: + case READ_BUFFER: + case WRITE_BUFFER: + break; + + default: + errcode = DID_ERROR; + ret = 1; + } /* end switch */ + + scb->scsi_cmd->result = errcode << 16; + } else { /* bus == 0 */ + /* restrict access to physical drives */ + if ((scb->scsi_cmd->cmnd[0] == INQUIRY) && + ((((char *) scb->scsi_cmd->buffer)[0] & 0x1f) == TYPE_DISK)) { + + scb->scsi_cmd->result = DID_TIME_OUT << 16; + + ret = 1; + } + } /* else */ + } else { /* recovered error / success */ +#if IPS_DEBUG >= 1 + if (scb->bus == 0) { + printk(KERN_NOTICE "(%s%d) Unrecovered Logical Drive Error OpCode: %x, BSB: %x, ESB: %x\n", + ips_name, ha->host_num, + scb->cmd.basic_io.op_code, basic_status, ext_status); + } +#endif + + ret = ips_map_status(scb, sp); + } /* else */ + + return (ret); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_online */ +/* */ +/* Routine Description: */ +/* */ +/* Determine if a logical drive is online */ +/* */ +/****************************************************************************/ +static int +ips_online(ips_ha_t *ha, ips_scb_t *scb) { + DBG("ips_online"); + + if (scb->target_id >= MAX_LOGICAL_DRIVES) + return (0); + + if ((scb->basic_status & GSC_STATUS_MASK) > 1) { + memset(&ha->adapt->logical_drive_info, 0, sizeof(ha->adapt->logical_drive_info)); + + return (0); + } + + if (scb->target_id < ha->adapt->logical_drive_info.no_of_log_drive && + ha->adapt->logical_drive_info.drive_info[scb->target_id].state != OFF_LINE && + ha->adapt->logical_drive_info.drive_info[scb->target_id].state != FREE && + ha->adapt->logical_drive_info.drive_info[scb->target_id].state != CRS && + ha->adapt->logical_drive_info.drive_info[scb->target_id].state != SYS) + return (1); + else + return (0); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_inquiry */ +/* */ +/* Routine Description: */ +/* */ +/* Simulate an inquiry command to a logical drive */ +/* */ +/****************************************************************************/ +static int +ips_inquiry(ips_ha_t *ha, ips_scb_t *scb) { + IPS_INQUIRYDATA inq; + + DBG("ips_inquiry"); + + memset(&inq, 0, sizeof(IPS_INQUIRYDATA)); + + inq.DeviceType = TYPE_DISK; + inq.DeviceTypeQualifier = 0; + inq.RemoveableMedia = 0; + inq.Versions = 0x1; /* SCSI I */ + inq.AdditionalLength = 31; + strncpy(inq.VendorId, "IBM ", 8); + strncpy(inq.ProductId, "SERVERAID ", 16); + strncpy(inq.ProductRevisionLevel, "1.00", 4); + + memcpy(scb->scsi_cmd->request_buffer, &inq, scb->scsi_cmd->request_bufflen); + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_rdcap */ +/* */ +/* Routine Description: */ +/* */ +/* Simulate a read capacity command to a logical drive */ +/* */ +/****************************************************************************/ +static int +ips_rdcap(ips_ha_t *ha, ips_scb_t *scb) { + CAPACITY_T *cap; + + DBG("ips_rdcap"); + + if (scb->scsi_cmd->bufflen < 8) + return (0); + + cap = (CAPACITY_T *) scb->scsi_cmd->request_buffer; + + cap->lba = htonl(ha->adapt->logical_drive_info.drive_info[scb->target_id].sector_count - 1); + cap->len = htonl((u32) IPS_BLKSIZE); + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_msense */ +/* */ +/* Routine Description: */ +/* */ +/* Simulate a mode sense command to a logical drive */ +/* */ +/****************************************************************************/ +static int +ips_msense(ips_ha_t *ha, ips_scb_t *scb) { + u16 heads; + u16 sectors; + u32 cylinders; + ips_mdata_t mdata; + + DBG("ips_msense"); + + if (ha->enq->ulDriveSize[scb->target_id] > 0x400000 && + (ha->enq->ucMiscFlag & 0x8) == 0) { + heads = NORM_MODE_HEADS; + sectors = NORM_MODE_SECTORS; + } else { + heads = COMP_MODE_HEADS; + sectors = COMP_MODE_SECTORS; + } + + cylinders = ha->enq->ulDriveSize[scb->target_id] / (heads * sectors); + + mdata.plh.plh_type = 0; + mdata.plh.plh_wp = 0; + mdata.plh.plh_bdl = 8; + + switch (scb->scsi_cmd->cmnd[2] & 0x3f) { + case 0x03: /* page 3 */ + mdata.pdata.pg3.pg_pc = 0x3; + mdata.pdata.pg3.pg_res1 = 0; + mdata.pdata.pg3.pg_len = sizeof(DADF_T); + mdata.plh.plh_len = 3 + mdata.plh.plh_bdl + mdata.pdata.pg3.pg_len; + mdata.pdata.pg3.pg_trk_z = 0; + mdata.pdata.pg3.pg_asec_z = 0; + mdata.pdata.pg3.pg_atrk_z = 0; + mdata.pdata.pg3.pg_atrk_v = 0; + mdata.pdata.pg3.pg_sec_t = htons(sectors); + mdata.pdata.pg3.pg_bytes_s = htons(IPS_BLKSIZE); + mdata.pdata.pg3.pg_intl = htons(1); + mdata.pdata.pg3.pg_trkskew = 0; + mdata.pdata.pg3.pg_cylskew = 0; + mdata.pdata.pg3.pg_res2 = 0; + mdata.pdata.pg3.pg_ins = 0; + mdata.pdata.pg3.pg_surf = 0; + mdata.pdata.pg3.pg_rmb = 0; + mdata.pdata.pg3.pg_hsec = 0; + mdata.pdata.pg3.pg_ssec = 1; + break; + + case 0x4: + mdata.pdata.pg4.pg_pc = 0x4; + mdata.pdata.pg4.pg_res1 = 0; + mdata.pdata.pg4.pg_len = sizeof(RDDG_T); + mdata.plh.plh_len = 3 + mdata.plh.plh_bdl + mdata.pdata.pg4.pg_len; + mdata.pdata.pg4.pg_cylu = (cylinders >> 8) & 0xffff; + mdata.pdata.pg4.pg_cyll = cylinders & 0xff; + mdata.pdata.pg4.pg_head = heads; + mdata.pdata.pg4.pg_wrpcompu = 0; + mdata.pdata.pg4.pg_wrpcompl = 0; + mdata.pdata.pg4.pg_redwrcur = 0; + mdata.pdata.pg4.pg_drstep = htons(1); + mdata.pdata.pg4.pg_landu = 0; + mdata.pdata.pg4.pg_landl = 0; + mdata.pdata.pg4.pg_res2 = 0; + break; + default: + return (0); + } /* end switch */ + + memcpy(scb->scsi_cmd->request_buffer, &mdata, scb->scsi_cmd->request_bufflen); + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_reqsen */ +/* */ +/* Routine Description: */ +/* */ +/* Simulate a request sense command to a logical drive */ +/* */ +/****************************************************************************/ +static int +ips_reqsen(ips_ha_t *ha, ips_scb_t *scb) { + char *sp; + + DBG("ips_reqsen"); + + sp = (char *) scb->scsi_cmd->sense_buffer; + memset(sp, 0, sizeof(scb->scsi_cmd->sense_buffer)); + + sp[0] = 0x70; + sp[3] = NO_SENSE; + sp[7] = 0xe; + sp[12] = NO_SENSE; + + return (0); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_free */ +/* */ +/* Routine Description: */ +/* */ +/* Free any allocated space for this controller */ +/* */ +/****************************************************************************/ +static void +ips_free(ips_ha_t *ha) { + int i; + + DBG("ips_free"); + + if (ha) { + if (ha->enq) { + kfree(ha->enq); + ha->enq = NULL; + } + + if (ha->conf) { + kfree(ha->conf); + ha->conf = NULL; + } + + if (ha->adapt) { + kfree(ha->adapt); + ha->adapt = NULL; + } + + if (ha->nvram) { + kfree(ha->nvram); + ha->nvram = NULL; + } + + if (ha->subsys) { + kfree(ha->subsys); + ha->subsys = NULL; + } + + if (ha->dummy) { + kfree(ha->dummy); + ha->dummy = NULL; + } + + if (ha->scbs) { + for (i = 0; i < ha->max_cmds; i++) { + if (ha->scbs[i].sg_list) + kfree(ha->scbs[i].sg_list); + } + + kfree(ha->scbs); + ha->scbs = NULL; + } /* end if */ + } +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_allocatescbs */ +/* */ +/* Routine Description: */ +/* */ +/* Allocate the command blocks */ +/* */ +/****************************************************************************/ +static int +ips_allocatescbs(ips_ha_t *ha) { + ips_scb_t *scb_p; + int i; + + DBG("ips_allocatescbs"); + + /* Allocate memory for the CCBs */ + ha->scbs = (ips_scb_t *) kmalloc(ha->max_cmds * sizeof(ips_scb_t), GFP_KERNEL|GFP_DMA); + + memset(ha->scbs, 0, ha->max_cmds * sizeof(ips_scb_t)); + + for (i = 0; i < ha->max_cmds; i++) { + scb_p = &ha->scbs[i]; + + /* allocate S/G list */ + scb_p->sg_list = (SG_LIST *) kmalloc(sizeof(SG_LIST) * MAX_SG_ELEMENTS, GFP_KERNEL|GFP_DMA); + + if (! scb_p->sg_list) + return (0); + + /* add to the free list */ + if (i < ha->max_cmds - 1) { + scb_p->q_next = ha->scb_freelist; + ha->scb_freelist = scb_p; + } + } + + /* success */ + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_init_scb */ +/* */ +/* Routine Description: */ +/* */ +/* Initialize a CCB to default values */ +/* */ +/****************************************************************************/ +static void +ips_init_scb(ips_ha_t *ha, ips_scb_t *scb) { + SG_LIST *sg_list; + + DBG("ips_init_scb"); + + if (scb == NULL) + return ; + + sg_list = scb->sg_list; + + /* zero fill */ + memset(scb, 0, sizeof(ips_scb_t)); + memset(ha->dummy, 0, sizeof(BASIC_IO_CMD)); + + /* Initialize dummy command bucket */ + ha->dummy->op_code = 0xFF; + ha->dummy->ccsar = VIRT_TO_BUS(ha->dummy); + ha->dummy->command_id = MAX_CMDS; + + /* set bus address of scb */ + scb->scb_busaddr = VIRT_TO_BUS(scb); + scb->sg_list = sg_list; + + /* Neptune Fix */ + scb->cmd.basic_io.cccr = ILE; + scb->cmd.basic_io.ccsar = VIRT_TO_BUS(ha->dummy); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_get_scb */ +/* */ +/* Routine Description: */ +/* */ +/* Initialize a CCB to default values */ +/* */ +/* ASSUMED to be callled from within a lock */ +/* */ +/****************************************************************************/ +static ips_scb_t * +ips_getscb(ips_ha_t *ha) { + ips_scb_t *scb; + unsigned int cpu_flags; + + DBG("ips_getscb"); + + spin_lock_irqsave(&ha->scb_lock, cpu_flags); + if ((scb = ha->scb_freelist) == NULL) { + spin_unlock_irqrestore(&ha->scb_lock, cpu_flags); + + return (NULL); + } + + ha->scb_freelist = scb->q_next; + scb->q_next = NULL; + + spin_unlock_irqrestore(&ha->scb_lock, cpu_flags); + + ips_init_scb(ha, scb); + + return (scb); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_free_scb */ +/* */ +/* Routine Description: */ +/* */ +/* Return an unused CCB back to the free list */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static void +ips_freescb(ips_ha_t *ha, ips_scb_t *scb) { + unsigned int cpu_flags; + + DBG("ips_freescb"); + + /* check to make sure this is not our "special" scb */ + if (IPS_COMMAND_ID(ha, scb) < (ha->max_cmds - 1)) { + spin_lock_irqsave(&ha->scb_lock, cpu_flags); + scb->q_next = ha->scb_freelist; + ha->scb_freelist = scb; + spin_unlock_irqrestore(&ha->scb_lock, cpu_flags); + } +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_reset_adapter */ +/* */ +/* Routine Description: */ +/* */ +/* Reset the controller */ +/* */ +/****************************************************************************/ +static int +ips_reset_adapter(ips_ha_t *ha) { + u8 Isr; + u8 Cbsp; + u8 PostByte[MAX_POST_BYTES]; + u8 ConfigByte[MAX_CONFIG_BYTES]; + int i, j; + int reset_counter; + + DBG("ips_reset_adapter"); + +#if IPS_DEBUG >= 1 + printk(KERN_WARNING "ips_reset_adapter: io addr: %x, irq: %d\n", + ha->io_addr, ha->irq); +#endif + + reset_counter = 0; + + while (reset_counter < 2) { + reset_counter++; + + outb(RST, ha->io_addr + SCPR); + MDELAY(ONE_SEC); + outb(0, ha->io_addr + SCPR); + MDELAY(ONE_SEC); + + for (i = 0; i < MAX_POST_BYTES; i++) { + for (j = 0; j < 45; j++) { + Isr = inb(ha->io_addr + HISR); + if (Isr & GHI) + break; + + MDELAY(ONE_SEC); + } + + if (j >= 45) { + /* error occured */ + if (reset_counter < 2) + continue; + else + /* reset failed */ + return (0); + } + + PostByte[i] = inb(ha->io_addr + ISPR); + outb(Isr, ha->io_addr + HISR); + } + + if (PostByte[0] < GOOD_POST_BASIC_STATUS) { + printk("(%s%d) reset controller fails (post status %x %x).\n", + ips_name, ha->host_num, PostByte[0], PostByte[1]); + + return (0); + } + + for (i = 0; i < MAX_CONFIG_BYTES; i++) { + for (j = 0; j < 240; j++) { + Isr = inb(ha->io_addr + HISR); + if (Isr & GHI) + break; + + MDELAY(ONE_SEC); /* 100 msec */ + } + + if (j >= 240) { + /* error occured */ + if (reset_counter < 2) + continue; + else + /* reset failed */ + return (0); + } + + ConfigByte[i] = inb(ha->io_addr + ISPR); + outb(Isr, ha->io_addr + HISR); + } + + if (ConfigByte[0] == 0 && ConfigByte[1] == 2) { + printk("(%s%d) reset controller fails (status %x %x).\n", + ips_name, ha->host_num, ConfigByte[0], ConfigByte[1]); + + return (0); + } + + for (i = 0; i < 240; i++) { + Cbsp = inb(ha->io_addr + CBSP); + + if ((Cbsp & OP) == 0) + break; + + MDELAY(ONE_SEC); + } + + if (i >= 240) { + /* error occured */ + if (reset_counter < 2) + continue; + else + /* reset failed */ + return (0); + } + + /* setup CCCR */ + outw(0x1010, ha->io_addr + CCCR); + + /* Enable busmastering */ + outb(EBM, ha->io_addr + SCPR); + + /* setup status queues */ + ips_statinit(ha); + + /* Enable interrupts */ + outb(EI, ha->io_addr + HISR); + + /* if we get here then everything went OK */ + break; + } + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_statinit */ +/* */ +/* Routine Description: */ +/* */ +/* Initialize the status queues on the controller */ +/* */ +/****************************************************************************/ +static void +ips_statinit(ips_ha_t *ha) { + u32 phys_status_start; + + DBG("ips_statinit"); + + ha->adapt->p_status_start = ha->adapt->status; + ha->adapt->p_status_end = ha->adapt->status + MAX_CMDS; + ha->adapt->p_status_tail = ha->adapt->status; + + phys_status_start = VIRT_TO_BUS(ha->adapt->status); + outl(phys_status_start, ha->io_addr + SQSR); + outl(phys_status_start + STATUS_Q_SIZE, ha->io_addr + SQER); + outl(phys_status_start + STATUS_SIZE, ha->io_addr + SQHR); + outl(phys_status_start, ha->io_addr + SQTR); + + ha->adapt->hw_status_start = phys_status_start; + ha->adapt->hw_status_tail = phys_status_start; +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_statupd */ +/* */ +/* Routine Description: */ +/* */ +/* Remove an element from the status queue */ +/* */ +/****************************************************************************/ +static int +ips_statupd(ips_ha_t *ha) { + int command_id; + + DBG("ips_statupd"); + + if (ha->adapt->p_status_tail != ha->adapt->p_status_end) { + ha->adapt->p_status_tail++; + ha->adapt->hw_status_tail += sizeof(STATUS); + } else { + ha->adapt->p_status_tail = ha->adapt->p_status_start; + ha->adapt->hw_status_tail = ha->adapt->hw_status_start; + } + + outl(ha->adapt->hw_status_tail, ha->io_addr + SQTR); + + command_id = ha->adapt->p_status_tail->command_id; + + return (command_id); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_issue */ +/* */ +/* Routine Description: */ +/* */ +/* Send a command down to the controller */ +/* */ +/* ASSUMED to be called from within a lock */ +/* */ +/****************************************************************************/ +static int +ips_issue(ips_ha_t *ha, ips_scb_t *scb) { + u32 TimeOut; + u16 val; + + DBG("ips_issue"); + +#if IPS_DEBUG >= 10 + if (scb->scsi_cmd) + printk(KERN_NOTICE "%s: ips_issue: cmd 0x%X id %d (%d %d %d)\n", + ips_name, + scb->cdb[0], + scb->cmd.basic_io.command_id, + scb->bus, + scb->target_id, + scb->lun); + else + printk(KERN_NOTICE "%s: ips_issue: logical cmd id %d\n", + ips_name, + scb->cmd.basic_io.command_id); +#if IPS_DEBUG >= 11 + MDELAY(ONE_SEC); +#endif +#endif + + TimeOut = 0; + + while ((val = inw(ha->io_addr + CCCR)) & SEMAPHORE) { + UDELAY(1000); + + if (++TimeOut >= SEMAPHORE_TIMEOUT) { + if (!(val & START_STOP_BIT)) + break; + + printk(KERN_WARNING "(%s%d) ips_issue val [0x%x].\n", + ips_name, ha->host_num, val); + printk(KERN_WARNING "(%s%d) ips_issue semaphore chk timeout.\n", + ips_name, ha->host_num); + + return (IPS_FAILURE); + } /* end if */ + } /* end while */ + + outl(scb->scb_busaddr, ha->io_addr + CCSAR); + outw(START_COMMAND, ha->io_addr + CCCR); + + return (IPS_SUCCESS); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_isintr */ +/* */ +/* Routine Description: */ +/* */ +/* Test to see if an interrupt is for us */ +/* */ +/****************************************************************************/ +static int +ips_isintr(ips_ha_t *ha) { + u8 Isr; + + DBG("ips_isintr"); + + Isr = inb(ha->io_addr + HISR); + + if (Isr == 0xFF) + /* ?!?! Nothing really there */ + return (0); + + if (Isr & SCE) + return (1); + else if (Isr & (SQO | GHI)) { + /* status queue overflow or GHI */ + /* just clear the interrupt */ + outb(Isr, ha->io_addr + HISR); + } + + return (0); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_wait */ +/* */ +/* Routine Description: */ +/* */ +/* Wait for a command to complete */ +/* */ +/****************************************************************************/ +static int +ips_wait(ips_ha_t *ha, int time, int intr) { + int ret; + + DBG("ips_wait"); + + ret = IPS_FAILURE; + + time *= ONE_SEC; /* convert seconds to milliseconds */ + + while (time > 0) { + if (intr == IPS_INTR_OFF) { + if (ha->waitflag == FALSE) { + /* + * controller generated an interupt to + * acknowledge completion of the command + * and ips_intr() has serviced the interrupt. + */ + ret = IPS_SUCCESS; + break; + } + + /* + * NOTE: Interrupts are disabled here + * On an SMP system interrupts will only + * be disabled on one processor. + * So, ultimately we still need to set the + * "I'm in the interrupt handler flag" + */ + while (test_and_set_bit(IPS_IN_INTR, &ha->flags)) + UDELAY(1000); + + ips_intr(ha); + + clear_bit(IPS_IN_INTR, &ha->flags); + + } else { + if (ha->waitflag == FALSE) { + ret = IPS_SUCCESS; + break; + } + } + + UDELAY(1000); /* 1 milisecond */ + time--; + } + + return (ret); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_write_driver_status */ +/* */ +/* Routine Description: */ +/* */ +/* Write OS/Driver version to Page 5 of the nvram on the controller */ +/* */ +/****************************************************************************/ +static int +ips_write_driver_status(ips_ha_t *ha) { + DBG("ips_write_driver_status"); + + if (!ips_readwrite_page5(ha, FALSE)) { + printk(KERN_WARNING "(%s%d) unable to read NVRAM page 5.\n", + ips_name, ha->host_num); + + return (0); + } + + /* check to make sure the page has a valid */ + /* signature */ + if (ha->nvram->signature != NVRAM_PAGE5_SIGNATURE) { +#if IPS_DEBUG >= 1 + printk("(%s%d) NVRAM page 5 has an invalid signature: %X.\n", + ips_name, ha->host_num, ha->nvram->signature); +#endif + return (1); + } + +#if IPS_DEBUG >= 2 + printk("(%s%d) Ad Type: %d, Ad Slot: %d, BIOS: %c%c%c%c %c%c%c%c.\n", + ips_name, ha->host_num, ha->nvram->adapter_type, ha->nvram->adapter_slot, + ha->nvram->bios_high[0], ha->nvram->bios_high[1], + ha->nvram->bios_high[2], ha->nvram->bios_high[3], + ha->nvram->bios_low[0], ha->nvram->bios_low[1], + ha->nvram->bios_low[2], ha->nvram->bios_low[3]); +#endif + + /* save controller type */ + ha->ad_type = ha->nvram->adapter_type; + + /* change values (as needed) */ + ha->nvram->operating_system = OS_LINUX; + strncpy((char *) ha->nvram->driver_high, IPS_VERSION_HIGH, 4); + strncpy((char *) ha->nvram->driver_low, IPS_VERSION_LOW, 4); + + /* now update the page */ + if (!ips_readwrite_page5(ha, TRUE)) { + printk(KERN_WARNING "(%s%d) unable to write NVRAM page 5.\n", + ips_name, ha->host_num); + + return (0); + } + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_read_adapter_status */ +/* */ +/* Routine Description: */ +/* */ +/* Do an Inquiry command to the adapter */ +/* */ +/****************************************************************************/ +static int +ips_read_adapter_status(ips_ha_t *ha) { + ips_scb_t *scb; + int ret; + + DBG("ips_read_adapter_status"); + + scb = &ha->scbs[ha->max_cmds-1]; + + ips_init_scb(ha, scb); + + scb->timeout = ips_cmd_timeout; + scb->cdb[0] = ENQUIRY; + + scb->cmd.basic_io.op_code = ENQUIRY; + scb->cmd.basic_io.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.basic_io.sg_count = 0; + scb->cmd.basic_io.sg_addr = VIRT_TO_BUS(ha->enq); + scb->cmd.basic_io.lba = 0; + scb->cmd.basic_io.sector_count = 0; + scb->cmd.basic_io.log_drv = 0; + scb->cmd.basic_io.reserved = 0; + + /* send command */ + ret = ips_send_wait(ha, scb, ips_cmd_timeout); + if ((ret == IPS_FAILURE) || (ret == IPS_SUCCESS_IMM)) + return (0); + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_read_subsystem_parameters */ +/* */ +/* Routine Description: */ +/* */ +/* Read subsystem parameters from the adapter */ +/* */ +/****************************************************************************/ +static int +ips_read_subsystem_parameters(ips_ha_t *ha) { + ips_scb_t *scb; + int ret; + + DBG("ips_read_subsystem_parameters"); + + scb = &ha->scbs[ha->max_cmds-1]; + + ips_init_scb(ha, scb); + + scb->timeout = ips_cmd_timeout; + scb->cdb[0] = GET_SUBSYS_PARAM; + + scb->cmd.basic_io.op_code = GET_SUBSYS_PARAM; + scb->cmd.basic_io.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.basic_io.sg_count = 0; + scb->cmd.basic_io.sg_addr = VIRT_TO_BUS(ha->subsys); + scb->cmd.basic_io.lba = 0; + scb->cmd.basic_io.sector_count = 0; + scb->cmd.basic_io.log_drv = 0; + scb->cmd.basic_io.reserved = 0; + + /* send command */ + ret = ips_send_wait(ha, scb, ips_cmd_timeout); + if ((ret == IPS_FAILURE) || (ret == IPS_SUCCESS_IMM)) + return (0); + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_read_config */ +/* */ +/* Routine Description: */ +/* */ +/* Read the configuration on the adapter */ +/* */ +/****************************************************************************/ +static int +ips_read_config(ips_ha_t *ha) { + ips_scb_t *scb; + int i; + int ret; + + DBG("ips_read_config"); + + /* set defaults for initiator IDs */ + ha->conf->init_id[0] = IPS_ADAPTER_ID; + for (i = 1; i < 4; i++) + ha->conf->init_id[i] = 7; + + scb = &ha->scbs[ha->max_cmds-1]; + + ips_init_scb(ha, scb); + + scb->timeout = ips_cmd_timeout; + scb->cdb[0] = READ_NVRAM_CONFIGURATION; + + scb->cmd.basic_io.op_code = READ_NVRAM_CONFIGURATION; + scb->cmd.basic_io.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.basic_io.sg_addr = VIRT_TO_BUS(ha->conf); + + /* send command */ + if (((ret = ips_send_wait(ha, scb, ips_cmd_timeout)) == IPS_FAILURE) || + (ret == IPS_SUCCESS_IMM) || + ((scb->basic_status & GSC_STATUS_MASK) > 1)) { + + memset(ha->conf, 0, sizeof(CONFCMD)); + + /* reset initiator IDs */ + ha->conf->init_id[0] = IPS_ADAPTER_ID; + for (i = 1; i < 4; i++) + ha->conf->init_id[i] = 7; + + return (0); + } + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_readwrite_page5 */ +/* */ +/* Routine Description: */ +/* */ +/* Read the configuration on the adapter */ +/* */ +/****************************************************************************/ +static int +ips_readwrite_page5(ips_ha_t *ha, int write) { + ips_scb_t *scb; + int ret; + + DBG("ips_readwrite_page5"); + + scb = &ha->scbs[ha->max_cmds-1]; + + ips_init_scb(ha, scb); + + scb->timeout = ips_cmd_timeout; + scb->cdb[0] = RW_NVRAM_PAGE; + + scb->cmd.nvram.op_code = RW_NVRAM_PAGE; + scb->cmd.nvram.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.nvram.page = 5; + scb->cmd.nvram.write = write; + scb->cmd.nvram.buffer_addr = VIRT_TO_BUS(ha->nvram); + scb->cmd.nvram.reserved = 0; + scb->cmd.nvram.reserved2 = 0; + + /* issue the command */ + if (((ret = ips_send_wait(ha, scb, ips_cmd_timeout)) == IPS_FAILURE) || + (ret == IPS_SUCCESS_IMM) || + ((scb->basic_status & GSC_STATUS_MASK) > 1)) { + + memset(ha->nvram, 0, sizeof(NVRAM_PAGE5)); + + return (0); + } + + return (1); +} + +/****************************************************************************/ +/* */ +/* Routine Name: ips_clear_adapter */ +/* */ +/* Routine Description: */ +/* */ +/* Clear the stripe lock tables */ +/* */ +/****************************************************************************/ +static int +ips_clear_adapter(ips_ha_t *ha) { + ips_scb_t *scb; + int ret; + + DBG("ips_clear_adapter"); + + scb = &ha->scbs[ha->max_cmds-1]; + + ips_init_scb(ha, scb); + + scb->timeout = ips_reset_timeout; + scb->cdb[0] = CONFIG_SYNC; + + scb->cmd.config_sync.op_code = CONFIG_SYNC; + scb->cmd.config_sync.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.config_sync.channel = 0; + scb->cmd.config_sync.source_target = POCL; + scb->cmd.config_sync.reserved = 0; + scb->cmd.config_sync.reserved2 = 0; + scb->cmd.config_sync.reserved3 = 0; + + /* issue command */ + ret = ips_send_wait(ha, scb, ips_reset_timeout); + if ((ret == IPS_FAILURE) || (ret == IPS_SUCCESS_IMM)) + return (0); + + /* send unlock stripe command */ + ips_init_scb(ha, scb); + + scb->cdb[0] = GET_ERASE_ERROR_TABLE; + scb->timeout = ips_reset_timeout; + + scb->cmd.unlock_stripe.op_code = GET_ERASE_ERROR_TABLE; + scb->cmd.unlock_stripe.command_id = IPS_COMMAND_ID(ha, scb); + scb->cmd.unlock_stripe.log_drv = 0; + scb->cmd.unlock_stripe.control = CSL; + scb->cmd.unlock_stripe.reserved = 0; + scb->cmd.unlock_stripe.reserved2 = 0; + scb->cmd.unlock_stripe.reserved3 = 0; + + /* issue command */ + ret = ips_send_wait(ha, scb, ips_reset_timeout); + if ((ret == IPS_FAILURE) || (ret == IPS_SUCCESS_IMM)) + return (0); + + return (1); +} + +#if defined (MODULE) + +Scsi_Host_Template driver_template = IPS; + + #include "scsi_module.c" + +#endif + + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 2 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -2 + * c-argdecl-indent: 2 + * c-label-offset: -2 + * c-continued-statement-offset: 2 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ |