diff options
Diffstat (limited to 'drivers/scsi/aic7xxx.c')
-rw-r--r-- | drivers/scsi/aic7xxx.c | 7361 |
1 files changed, 4356 insertions, 3005 deletions
diff --git a/drivers/scsi/aic7xxx.c b/drivers/scsi/aic7xxx.c index 96e66defd..005c889d7 100644 --- a/drivers/scsi/aic7xxx.c +++ b/drivers/scsi/aic7xxx.c @@ -1,5 +1,3 @@ -#define EXPERIMENTAL_FLAGS 0 - /*+M************************************************************************* * Adaptec AIC7xxx device driver for Linux. * @@ -29,21 +27,73 @@ * the Adaptec AIC-7770 Data Book, the ANSI SCSI specification, the * ANSI SCSI-2 specification (draft 10c), ... * - * ---------------------------------------------------------------- - * Modified to include support for wide and twin bus adapters, - * DMAing of SCBs, tagged queueing, IRQ sharing, bug fixes, + * -------------------------------------------------------------------------- + * + * Modifications by Daniel M. Eischen (deischen@iworks.InterWorks.org): + * + * Substantially modified to include support for wide and twin bus + * adapters, DMAing of SCBs, tagged queueing, IRQ sharing, bug fixes, * SCB paging, and other rework of the code. * - * Parts of this driver are based on the FreeBSD driver by Justin - * T. Gibbs. + * Parts of this driver were also based on the FreeBSD driver by + * Justin T. Gibbs. His copyright follows: + * + * -------------------------------------------------------------------------- + * Copyright (c) 1994-1997 Justin Gibbs. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Where this Software is combined with software released under the terms of + * the GNU Public License ("GPL") and the terms of the GPL would require the + * combined work to also be released under the terms of the GPL, the terms + * and conditions of this License will apply in addition to those of the + * GPL with the exception of any terms or conditions of this License that + * conflict with, or are expressly prohibited by, the GPL. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: aic7xxx.c,v 1.119 1997/06/27 19:39:18 gibbs Exp $ + *--------------------------------------------------------------------------- + * + * Thanks also go to (in alphabetical order) the following: + * + * Rory Bolt - Sequencer bug fixes + * Jay Estabrook - Initial DEC Alpha support + * Doug Ledford - Much needed abort/reset bug fixes + * Kai Makisara - DMAing of SCBs * * A Boot time option was also added for not resetting the scsi bus. * - * Form: aic7xxx=extended,no_reset + * Form: aic7xxx=extended + * aic7xxx=no_reset + * aic7xxx=ultra + * aic7xxx=irq_trigger:[0,1] # 0 edge, 1 level + * aic7xxx=verbose * - * -- Daniel M. Eischen, deischen@iworks.InterWorks.org, 07/07/96 + * Daniel M. Eischen, deischen@iworks.InterWorks.org, 1/23/97 * - * $Id: aic7xxx.c,v 4.0 1996/10/13 08:23:42 deang Exp $ + * $Id: aic7xxx.c,v 4.1 1997/06/12 08:23:42 deang Exp $ *-M*************************************************************************/ #ifdef MODULE @@ -67,7 +117,11 @@ #include "scsi.h" #include "hosts.h" #include "aic7xxx.h" + +#include "aic7xxx/sequencer.h" +#include "aic7xxx/scsi_message.h" #include "aic7xxx_reg.h" +#include "aic7xxx_seq.h" #include <linux/stat.h> #include <linux/malloc.h> /* for kmalloc() */ @@ -79,16 +133,20 @@ */ #define VIRT_TO_BUS(a) (unsigned int)virt_to_bus((void *)(a)) -static struct proc_dir_entry proc_scsi_aic7xxx = { +struct proc_dir_entry proc_scsi_aic7xxx = { PROC_SCSI_AIC7XXX, 7, "aic7xxx", - S_IFDIR | S_IRUGO | S_IXUGO, 2 + S_IFDIR | S_IRUGO | S_IXUGO, 2, + 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; -#define AIC7XXX_C_VERSION "$Revision: 4.0 $" +#define AIC7XXX_C_VERSION "$Revision: 4.1 $" #define NUMBER(arr) (sizeof(arr) / sizeof(arr[0])) -#define MIN(a,b) ((a < b) ? a : b) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) #define ALL_TARGETS -1 +#define ALL_CHANNELS '\0' +#define ALL_LUNS -1 #ifndef TRUE # define TRUE 1 #endif @@ -107,11 +165,8 @@ static struct proc_dir_entry proc_scsi_aic7xxx = { * support because all PCI dependent code is bracketed with * "#ifdef CONFIG_PCI ... #endif CONFIG_PCI". * - * o Twin bus support - this has been tested and does work. - * - * o DMAing of SCBs - thanks to Kai Makisara, this now works. - * This define is now taken out and DMAing of SCBs is always - * performed (8/12/95 - DE). + * o Twin bus support - this has been tested and does work. It is + * not an option anymore. * * o Tagged queueing - this driver is capable of tagged queueing * but I am unsure as to how well the higher level driver implements @@ -140,16 +195,16 @@ static struct proc_dir_entry proc_scsi_aic7xxx = { * LUN using its own heuristic based on the number of available * SCBs. * - * o 3985 support - The 3985 adapter is much like the 3940, but - * has three 7870 controllers as opposed to two for the 3940. - * It will get probed and recognized as three different adapters, - * but all three controllers can share the same external bank of - * 255 SCBs. If you enable AIC7XXX_SHARE_SCBS, then the driver - * will attempt to share the common bank of SCBs between the three - * controllers of the 3985. This is experimental and hasn't - * been tested. By default, we do not share the bank of SCBs, - * and force the controllers to use their own internal bank of - * 16 SCBs. Please let us know if sharing the SCB array works. + * o 3985 support - The 3985 adapter is much like the 3940, but has + * three 7870 controllers as opposed to two for the 3940. It will + * be probed and recognized as three different adapters, but all + * three controllers can share the same external bank of 255 SCBs. + * If you enable AIC7XXX_USE_EXT_SCBRAM, then the driver will attempt + * to use and share the common bank of SCBs between the three + * controllers of the 3985. This is experimental and hasn't been + * been tested. By default, we do not use external SCB RAM, and + * force the controllers to use their own internal bank of 16 SCBs. + * Please let us know if using the external SCB array works. * * o SCB paging support - SCB paging is enabled by defining * AIC7XXX_PAGE_ENABLE. Support for this was taken from the @@ -162,43 +217,54 @@ static struct proc_dir_entry proc_scsi_aic7xxx = { * Note that sharing of IRQs is not an option any longer. Linux supports * it so we support it. * - * Daniel M. Eischen, deischen@iworks.InterWorks.org, 06/30/96 + * Daniel M. Eischen, deischen@iworks.InterWorks.org, 01/26/96 */ -/* Uncomment this for testing twin bus support. */ -#define AIC7XXX_TWIN_SUPPORT - /* Uncomment this for tagged queueing. */ -/* #define AIC7XXX_TAGGED_QUEUEING */ +#ifdef CONFIG_AIC7XXX_TAGGED_QUEUEING +#define AIC7XXX_TAGGED_QUEUEING +#endif /* * You can try raising me if tagged queueing is enabled, or lowering * me if you only have 4 SCBs. */ -/* #define AIC7XXX_CMDS_PER_LUN 8 */ +#ifdef CONFIG_AIC7XXX_CMDS_PER_LUN +#define AIC7XXX_CMDS_PER_LUN CONFIG_AIC7XXX_CMDS_PER_LUN +#endif /* Set this to the delay in seconds after SCSI bus reset. */ +#ifdef CONFIG_AIC7XXX_RESET_DELAY +#define AIC7XXX_RESET_DELAY CONFIG_AIC7XXX_RESET_DELAY +#else #define AIC7XXX_RESET_DELAY 15 +#endif /* - * Uncomment the following define for collection of SCSI transfer statistics - * for the /proc filesystem. + * Control collection of SCSI transfer statistics for the /proc filesystem. * * NOTE: Do NOT enable this when running on kernels version 1.2.x and below. * NOTE: This does affect performance since it has to maintain statistics. */ -/* #define AIC7XXX_PROC_STATS */ +#ifdef CONFIG_AIC7XXX_PROC_STATS +#define AIC7XXX_PROC_STATS +#endif /* - * Uncomment the following to enable SCB paging. + * Enable SCB paging. */ -/* #define AIC7XXX_PAGE_ENABLE */ +#ifdef CONFIG_AIC7XXX_PAGE_ENABLE +#define AIC7XXX_PAGE_ENABLE +#endif /* - * Uncomment the following to enable sharing of the external bank - * of 255 SCBs for the 3985. + * Uncomment the following to enable use of the external bank + * of 255 SCBs. For 3985 adapters, this will also enable sharing + * of the SCB array across all three controllers. */ -#define AIC7XXX_SHARE_SCBS +#ifdef CONFIG_AIC7XXX_USE_EXT_SCBRAM +#define AIC7XXX_USE_EXT_SCBRAM +#endif /* * For debugging the abort/reset code. @@ -211,6 +277,87 @@ static struct proc_dir_entry proc_scsi_aic7xxx = { #define AIC7XXX_DEBUG /* + * Set this for defining the number of tagged commands on a device + * by device, and controller by controller basis. The first set + * of tagged commands will be used for the first detected aic7xxx + * controller, the second set will be used for the second detected + * aic7xxx controller, and so on. These values will *only* be used + * for targets that are tagged queueing capable; these values will + * be ignored in all other cases. The tag_commands is an array of + * 16 to allow for wide and twin adapters. Twin adapters will use + * indexes 0-7 for channel 0, and indexes 8-15 for channel 1. + * + * *** Determining commands per LUN *** + * + * When AIC7XXX_CMDS_PER_LUN is not defined, the driver will use its + * own algorithm to determine the commands/LUN. If SCB paging is + * enabled, the commands/LUN is 8. When SCB paging is not enabled, + * then commands/LUN is 8 for adapters with 16 or more hardware SCBs + * and 4 commands/LUN for adapters with 3 or 4 SCBs. + * + */ +/* #define AIC7XXX_TAGGED_QUEUEING_BY_DEVICE */ + +#ifdef AIC7XXX_TAGGED_QUEUEING_BY_DEVICE +typedef struct +{ + char tag_commands[16]; /* Allow for wide/twin channel adapters. */ +} adapter_tag_info_t; + +/* + * Make a define that will tell the driver to use it's own algorithm + * for determining commands/LUN (see Determining commands per LUN + * above). + */ +#define DEFAULT_TAG_COMMANDS {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +/* + * Modify this as you see fit for your system. By setting tag_commands + * to 0, the driver will use it's own algorithm for determining the + * number of commands to use (see above). When -1, the driver will + * not enable tagged queueing for that particular device. When positive + * (> 0) the values in the array are used for the queue_depth. Note + * that the maximum value for an entry is 127. + * + * In this example, the first line will enable tagged queueing for all + * the devices on the first probed aic7xxx adapter and tells the driver + * to use it's own algorithm for determining commands/LUN. + * + * The second line enables tagged queueing with 4 commands/LUN for IDs + * (1, 2-11, 13-15), disables tagged queueing for ID 12, and tells the + * driver to use its own algorithm for ID 1. + * + * The third line is the same as the first line. + * + * The fourth line disables tagged queueing for devices 0 and 3. It + * enables tagged queueing for the other IDs, with 16 commands/LUN + * for IDs 1 and 4, 127 commands/LUN for ID 8, and 4 commands/LUN for + * IDs 2, 5-7, and 9-15. + */ +adapter_tag_info_t aic7xxx_tag_info[] = +{ + {DEFAULT_TAG_COMMANDS}, + {{4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, -1, 4, 4, 4}}, + {DEFAULT_TAG_COMMANDS}, + {{-1, 16, 4, -1, 16, 4, 4, 4, 127, 4, 4, 4, 4, 4, 4, 4}} +}; +#endif + +/* + * Don't define this unless you have problems with the driver + * interrupt handler. The old method would register the drivers + * interrupt handler as a "fast" type interrupt handler that would + * lock out other interrupts. Since this driver can spend a lot + * of time in the interrupt handler, this is _not_ a good idea. + * It also conflicts with some of the more common ethernet drivers + * that don't use fast interrupts. Currently, Linux does not allow + * IRQ sharing unless both drivers can agree on the type of interrupt + * handler. + */ +/* #define AIC7XXX_OLD_ISR_TYPE */ + + +/* * Controller type and options */ typedef enum { @@ -232,14 +379,15 @@ typedef enum { AIC_7882, /* PCI aic7882 on 3940 Ultra */ AIC_7883, /* PCI aic7883 on 3985 Ultra */ AIC_7884 /* PCI aic7884 on 294x Ultra Differential */ -} aha_type; +} aha_chip_type; typedef enum { AIC_777x, /* AIC-7770 based */ - AIC_785x, /* AIC-7850 based */ + AIC_785x, /* AIC-7850 based (3 SCBs)*/ + AIC_786x, /* AIC-7860 based (7850 ultra) */ AIC_787x, /* AIC-7870 based */ - AIC_788x /* AIC-7880 based */ -} aha_chip_type; + AIC_788x /* AIC-7880 based (ultra) */ +} aha_chip_class_type; typedef enum { AIC_SINGLE, /* Single Channel */ @@ -269,24 +417,24 @@ typedef enum { * Don't forget to change this when changing the types! */ static const char *board_names[] = { - "<AIC-7xxx Unknown>", /* AIC_NONE */ - "AIC-7770", /* AIC_7770 */ - "AHA-2740", /* AIC_7771 */ - "AHA-2840", /* AIC_284x */ - "AIC-7850", /* AIC_7850 */ - "AIC-7855", /* AIC_7855 */ - "AIC-7850 Ultra", /* AIC_7860 */ - "AHA-2940A Ultra", /* AIC_7861 */ - "AIC-7870", /* AIC_7870 */ - "AHA-2940", /* AIC_7871 */ - "AHA-3940", /* AIC_7872 */ - "AHA-3985", /* AIC_7873 */ - "AHA-2940 Differential", /* AIC_7874 */ - "AIC-7880 Ultra", /* AIC_7880 */ - "AHA-2940 Ultra", /* AIC_7881 */ - "AHA-3940 Ultra", /* AIC_7882 */ - "AHA-3985 Ultra", /* AIC_7883 */ - "AHA-2940 Ultra Differential" /* AIC_7884 */ + "AIC-7xxx Unknown", /* AIC_NONE */ + "Adaptec AIC-7770 SCSI host adapter", /* AIC_7770 */ + "Adaptec AHA-274X SCSI host adapter", /* AIC_7771 */ + "Adaptec AHA-284X SCSI host adapter", /* AIC_284x */ + "Adaptec AIC-7850 SCSI host adapter", /* AIC_7850 */ + "Adaptec AIC-7855 SCSI host adapter", /* AIC_7855 */ + "Adaptec AIC-7860 Ultra SCSI host adapter", /* AIC_7860 */ + "Adaptec AHA-2940A Ultra SCSI host adapter", /* AIC_7861 */ + "Adaptec AIC-7870 SCSI host adapter", /* AIC_7870 */ + "Adaptec AHA-294X SCSI host adapter", /* AIC_7871 */ + "Adaptec AHA-394X SCSI host adapter", /* AIC_7872 */ + "Adaptec AHA-398X SCSI host adapter", /* AIC_7873 */ + "Adaptec AHA-2944 SCSI host adapter", /* AIC_7874 */ + "Adaptec AIC-7880 Ultra SCSI host adapter", /* AIC_7880 */ + "Adaptec AHA-294X Ultra SCSI host adapter", /* AIC_7881 */ + "Adaptec AHA-394X Ultra SCSI host adapter", /* AIC_7882 */ + "Adaptec AHA-398X Ultra SCSI host adapter", /* AIC_7883 */ + "Adaptec AHA-2944 Ultra SCSI host adapter" /* AIC_7884 */ }; /* @@ -310,12 +458,17 @@ static const char *board_names[] = { */ #define DID_RETRY_COMMAND DID_ERROR +#define HSCSIID 0x07 +#define HWSCSIID 0x0F +#define SCSI_RESET 0x040 + /* * EISA/VL-bus stuff */ #define MINSLOT 1 #define MAXSLOT 15 #define SLOTBASE(x) ((x) << 12) +#define BASE_TO_SLOT(x) ((x) >> 12) /* * Standard EISA Host ID regs (Offset from slot base) @@ -334,14 +487,6 @@ static const char *board_names[] = { #define INTDEF 0x5C /* Interrupt Definition Register */ /* - * Some defines for the HCNTRL register. - */ -#define REQ_PAUSE IRQMS | INTEN | PAUSE -#define UNPAUSE_274X IRQMS | INTEN -#define UNPAUSE_284X INTEN -#define UNPAUSE_294X IRQMS | INTEN - -/* * AIC-78X0 PCI registers */ #define CLASS_PROGIF_REVID 0x08 @@ -377,7 +522,7 @@ static const char *board_names[] = { * each word, while the C56 and C66 (4096 bits) use 8 bits to * address each word. */ -typedef enum {c46 = 6, c56_66 = 8} seeprom_chip_type; +typedef enum {C46 = 6, C56_66 = 8} seeprom_chip_type; /* * @@ -417,15 +562,15 @@ struct seeprom_config { /* * Host Adapter Control Bits */ -/* UNUSED 0x0001 */ +#define CFAUTOTERM 0x0001 /* Perform Auto termination */ #define CFULTRAEN 0x0002 /* Ultra SCSI speed enable (Ultra cards) */ #define CF284XSELTO 0x0003 /* Selection timeout (284x cards) */ #define CF284XFIFO 0x000C /* FIFO Threshold (284x cards) */ -#define CFSTERM 0x0004 /* SCSI low byte termination (non-wide cards) */ +#define CFSTERM 0x0004 /* SCSI low byte termination */ #define CFWSTERM 0x0008 /* SCSI high byte termination (wide card) */ #define CFSPARITY 0x0010 /* SCSI parity */ #define CF284XSTERM 0x0020 /* SCSI low byte termination (284x cards) */ -#define CFRESETB 0x0040 /* reset SCSI bus at IC initialization */ +#define CFRESETB 0x0040 /* reset SCSI bus at boot */ /* UNUSED 0xFF80 */ unsigned short adapter_control; /* word 17 */ @@ -448,36 +593,17 @@ struct seeprom_config { unsigned short checksum; /* word 31 */ }; +#define SELBUS_MASK 0x0a +#define SELNARROW 0x00 +#define SELBUSB 0x08 +#define SINGLE_BUS 0x00 -#define SCSI_RESET 0x040 - -/* - * Pause the sequencer and wait for it to actually stop - this - * is important since the sequencer can disable pausing for critical - * sections. - */ -#define PAUSE_SEQUENCER(p) \ - synchronize_irq(); \ - outb(p->pause, HCNTRL + p->base); \ - while ((inb(HCNTRL + p->base) & PAUSE) == 0) \ - ; \ - -/* - * Unpause the sequencer. Unremarkable, yet done often enough to - * warrant an easy way to do it. - */ -#define UNPAUSE_SEQUENCER(p) \ - outb(p->unpause, HCNTRL + p->base) - -/* - * Restart the sequencer program from address zero - */ -#define RESTART_SEQUENCER(p) \ - do { \ - outb(SEQRESET | FASTMODE, SEQCTL + p->base); \ - } while (inb(SEQADDR0 + p->base) != 0 && \ - inb(SEQADDR1 + p->base) != 0); \ - UNPAUSE_SEQUENCER(p); +#define SCB_TARGET(scb) \ + (((scb)->hscb->target_channel_lun & TID) >> 4) +#define SCB_LUN(scb) \ + ((scb)->hscb->target_channel_lun & LID) +#define SCB_IS_SCSIBUS_B(scb) \ + (((scb)->hscb->target_channel_lun & SELBUSB) != 0) /* * If an error occurs during a data transfer phase, run the command @@ -541,12 +667,6 @@ static struct Scsi_Host *aic7xxx_boards[NR_IRQS + 1]; static int aic7xxx_spurious_count; /* - * The driver keeps up to four scb structures per card in memory. Only the - * first 25 bytes of the structure are valid for the hardware, the rest used - * for driver level bookkeeping. - */ - -/* * As of Linux 2.1, the mid-level SCSI code uses virtual addresses * in the scatter-gather lists. We need to convert the virtual * addresses to physical addresses. @@ -559,20 +679,28 @@ struct hw_scatterlist { /* * Maximum number of SG segments these cards can support. */ -#define MAX_SG 256 +#define AIC7XXX_MAX_SG 27 -struct aic7xxx_scb { +/* + * The maximum number of SCBs we could have for ANY type + * of card. DON'T FORGET TO CHANGE THE SCB MASK IN THE + * SEQUENCER CODE IF THIS IS MODIFIED! + */ +#define AIC7XXX_MAXSCB 255 + + +struct aic7xxx_hwscb { /* ------------ Begin hardware supported fields ---------------- */ /* 0*/ unsigned char control; /* 1*/ unsigned char target_channel_lun; /* 4/1/3 bits */ /* 2*/ unsigned char target_status; /* 3*/ unsigned char SG_segment_count; -/* 4*/ unsigned char SG_list_pointer[4] __attribute__ ((packed)); +/* 4*/ unsigned int SG_list_pointer; /* 8*/ unsigned char residual_SG_segment_count; -/* 9*/ unsigned char residual_data_count[3] __attribute__ ((packed)); -/*12*/ unsigned char data_pointer[4] __attribute__ ((packed)); -/*16*/ unsigned int data_count __attribute__ ((packed)); /* must be 32 bits */ -/*20*/ unsigned char SCSI_cmd_pointer[4] __attribute__ ((packed)); +/* 9*/ unsigned char residual_data_count[3]; +/*12*/ unsigned int data_pointer; +/*16*/ unsigned int data_count; +/*20*/ unsigned int SCSI_cmd_pointer; /*24*/ unsigned char SCSI_cmd_length; /*25*/ u_char tag; /* Index into our kernel SCB array. * Also used as the tag for tagged I/O @@ -580,29 +708,47 @@ struct aic7xxx_scb { #define SCB_PIO_TRANSFER_SIZE 26 /* amount we need to upload/download * via PIO to initialize a transaction. */ -/*26*/ u_char next; /* Used to thread SCBs awaiting selection +/*26*/ unsigned char next; /* Used to thread SCBs awaiting selection * or disconnected down in the sequencer. */ - /*-----------------end of hardware supported fields----------------*/ - Scsi_Cmnd *cmd; /* Scsi_Cmnd for this scb */ - struct aic7xxx_scb *q_next; /* next scb in queue */ -#define SCB_FREE 0x00 -#define SCB_ACTIVE 0x01 -#define SCB_ABORTED 0x02 -#define SCB_DEVICE_RESET 0x04 -#define SCB_IMMED 0x08 -#define SCB_SENSE 0x10 -#define SCB_QUEUED_FOR_DONE 0x40 -#define SCB_PAGED_OUT 0x80 -#define SCB_WAITINGQ 0x100 -#define SCB_ASSIGNEDQ 0x200 -#define SCB_SENTORDEREDTAG 0x400 -#define SCB_IN_PROGRESS (SCB_ACTIVE | SCB_PAGED_OUT | \ - SCB_WAITINGQ | SCB_ASSIGNEDQ) - int state; /* current state of scb */ - unsigned int position; /* Position in scb array */ - struct hw_scatterlist sg_list[MAX_SG]; /* SG list in adapter format */ - unsigned char sense_cmd[6]; /* Allocate 6 characters for sense command */ +/*27*/ unsigned char prev; +/*28*/ unsigned int pad; /* + * Unused by the kernel, but we require + * the padding so that the array of + * hardware SCBs is alligned on 32 byte + * boundaries so the sequencer can index + */ +}; + +typedef enum { + SCB_FREE = 0x0000, + SCB_ACTIVE = 0x0001, + SCB_ABORTED = 0x0002, + SCB_DEVICE_RESET = 0x0004, + SCB_SENSE = 0x0008, + SCB_TIMEDOUT = 0x0010, + SCB_QUEUED_FOR_DONE = 0x0020, + SCB_RECOVERY_SCB = 0x0040, + SCB_WAITINGQ = 0x0080, + SCB_ASSIGNEDQ = 0x0100, + SCB_SENTORDEREDTAG = 0x0200, + SCB_MSGOUT_SDTR = 0x0400, + SCB_MSGOUT_WDTR = 0x0800, + SCB_ABORT = 0x1000, + SCB_QUEUED_ABORT = 0x2000 +} scb_flag_type; + +struct aic7xxx_scb { + struct aic7xxx_hwscb *hscb; /* corresponding hardware scb */ + Scsi_Cmnd *cmd; /* Scsi_Cmnd for this scb */ + struct aic7xxx_scb *q_next; /* next scb in queue */ + scb_flag_type flags; /* current state of scb */ + struct hw_scatterlist *sg_list; /* SG list in adapter format */ + unsigned char sg_count; + unsigned char sense_cmd[6]; /* + * Allocate 6 characters for + * sense command. + */ }; /* @@ -627,20 +773,17 @@ static unsigned char generic_sense[] = { REQUEST_SENSE, 0, 0, 0, 255, 0 }; typedef struct { + struct aic7xxx_hwscb *hscbs; scb_queue_type free_scbs; /* * SCBs assigned to free slot on * card (no paging required) */ - int numscbs; /* current number of scbs */ - int activescbs; /* active scbs */ -} scb_usage_type; - -/* - * The maximum number of SCBs we could have for ANY type - * of card. DON'T FORGET TO CHANGE THE SCB MASK IN THE - * SEQUENCER CODE IF THIS IS MODIFIED! - */ -#define AIC7XXX_MAXSCB 255 + unsigned char numscbs; /* current number of scbs */ + unsigned char maxhscbs; /* hardware scbs */ + unsigned char maxscbs; /* max scbs including pageable scbs */ + struct aic7xxx_scb *scb_array[AIC7XXX_MAXSCB]; + unsigned int reserve[100]; +} scb_data_type; /* * Define a structure used for each host adapter, only one per IRQ. @@ -648,17 +791,26 @@ typedef struct { struct aic7xxx_host { struct Scsi_Host *host; /* pointer to scsi host */ int host_no; /* SCSI host number */ + int instance; /* aic7xxx instance number */ + int scsi_id; /* host adapter SCSI ID */ + int scsi_id_b; /* channel B for twin adapters */ + int irq; /* IRQ for this adapter */ int base; /* card base address */ - int maxhscbs; /* hardware SCBs */ - int maxscbs; /* max SCBs (including pageable) */ -#define A_SCANNED 0x0001 -#define B_SCANNED 0x0002 -#define EXTENDED_TRANSLATION 0x0004 -#define HAVE_SEEPROM 0x0008 -#define ULTRA_ENABLED 0x0010 -#define PAGE_ENABLED 0x0020 -#define IN_ISR 0x0040 -#define USE_DEFAULTS 0x0080 + unsigned int mbase; /* I/O memory address */ + volatile unsigned char *maddr; /* memory mapped address */ +#define A_SCANNED 0x0001 +#define B_SCANNED 0x0002 +#define EXTENDED_TRANSLATION 0x0004 +#define FLAGS_CHANNEL_B_PRIMARY 0x0008 +#define MULTI_CHANNEL 0x0010 +#define ULTRA_ENABLED 0x0020 +#define PAGE_ENABLED 0x0040 +#define USE_DEFAULTS 0x0080 +#define BIOS_ENABLED 0x0100 +#define IN_ISR 0x0200 +#define IN_TIMEOUT 0x0400 +#define SHARED_SCBDATA 0x0800 +#define HAVE_SEEPROM 0x1000 unsigned int flags; unsigned int isr_count; /* Interrupt count */ unsigned short needsdtr_copy; /* default config */ @@ -669,36 +821,22 @@ struct aic7xxx_host { unsigned short wdtr_pending; unsigned short orderedtag; unsigned short discenable; /* Targets allowed to disconnect */ - aha_type type; /* card type */ - aha_chip_type chip_type; /* chip base type */ + aha_chip_type chip_type; /* card type */ + aha_chip_class_type chip_class; aha_bus_type bus_type; /* normal/twin/wide bus */ - char * mbase; /* I/O memory address */ - unsigned char chan_num; /* for 3940/3985, channel number */ + unsigned char chan_num; /* for 39xx, channel number */ unsigned char unpause; /* unpause value for HCNTRL */ unsigned char pause; /* pause value for HCNTRL */ unsigned char qcntmask; - struct seeprom_config seeprom; + unsigned char qfullcount; + unsigned char curqincnt; struct Scsi_Host *next; /* allow for multiple IRQs */ - struct aic7xxx_scb *scb_array[AIC7XXX_MAXSCB]; /* active commands */ - struct aic7xxx_scb *pagedout_ntscbs[16]; /* - * paged-out, non-tagged scbs - * indexed by target. - */ - scb_queue_type page_scbs; /* - * SCBs that will require paging - * before use (no assigned slot) - */ + unsigned char activescbs; /* active scbs */ scb_queue_type waiting_scbs; /* - * SCBs waiting to be paged and - * started. + * SCBs waiting for space in + * the QINFIFO. */ - scb_queue_type assigned_scbs; /* - * SCBs that were waiting but have - * have now been assigned a slot - * by aic7xxx_free_scb - */ - scb_usage_type scb_usage; - scb_usage_type *scb_link; + scb_data_type *scb_data; struct aic7xxx_cmd_queue { Scsi_Cmnd *head; @@ -710,6 +848,7 @@ struct aic7xxx_host { #define BUS_DEVICE_RESET_PENDING 0x02 int flags; int commands_sent; + int active_cmds; } device_status[16]; #ifdef AIC7XXX_PROC_STATS /* @@ -735,34 +874,10 @@ struct aic7xxx_host { #endif /* AIC7XXX_PROC_STATS */ }; -struct aic7xxx_host_config { - int irq; /* IRQ number */ - int mbase; /* memory base address*/ - int base; /* I/O base address*/ - int maxhscbs; /* hardware SCBs */ - int maxscbs; /* max SCBs (including pageable) */ - int unpause; /* unpause value for HCNTRL */ - int pause; /* pause value for HCNTRL */ - int scsi_id; /* host SCSI ID */ - int scsi_id_b; /* host SCSI ID B channel for twin cards */ - unsigned int flags; /* used the same as struct aic7xxx_host flags */ - int chan_num; /* for 3940/3985, channel number */ - unsigned char busrtime; /* bus release time */ - unsigned char bus_speed; /* bus speed */ - unsigned char qcntmask; - aha_type type; /* card type */ - aha_chip_type chip_type; /* chip base type */ - aha_bus_type bus_type; /* normal/twin/wide bus */ - aha_status_type bios; /* BIOS is enabled/disabled */ - aha_status_type parity; /* bus parity enabled/disabled */ - aha_status_type low_term; /* bus termination low byte */ - aha_status_type high_term; /* bus termination high byte (wide cards only) */ -}; - /* * Valid SCSIRATE values. (p. 3-17) - * Provides a mapping of transfer periods in ns to the proper value to - * stick in the scsiscfr reg to use that transfer rate. + * Provides a mapping of transfer periods in ns/4 to the proper value to + * stick in the SCSIRATE reg to use that transfer rate. */ static struct { short period; @@ -771,17 +886,17 @@ static struct { short rate; const char *english; } aic7xxx_syncrates[] = { - { 50, 0x100, "20.0" }, - { 62, 0x110, "16.0" }, - { 75, 0x120, "13.4" }, - { 100, 0x000, "10.0" }, - { 125, 0x010, "8.0" }, - { 150, 0x020, "6.67" }, - { 175, 0x030, "5.7" }, - { 200, 0x040, "5.0" }, - { 225, 0x050, "4.4" }, - { 250, 0x060, "4.0" }, - { 275, 0x070, "3.6" } + { 12, 0x100, "20.0" }, + { 15, 0x110, "16.0" }, + { 18, 0x120, "13.4" }, + { 25, 0x000, "10.0" }, + { 31, 0x010, "8.0" }, + { 37, 0x020, "6.67" }, + { 43, 0x030, "5.7" }, + { 50, 0x040, "5.0" }, + { 56, 0x050, "4.4" }, + { 62, 0x060, "4.0" }, + { 68, 0x070, "3.6" } }; static int num_aic7xxx_syncrates = @@ -790,166 +905,51 @@ static int num_aic7xxx_syncrates = #ifdef CONFIG_PCI static int number_of_3940s = 0; static int number_of_3985s = 0; -#ifdef AIC7XXX_SHARE_SCBS -static scb_usage_type *shared_3985_scbs = NULL; -#endif -#endif CONFIG_PCI +#endif /* CONFIG_PCI */ #ifdef AIC7XXX_DEBUG -static void -debug_config(struct aic7xxx_host_config *p) -{ - int scsi_conf; - unsigned char brelease; - unsigned char dfthresh; - - static int DFT[] = { 0, 50, 75, 100 }; - static int SST[] = { 256, 128, 64, 32 }; - static const char *BUSW[] = { "", "-TWIN", "-WIDE" }; - - scsi_conf = inb(SCSICONF + p->base); - - /* - * Scale the Data FIFO Threshhold and the Bus Release Time; they are - * stored in formats compatible for writing to sequencer registers. - */ - dfthresh = p->bus_speed >> 6; - - if (p->chip_type == AIC_777x) - { - brelease = p->busrtime >> 2; - } - else - { - brelease = p->busrtime; - } - if (brelease == 0) - { - brelease = 2; - } - - switch (p->type) - { - case AIC_7770: - case AIC_7771: - printk("%s%s AT EISA SLOT %d:\n", board_names[p->type], BUSW[p->bus_type], - p->base >> 12); - break; - - case AIC_284x: - printk("%s%s AT VLB SLOT %d:\n", board_names[p->type], BUSW[p->bus_type], - p->base >> 12); - break; - - case AIC_7850: - case AIC_7855: - case AIC_7860: - case AIC_7861: - case AIC_7870: - case AIC_7871: - case AIC_7872: - case AIC_7873: - case AIC_7874: - case AIC_7880: - case AIC_7881: - case AIC_7882: - case AIC_7883: - case AIC_7884: - printk("%s%s (PCI-bus), I/O 0x%x, Mem 0x%x:\n", board_names[p->type], - BUSW[p->bus_type], p->base, p->mbase); - break; - - default: - panic("aic7xxx: (debug_config) internal error.\n"); - } - - printk(" irq %d\n" - " bus release time %d bclks\n" - " data fifo threshold %d%%\n", - p->irq, - brelease, - DFT[dfthresh]); - - printk(" SCSI CHANNEL A:\n" - " scsi id %d\n" - " scsi selection timeout %d ms\n" - " scsi bus reset at power-on %sabled\n", - scsi_conf & 0x07, - SST[(scsi_conf >> 3) & 0x03], - (scsi_conf & 0x40) ? "en" : "dis"); - - if ((p->chip_type == AIC_777x) && (p->parity == AIC_UNKNOWN)) - { - /* - * Set the parity for 7770 based cards. - */ - p->parity = (scsi_conf & 0x20) ? AIC_ENABLED : AIC_DISABLED; - } - if (p->parity != AIC_UNKNOWN) - { - printk(" scsi bus parity %sabled\n", - (p->parity == AIC_ENABLED) ? "en" : "dis"); - } - - if ((p->type == AIC_7770) || (p->type == AIC_7771)) - { - p->low_term = (scsi_conf & 0x80) ? AIC_ENABLED : AIC_DISABLED; - } - if (p->low_term != AIC_UNKNOWN) - { - printk(" scsi bus termination (low byte) %sabled\n", - (p->low_term == AIC_ENABLED) ? "en" : "dis"); - } - if ((p->bus_type == AIC_WIDE) && (p->high_term != AIC_UNKNOWN)) - { - printk(" scsi bus termination (high byte) %sabled\n", - (p->high_term == AIC_ENABLED) ? "en" : "dis"); - } -} - #if 0 static void debug_scb(struct aic7xxx_scb *scb) { - printk("control 0x%x, tcl 0x%x, sg_count %d, sg_ptr 0x%x, cmdp 0x%x, cmdlen %d\n", - scb->control, scb->target_channel_lun, scb->SG_segment_count, - (scb->SG_list_pointer[3] << 24) | (scb->SG_list_pointer[2] << 16) | - (scb->SG_list_pointer[1] << 8) | scb->SG_list_pointer[0], - (scb->SCSI_cmd_pointer[3] << 24) | (scb->SCSI_cmd_pointer[2] << 16) | - (scb->SCSI_cmd_pointer[1] << 8) | scb->SCSI_cmd_pointer[0], - scb->SCSI_cmd_length); - printk("reserved 0x%x, target status 0x%x, resid SG count %d, resid data count %d\n", - (scb->RESERVED[1] << 8) | scb->RESERVED[0], scb->target_status, - scb->residual_SG_segment_count, - ((scb->residual_data_count[2] << 16) | - (scb->residual_data_count[1] << 8) | - (scb->residual_data_count[0])); - printk("data ptr 0x%x, data count %d, next waiting %d\n", - (scb->data_pointer[3] << 24) | (scb->data_pointer[2] << 16) | - (scb->data_pointer[1] << 8) | scb->data_pointer[0], - scb->data_count, scb->next_waiting); - printk("next ptr 0x%lx, Scsi Cmnd 0x%lx, state 0x%x, position %d\n", - (unsigned long) scb->next, (unsigned long) scb->cmd, scb->state, - scb->position); + struct aic7xxx_hwscb *hscb = scb->hscb; + + printk("scb:%p control:0x%x tcl:0x%x cmdlen:%d cmdpointer:0x%lx\n", + scb, + hscb->control, + hscb->target_channel_lun, + hscb->SCSI_cmd_length, + hscb->SCSI_cmd_pointer ); + printk(" datlen:%d data:0x%lx segs:0x%x segp:0x%lx\n", + hscb->data_count, + hscb->data_pointer, + hscb->SG_segment_count, + hscb->SG_list_pointer); + printk(" sg_addr:%lx sg_len:%ld\n", + hscb->sg_list[0].address, + hscb->sg_list[0].length); } #endif #else -# define debug_config(x) # define debug_scb(x) #endif AIC7XXX_DEBUG -#define TCL_OF_SCB(x) (((x)->target_channel_lun >> 4) & 0xf), \ - (((x)->target_channel_lun >> 3) & 0x01), \ - ((x)->target_channel_lun & 0x07) +#define TCL_OF_SCB(scb) (((scb->hscb)->target_channel_lun >> 4) & 0xf), \ + (((scb->hscb)->target_channel_lun >> 3) & 0x01), \ + ((scb->hscb)->target_channel_lun & 0x07) + +#define TC_OF_SCB(scb) (((scb->hscb)->target_channel_lun >> 4) & 0xf), \ + (((scb->hscb)->target_channel_lun >> 3) & 0x01) -#define TARGET_INDEX(x) ((x)->target | ((x)->channel << 3)) +#define CHAN_TO_INT(chan) ((chan) == 'A' ? 0 : 1) + +#define TARGET_INDEX(cmd) ((cmd)->target | ((cmd)->channel << 3)) /* * XXX - these options apply unilaterally to _all_ 274x/284x/294x - * cards in the system. This should be fixed, but then, - * does anyone really have more than one in a machine? + * cards in the system. This should be fixed. */ static unsigned int aic7xxx_extended = 0; /* extended translation on? */ static unsigned int aic7xxx_no_reset = 0; /* no resetting of SCSI bus */ @@ -959,6 +959,53 @@ static int aic7xxx_irq_trigger = -1; /* * 1 use level triggered */ static int aic7xxx_enable_ultra = 0; /* enable ultra SCSI speeds */ +static int aic7xxx_verbose = 0; /* verbose messages */ + + +/**************************************************************************** + * + * These functions are not used yet, but when we do memory mapped + * IO, we'll use them then. + * + ***************************************************************************/ +static inline unsigned char +aic_inb(struct aic7xxx_host *p, long port) +{ + if (p->maddr != NULL) + return (p->maddr[port]); + else + return (inb(p->base + port)); +} + +static inline void +aic_outb(struct aic7xxx_host *p, unsigned char val, long port) +{ + if (p->maddr != NULL) + p->maddr[port] = val; + else + outb(val, p->base + port); +} + +static inline void +aic_outsb(struct aic7xxx_host *p, long port, unsigned char *valp, size_t size) +{ + if (p->maddr != NULL) + { + __asm __volatile(" + cld; + 1: lodsb; + movb %%al,(%0); + loop 1b" : + : + "r" ((p)->maddr + (port)), + "S" ((valp)), "c" ((size)) : + "%esi", "%ecx", "%eax"); + } + else + { + outsb(p->base + port, valp, size); + } +} /*+F************************************************************************* * Function: @@ -983,6 +1030,7 @@ aic7xxx_setup(char *s, int *dummy) { "no_reset", &aic7xxx_no_reset }, { "irq_trigger", &aic7xxx_irq_trigger }, { "ultra", &aic7xxx_enable_ultra }, + { "verbose", &aic7xxx_verbose }, { NULL, NULL } }; @@ -1008,58 +1056,230 @@ aic7xxx_setup(char *s, int *dummy) /*+F************************************************************************* * Function: + * pause_sequencer + * + * Description: + * Pause the sequencer and wait for it to actually stop - this + * is important since the sequencer can disable pausing for critical + * sections. + *-F*************************************************************************/ +static inline void +pause_sequencer(struct aic7xxx_host *p) +{ + outb(p->pause, p->base + HCNTRL); + while ((inb(p->base + HCNTRL) & PAUSE) == 0) + { + ; + } +} + +/*+F************************************************************************* + * Function: + * unpause_sequencer + * + * Description: + * Unpause the sequencer. Unremarkable, yet done often enough to + * warrant an easy way to do it. + *-F*************************************************************************/ +static inline void +unpause_sequencer(struct aic7xxx_host *p, int unpause_always) +{ + if (unpause_always || + ((inb(p->base + INTSTAT) & (SCSIINT | SEQINT | BRKADRINT)) == 0)) + { + outb(p->unpause, p->base + HCNTRL); + } +} + +/*+F************************************************************************* + * Function: + * restart_sequencer + * + * Description: + * Restart the sequencer program from address zero. This assumes + * that the sequencer is already paused. + *-F*************************************************************************/ +static inline void +restart_sequencer(struct aic7xxx_host *p) +{ + /* Set the sequencer address to 0. */ + outb(0, p->base + SEQADDR0); + outb(0, p->base + SEQADDR1); + + /* + * Reset and unpause the sequencer. The reset is suppose to + * start the sequencer running, but we do an unpause to make + * sure. + */ + outb(SEQRESET | FASTMODE, p->base + SEQCTL); + + unpause_sequencer(p, /*unpause_always*/ TRUE); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_next_patch + * + * Description: + * Find the next patch to download. + *-F*************************************************************************/ +static struct patch * +aic7xxx_next_patch(struct patch *cur_patch, int options, int instrptr) +{ + while (cur_patch != NULL) + { + if ((((cur_patch->options & options) != 0) && (cur_patch->negative == FALSE)) + || (((cur_patch->options & options) == 0) && (cur_patch->negative == TRUE)) + || (instrptr >= cur_patch->end)) + { + /* + * Either we want to keep this section of code, or we have consumed + * this patch. Skip to the next patch. + */ + cur_patch++; + if (cur_patch->options == 0) + { + /* Out of patches. */ + cur_patch = NULL; + } + } + else + { + /* Found an OK patch. */ + break; + } + } + return (cur_patch); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_download_instr + * + * Description: + * Find the next patch to download. + *-F*************************************************************************/ +static void +aic7xxx_download_instr(struct aic7xxx_host *p, int options, int instrptr) +{ + unsigned char opcode; + struct ins_format3 *instr; + + instr = (struct ins_format3 *) &seqprog[instrptr * 4]; + /* Pull the opcode */ + opcode = instr->opcode_addr >> 1; + switch (opcode) + { + case AIC_OP_JMP: + case AIC_OP_JC: + case AIC_OP_JNC: + case AIC_OP_CALL: + case AIC_OP_JNE: + case AIC_OP_JNZ: + case AIC_OP_JE: + case AIC_OP_JZ: + { + int address_offset; + struct ins_format3 new_instr; + unsigned int address; + struct patch *patch; + int i; + + address_offset = 0; + new_instr = *instr; /* Strucure copy */ + address = new_instr.address; + address |= (new_instr.opcode_addr & ADDR_HIGH_BIT) << 8; + for (i = 0; i < NUMBER(patches); i++) + { + patch = &patches[i]; + if ((((patch->options & options) == 0) && (patch->negative == FALSE)) || + (((patch->options & options) != 0) && (patch->negative == TRUE))) + { + if (address >= patch->end) + { + address_offset += patch->end - patch->begin; + } + } + } + address -= address_offset; + new_instr.address = address &0xFF; + new_instr.opcode_addr &= ~ADDR_HIGH_BIT; + new_instr.opcode_addr |= (address >> 8) & ADDR_HIGH_BIT; + outsb(p->base + SEQRAM, &new_instr.immediate, 4); + break; + } + + case AIC_OP_OR: + case AIC_OP_AND: + case AIC_OP_XOR: + case AIC_OP_ADD: + case AIC_OP_ADC: + case AIC_OP_ROL: + outsb(p->base + SEQRAM, &instr->immediate, 4); + break; + + default: + panic("aic7xxx: Unknown opcode encountered in sequencer program."); + break; + } +} + + +/*+F************************************************************************* + * Function: * aic7xxx_loadseq * * Description: * Load the sequencer code into the controller memory. *-F*************************************************************************/ static void -aic7xxx_loadseq(int base) +aic7xxx_loadseq(struct aic7xxx_host *p) { - static unsigned char seqprog[] = { - /* - * Each sequencer instruction is 29 bits - * long (fill in the excess with zeroes) - * and has to be loaded from least -> most - * significant byte, so this table has the - * byte ordering reversed. - */ -# include "aic7xxx_seq.h" - }; + int options; + struct patch *cur_patch; + int i; + int downloaded; - /* - * When the AIC-7770 is paused (as on chip reset), the - * sequencer address can be altered and a sequencer - * program can be loaded by writing it, byte by byte, to - * the sequencer RAM port - the Adaptec documentation - * recommends using REP OUTSB to do this, hence the inline - * assembly. Since the address autoincrements as we load - * the program, reset it back to zero afterward. Disable - * sequencer RAM parity error detection while loading, and - * make sure the LOADRAM bit is enabled for loading. - */ - outb(PERRORDIS | SEQRESET | LOADRAM, SEQCTL + base); + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Downloading sequencer code..."); + } + options = 1; /* Code for all options. */ + downloaded = 0; + if ((p->flags & ULTRA_ENABLED) != 0) + options |= ULTRA; + if (p->bus_type == AIC_TWIN) + options |= TWIN_CHANNEL; + if (p->scb_data->maxscbs > p->scb_data->maxhscbs) + options |= SCB_PAGING; - outsb(SEQRAM + base, seqprog, sizeof(seqprog)); + cur_patch = patches; + outb(PERRORDIS | LOADRAM, p->base + SEQCTL); + outb(0, p->base + SEQADDR0); + outb(0, p->base + SEQADDR1); - /* - * WARNING! This is a magic sequence! After extensive - * experimentation, it seems that you MUST turn off the - * LOADRAM bit before you play with SEQADDR again, else - * you will end up with parity errors being flagged on - * your sequencer program. (You would also think that - * turning off LOADRAM and setting SEQRESET to reset the - * address to zero would work, but you need to do it twice - * for it to take effect on the address. Timing problem?) - */ - do { - /* - * Actually, reset it until - * the address shows up as - * zero just to be safe.. - */ - outb(SEQRESET | FASTMODE, SEQCTL + base); - } while ((inb(SEQADDR0 + base) != 0) && (inb(SEQADDR1 + base) != 0)); + for (i = 0; i < sizeof(seqprog) / 4; i++) + { + cur_patch = aic7xxx_next_patch(cur_patch, options, i); + if (cur_patch && (cur_patch->begin <= i) && (cur_patch->end > i)) + { + /* Skip this instruction for this configuration. */ + continue; + } + aic7xxx_download_instr(p, options, i); + downloaded++; + } + + outb(FASTMODE, p->base + SEQCTL); + outb(0, p->base + SEQADDR0); + outb(0, p->base + SEQADDR1); + + if (aic7xxx_verbose) + { + printk(" %d instructions downloaded\n", downloaded); + } } /*+F************************************************************************* @@ -1067,18 +1287,22 @@ aic7xxx_loadseq(int base) * aic7xxx_delay * * Description: - * Delay for specified amount of time. + * Delay for specified amount of time. We use udelay because the timer + * interrupt is not guaranteed to be enabled. This will cause an + * infinite loop since jiffies (clock ticks) is not updated. *-F*************************************************************************/ static void aic7xxx_delay(int seconds) { - unsigned long i; - - i = jiffies + (seconds * HZ); /* compute time to stop */ + int i; - while (jiffies < i) + /* + * Call udelay() for 1 millisecond inside a loop for + * the requested amount of seconds. + */ + for (i=0; i < seconds*1000; i++) { - ; /* Do nothing! */ + udelay(1000); /* Delay for 1 millisecond. */ } } @@ -1090,7 +1314,7 @@ aic7xxx_delay(int seconds) * Return a string containing just the RCS version number from either * an Id or Revision RCS clause. *-F*************************************************************************/ -static const char * +const char * rcs_version(const char *version_info) { static char buf[10]; @@ -1150,8 +1374,10 @@ aic7xxx_info(struct Scsi_Host *notused) strcat(buffer, rcs_version(AIC7XXX_C_VERSION)); strcat(buffer, "/"); strcat(buffer, rcs_version(AIC7XXX_H_VERSION)); +#if 0 strcat(buffer, "/"); strcat(buffer, rcs_version(AIC7XXX_SEQ_VER)); +#endif return buffer; } @@ -1161,9 +1387,12 @@ aic7xxx_info(struct Scsi_Host *notused) * aic7xxx_length * * Description: - * How much data should be transferred for this SCSI command? Stop - * at segment sg_last if it's a scatter-gather command so we can - * compute underflow easily. + * How much data should be transferred for this SCSI command? Assume + * all segments are to be transferred except for the last sg_last + * segments. This will allow us to compute underflow easily. To + * calculate the total length of the command, use sg_last = 0. To + * calculate the length of all but the last 2 SG segments, use + * sg_last = 2. *-F*************************************************************************/ static unsigned aic7xxx_length(Scsi_Cmnd *cmd, int sg_last) @@ -1177,7 +1406,7 @@ aic7xxx_length(Scsi_Cmnd *cmd, int sg_last) if (cmd->use_sg) { - for (i = length = 0; (i < cmd->use_sg) && (i < segments); i++) + for (i = length = 0; i < segments; i++) { length += sg[i].length; } @@ -1199,9 +1428,9 @@ aic7xxx_length(Scsi_Cmnd *cmd, int sg_last) *-F*************************************************************************/ static void aic7xxx_scsirate(struct aic7xxx_host *p, unsigned char *scsirate, - short period, unsigned char offset, int target, char channel) + unsigned char *period, unsigned char *offset, int target, char channel) { - int i; + int i = num_aic7xxx_syncrates; unsigned long ultra_enb_addr; unsigned char ultra_enb, sxfrctl0; @@ -1209,11 +1438,11 @@ aic7xxx_scsirate(struct aic7xxx_host *p, unsigned char *scsirate, * If the offset is 0, then the device is requesting asynchronous * transfers. */ - if (offset != 0) + if ((*period >= aic7xxx_syncrates[i].period) && *offset != 0) { for (i = 0; i < num_aic7xxx_syncrates; i++) { - if ((aic7xxx_syncrates[i].period - period) >= 0) + if (*period <= aic7xxx_syncrates[i].period) { /* * Watch out for Ultra speeds when ultra is not enabled and @@ -1229,99 +1458,57 @@ aic7xxx_scsirate(struct aic7xxx_host *p, unsigned char *scsirate, */ continue; } - *scsirate = (aic7xxx_syncrates[i].rate) | (offset & 0x0F); + *scsirate = (aic7xxx_syncrates[i].rate & 0xF0) | (*offset & 0x0F); + *period = aic7xxx_syncrates[i].period; - /* - * Ensure Ultra mode is set properly for this target. - */ - ultra_enb_addr = ULTRA_ENB; - if ((channel == 'B') || (target > 7)) - { - ultra_enb_addr++; - } - ultra_enb = inb(p->base + ultra_enb_addr); - sxfrctl0 = inb(p->base + SXFRCTL0); - if (aic7xxx_syncrates[i].rate & ULTRA_SXFR) + if (aic7xxx_verbose) { - ultra_enb |= 0x01 << (target & 0x07); - sxfrctl0 |= ULTRAEN; + printk("scsi%d: Target %d, channel %c, now synchronous at %sMHz, " + "offset %d.\n", p->host_no, target, channel, + aic7xxx_syncrates[i].english, *offset); } - else - { - ultra_enb &= ~(0x01 << (target & 0x07)); - sxfrctl0 &= ~ULTRAEN; - } - outb(ultra_enb, p->base + ultra_enb_addr); - outb(sxfrctl0, p->base + SXFRCTL0); - - printk("scsi%d: Target %d, channel %c, now synchronous at %sMHz, " - "offset %d.\n", p->host_no, target, channel, - aic7xxx_syncrates[i].english, offset); - return; + break; } } } - /* - * Default to asynchronous transfer - */ - *scsirate = 0; - printk("scsi%d: Target %d, channel %c, using asynchronous transfers.\n", - p->host_no, target, channel); -} - -/*+F************************************************************************* - * Function: - * aic7xxx_putscb - * - * Description: - * Transfer a SCB to the controller. - *-F*************************************************************************/ -static inline void -aic7xxx_putscb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) -{ - int base = p->base; - - outb(SCBAUTO, SCBCNT + base); + if (i >= num_aic7xxx_syncrates) + { + /* + * Use asynchronous transfers. + */ + *scsirate = 0; + *period = 0; + *offset = 0; + if (aic7xxx_verbose) + { + printk("scsi%d: Target %d, channel %c, using asynchronous transfers.\n", + p->host_no, target, channel); + } + } /* - * By turning on the SCB auto increment, any reference - * to the SCB I/O space postincrements the SCB address - * we're looking at. So turn this on and dump the relevant - * portion of the SCB to the card. - * - * We can do 16bit transfers on all but 284x. + * Ensure Ultra mode is set properly for this target. */ - if (p->type == AIC_284x) + ultra_enb_addr = ULTRA_ENB; + if ((channel == 'B') || (target > 7)) + { + ultra_enb_addr++; + } + ultra_enb = inb(p->base + ultra_enb_addr); + sxfrctl0 = inb(p->base + SXFRCTL0); + if ((*scsirate != 0) && (aic7xxx_syncrates[i].rate & ULTRA_SXFR)) { - outsb(SCBARRAY + base, scb, SCB_PIO_TRANSFER_SIZE); + ultra_enb |= 0x01 << (target & 0x07); + sxfrctl0 |= FAST20; } else { - outsl(SCBARRAY + base, scb, (SCB_PIO_TRANSFER_SIZE + 3) / 4); + ultra_enb &= ~(0x01 << (target & 0x07)); + sxfrctl0 &= ~FAST20; } - - outb(0, SCBCNT + base); -} - -/*+F************************************************************************* - * Function: - * aic7xxx_getscb - * - * Description: - * Get a SCB from the controller. - *-F*************************************************************************/ -static inline void -aic7xxx_getscb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) -{ - int base = p->base; - - /* - * This is almost identical to aic7xxx_putscb(). - */ - outb(SCBAUTO, SCBCNT + base); - insb(SCBARRAY + base, scb, SCB_PIO_TRANSFER_SIZE); - outb(0, SCBCNT + base); + outb(ultra_enb, p->base + ultra_enb_addr); + outb(sxfrctl0, p->base + SXFRCTL0); } /*+F************************************************************************* @@ -1375,6 +1562,47 @@ scbq_remove_head(scb_queue_type *queue) /*+F************************************************************************* * Function: + * scbq_remove + * + * Description: + * Removes an SCB from the list. + * + *-F*************************************************************************/ +static inline void +scbq_remove(scb_queue_type *queue, struct aic7xxx_scb *scb) +{ + if (queue->head == scb) + { + /* At beginning of queue, remove from head. */ + scbq_remove_head(queue); + } + else + { + struct aic7xxx_scb *curscb = queue->head; + + /* + * Search until the next scb is the one we're looking for, or + * we run out of queue. + */ + while ((curscb != NULL) && (curscb->q_next != scb)) + { + curscb = curscb->q_next; + } + if (curscb != NULL) + { + /* Found it. */ + curscb->q_next = scb->q_next; + if (scb->q_next == NULL) + { + /* Update the tail when removing the tail. */ + queue->tail = curscb; + } + } + } +} + +/*+F************************************************************************* + * Function: * scbq_insert_tail * * Description: @@ -1404,23 +1632,87 @@ scbq_insert_tail(scb_queue_type *queue, struct aic7xxx_scb *scb) * to be reset and all devices on that channel must be aborted. *-F*************************************************************************/ static int -aic7xxx_match_scb(struct aic7xxx_scb *scb, int target, char channel) +aic7xxx_match_scb(struct aic7xxx_scb *scb, int target, char channel, + int lun, unsigned char tag) { - int targ = (scb->target_channel_lun >> 4) & 0x0F; - char chan = (scb->target_channel_lun & SELBUSB) ? 'B' : 'A'; + int targ = (scb->hscb->target_channel_lun >> 4) & 0x0F; + char chan = (scb->hscb->target_channel_lun & SELBUSB) ? 'B' : 'A'; + int slun = scb->hscb->target_channel_lun & 0x07; + int match; #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (match_scb) comparing target/channel %d/%c to scb %d/%c\n", - target, channel, targ, chan); + printk("scsi%d: (targ %d/chan %c) matching scb to (targ %d/chan %c)\n", + scb->cmd->device->host->host_no, target, channel, targ, chan); #endif - if (target == ALL_TARGETS) + match = ((chan == channel) || (channel == ALL_CHANNELS)); + if (match != 0) + match = ((targ == target) || (target == ALL_TARGETS)); + if (match != 0) + match = ((lun == slun) || (lun == ALL_LUNS)); + if (match != 0) + match = ((tag == scb->hscb->tag) || (tag == SCB_LIST_NULL)); + + return (match); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_add_curscb_to_free_list + * + * Description: + * Adds the current scb (in SCBPTR) to the list of free SCBs. + *-F*************************************************************************/ +static void +aic7xxx_add_curscb_to_free_list(struct aic7xxx_host *p) +{ + /* + * Invalidate the tag so that aic7xxx_find_scb doesn't think + * it's active + */ + outb(SCB_LIST_NULL, p->base + SCB_TAG); + + outb(inb(p->base + FREE_SCBH), p->base + SCB_NEXT); + outb(inb(p->base + SCBPTR), p->base + FREE_SCBH); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_rem_scb_from_disc_list + * + * Description: + * Removes the current SCB from the disconnected list and adds it + * to the free list. + *-F*************************************************************************/ +static unsigned char +aic7xxx_rem_scb_from_disc_list(struct aic7xxx_host *p, unsigned char scbptr) +{ + unsigned char next; + unsigned char prev; + + outb(scbptr, p->base + SCBPTR); + next = inb(p->base + SCB_NEXT); + prev = inb(p->base + SCB_PREV); + + outb(0, p->base + SCB_CONTROL); + + aic7xxx_add_curscb_to_free_list(p); + + if (prev != SCB_LIST_NULL) { - return (chan == channel); + outb(prev, p->base + SCBPTR); + outb(next, p->base + SCB_NEXT); } else { - return ((chan == channel) && (targ == target)); + outb(next, p->base + DISCONNECTED_SCBH); } + + if (next != SCB_LIST_NULL) + { + outb(next, p->base + SCBPTR); + outb(prev, p->base + SCB_PREV); + } + return next; } /*+F************************************************************************* @@ -1428,51 +1720,93 @@ aic7xxx_match_scb(struct aic7xxx_scb *scb, int target, char channel) * aic7xxx_busy_target * * Description: - * Set the specified target active. + * Set the specified target busy. *-F*************************************************************************/ static void -aic7xxx_busy_target(unsigned char target, char channel, int base) +aic7xxx_busy_target(struct aic7xxx_host *p, unsigned char target, + char channel, unsigned char scbid) { - unsigned char active; - unsigned long active_port = ACTIVE_A + base; + unsigned char active_scb; + unsigned char info_scb; + unsigned int scb_offset; + + info_scb = target / 4; + if (channel == 'B') + info_scb = info_scb + 2; + + active_scb = inb(p->base + SCBPTR); + outb(info_scb, p->base + SCBPTR); + scb_offset = SCB_BUSYTARGETS + (target & 0x03); + outb(scbid, p->base + scb_offset); + outb(active_scb, p->base + SCBPTR); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_index_busy_target + * + * Description: + * Returns the index of the busy target, and optionally sets the + * target inactive. + *-F*************************************************************************/ +static unsigned char +aic7xxx_index_busy_target(struct aic7xxx_host *p, unsigned char target, + char channel, int unbusy) +{ + unsigned char active_scb; + unsigned char info_scb; + unsigned char busy_scbid; + unsigned int scb_offset; + + info_scb = target / 4; + if (channel == 'B') + info_scb = info_scb + 2; - if ((target > 0x07) || (channel == 'B')) + active_scb = inb(p->base + SCBPTR); + outb(info_scb, p->base + SCBPTR); + scb_offset = SCB_BUSYTARGETS + (target & 0x03); + busy_scbid = inb(p->base + scb_offset); + if (unbusy) { - /* - * targets on the Second channel or above id 7 store info in byte two - * of ACTIVE - */ - active_port++; + outb(SCB_LIST_NULL, p->base + scb_offset); } - active = inb(active_port); - active |= (0x01 << (target & 0x07)); - outb(active, active_port); + outb(active_scb, p->base + SCBPTR); + return (busy_scbid); } /*+F************************************************************************* * Function: - * aic7xxx_unbusy_target + * aic7xxx_find_scb * * Description: - * Set the specified target inactive. + * Look through the SCB array of the card and attempt to find the + * hardware SCB that corresponds to the passed in SCB. Return + * SCB_LIST_NULL if unsuccessful. This routine assumes that the + * card is already paused. *-F*************************************************************************/ -static void -aic7xxx_unbusy_target(unsigned char target, char channel, int base) +static unsigned char +aic7xxx_find_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) { - unsigned char active; - unsigned long active_port = ACTIVE_A + base; + unsigned char saved_scbptr; + unsigned char curindex; - if ((target > 0x07) || (channel == 'B')) + saved_scbptr = inb(p->base + SCBPTR); + curindex = 0; + for (curindex = 0; curindex < p->scb_data->maxhscbs; curindex++) { - /* - * targets on the Second channel or above id 7 store info in byte two - * of ACTIVE - */ - active_port++; + outb(curindex, p->base + SCBPTR); + if (inb(p->base + SCB_TAG) == scb->hscb->tag) + { + break; + } } - active = inb(active_port); - active &= ~(0x01 << (target & 0x07)); - outb(active, active_port); + outb(saved_scbptr, p->base + SCBPTR); + if (curindex >= p->scb_data->maxhscbs) + { + curindex = SCB_LIST_NULL; + } + + return (curindex); } /*+F************************************************************************* @@ -1480,68 +1814,60 @@ aic7xxx_unbusy_target(unsigned char target, char channel, int base) * aic7xxx_allocate_scb * * Description: - * Get a free SCB either from one already assigned to a hardware - * slot, or one that will require an SCB to be paged out before - * use. If there are none, attempt to allocate a new one. + * Get an SCB from the free list or by allocating a new one. *-F*************************************************************************/ static struct aic7xxx_scb * aic7xxx_allocate_scb(struct aic7xxx_host *p) { - struct aic7xxx_scb *scbp = NULL; - int maxscbs; + struct aic7xxx_scb *scbp = NULL; + struct aic7xxx_hwscb *hscbp = NULL; +#ifdef AGRESSIVE + long processor_flags; - scbp = p->scb_link->free_scbs.head; + save_flags(processor_flags); + cli(); +#endif + + scbp = p->scb_data->free_scbs.head; if (scbp != NULL) { - scbq_remove_head(&p->scb_link->free_scbs); + scbq_remove_head(&p->scb_data->free_scbs); } else { - /* - * This should always be NULL if paging is not enabled. - */ - scbp = p->page_scbs.head; - if (scbp != NULL) + if (p->scb_data->numscbs < p->scb_data->maxscbs) { - scbq_remove_head(&p->page_scbs); - } - else - { - /* - * Set limit the SCB allocation to the maximum number of - * hardware SCBs if paging is not enabled; otherwise use - * the maximum (255). - */ - if (p->flags & PAGE_ENABLED) - maxscbs = p->maxscbs; - else - maxscbs = p->maxhscbs; - if (p->scb_link->numscbs < maxscbs) - { - int scb_index = p->scb_link->numscbs; - int scb_size = sizeof(struct aic7xxx_scb); + int scb_index = p->scb_data->numscbs; + int scb_size = sizeof(struct aic7xxx_scb) + + sizeof (struct hw_scatterlist) * AIC7XXX_MAX_SG; - p->scb_array[scb_index] = kmalloc(scb_size, GFP_ATOMIC | GFP_DMA); - scbp = (p->scb_array[scb_index]); - if (scbp != NULL) - { - memset(scbp, 0, sizeof(*scbp)); - scbp->tag = scb_index; - if (scb_index < p->maxhscbs) - scbp->position = scb_index; - else - scbp->position = SCB_LIST_NULL; - p->scb_link->numscbs++; - } + scbp = kmalloc(scb_size, GFP_ATOMIC); + if (scbp != NULL) + { + memset(scbp, 0, sizeof(struct aic7xxx_scb)); + hscbp = &p->scb_data->hscbs[scb_index]; + scbp->hscb = hscbp; + scbp->sg_list = (struct hw_scatterlist *) &scbp[1]; + memset(hscbp, 0, sizeof(struct aic7xxx_hwscb)); + hscbp->tag = scb_index; + p->scb_data->numscbs++; + /* + * Place in the scb array; never is removed + */ + p->scb_data->scb_array[scb_index] = scbp; } } } +#ifdef AIC7XXX_DEBUG if (scbp != NULL) { -#ifdef AIC7XXX_DEBUG - p->scb_link->activescbs++; -#endif + p->activescbs++; } +#endif + +#ifdef AGRESSIVE + restore_flags(processor_flags); +#endif return (scbp); } @@ -1581,6 +1907,7 @@ aic7xxx_done_cmds_complete(struct aic7xxx_host *p) cmd = p->completeq.head; p->completeq.head = (Scsi_Cmnd *)cmd->host_scribble; cmd->host_scribble = NULL; + p->device_status[TARGET_INDEX(cmd)].active_cmds--; cmd->scsi_done(cmd); } p->completeq.tail = NULL; @@ -1591,53 +1918,29 @@ aic7xxx_done_cmds_complete(struct aic7xxx_host *p) * aic7xxx_free_scb * * Description: - * Free the scb and update the page, waiting, free scb lists. + * Free the scb and insert into the free scb list. *-F*************************************************************************/ static void aic7xxx_free_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb) { - struct aic7xxx_scb *wscb; + struct aic7xxx_hwscb *hscb; + long flags; + + hscb = scb->hscb; + save_flags(flags); + cli(); - scb->state = SCB_FREE; + scb->flags = SCB_FREE; scb->cmd = NULL; - scb->control = 0; - scb->state = 0; + hscb->control = 0; + hscb->target_status = 0; - if (scb->position == SCB_LIST_NULL) - { - scbq_insert_head(&p->page_scbs, scb); - } - else - { - /* - * If there are any SCBS on the waiting queue, assign the slot of this - * "freed" SCB to the first one. We'll run the waiting queues after - * all command completes for a particular interrupt are completed or - * when we start another command. - */ - wscb = p->waiting_scbs.head; - if (wscb != NULL) - { - scbq_remove_head(&p->waiting_scbs); - wscb->position = scb->position; - scbq_insert_tail(&p->assigned_scbs, wscb); - wscb->state = (wscb->state & ~SCB_WAITINGQ) | SCB_ASSIGNEDQ; - - /* - * The "freed" SCB will need to be assigned a slot before being - * used, so put it in the page_scbs queue. - */ - scb->position = SCB_LIST_NULL; - scbq_insert_head(&p->page_scbs, scb); - } - else - { - scbq_insert_head(&p->scb_link->free_scbs, scb); - } + scbq_insert_head(&p->scb_data->free_scbs, scb); #ifdef AIC7XXX_DEBUG - p->scb_link->activescbs--; /* For debugging purposes. */ + p->activescbs--; /* For debugging purposes. */ #endif - } + + restore_flags(flags); } /*+F************************************************************************* @@ -1652,68 +1955,113 @@ aic7xxx_done(struct aic7xxx_host *p, struct aic7xxx_scb *scb) { Scsi_Cmnd *cmd = scb->cmd; + if (scb->flags & SCB_RECOVERY_SCB) + { + p->flags &= ~IN_TIMEOUT; + } + if (cmd->result == DID_OK) + { + if (scb->flags & SCB_ABORTED) + { + cmd->result = (DID_RESET << 16); + } + } + if ((scb->flags & (SCB_MSGOUT_WDTR | SCB_MSGOUT_SDTR)) != 0) + { + unsigned short mask; + + mask = 0x01 << TARGET_INDEX(scb->cmd); + if (scb->flags & SCB_MSGOUT_WDTR) + { + p->wdtr_pending &= ~mask; + } + if (scb->flags & SCB_MSGOUT_SDTR) + { + p->sdtr_pending &= ~mask; + } + } aic7xxx_free_scb(p, scb); aic7xxx_queue_cmd_complete(p, cmd); +#ifdef AIC7XXX_PROC_STATS + { + int actual; + + /* + * XXX: we should actually know how much actually transferred + * XXX: for each command, but apparently that's too difficult. + */ + actual = aic7xxx_length(cmd, 0); + if (!(scb->flags & (SCB_ABORTED | SCB_SENSE)) && (actual > 0) + && (aic7xxx_error(cmd) == 0)) + { + struct aic7xxx_xferstats *sp; + long *ptr; + int x; + + sp = &p->stats[cmd->channel & 0x01][cmd->target & 0x0F][cmd->lun & 0x07]; + sp->xfers++; + + if (cmd->request.cmd == WRITE) + { + sp->w_total++; + sp->w_total512 += (actual >> 9); + ptr = sp->w_bins; + } + else + { + sp->r_total++; + sp->r_total512 += (actual >> 9); + ptr = sp->r_bins; + } + for (x = 9; x <= 17; x++) + { + if (actual < (1 << x)) + { + ptr[x - 9]++; + break; + } + } + if (x > 17) + { + ptr[x - 9]++; + } + } + } +#endif /* AIC7XXX_PROC_STATS */ } /*+F************************************************************************* * Function: - * aic7xxx_done_aborted_scbs + * aic7xxx_run_done_queue * * Description: - * Calls the scsi_done() for the Scsi_Cmnd of each scb in the - * aborted list, and adds each scb to the free list. + * Calls the aic7xxx_done() for the Scsi_Cmnd of each scb in the + * aborted list, and adds each scb to the free list. If complete + * is TRUE, we also process the commands complete list. *-F*************************************************************************/ static void -aic7xxx_done_aborted_scbs(struct aic7xxx_host *p) +aic7xxx_run_done_queue(struct aic7xxx_host *p, /*complete*/ int complete) { - Scsi_Cmnd *cmd; struct aic7xxx_scb *scb; int i; - for (i = 0; i < p->scb_link->numscbs; i++) + for (i = 0; i < p->scb_data->numscbs; i++) { - scb = (p->scb_array[i]); - if (scb->state & SCB_QUEUED_FOR_DONE) + scb = p->scb_data->scb_array[i]; + if (scb->flags & SCB_QUEUED_FOR_DONE) { #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (done_aborted_scbs) Aborting scb %d, TCL=%d/%d/%d\n", - scb->position, TCL_OF_SCB(scb)); + printk("(scsi%d:%d:%d) Aborting scb %d\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag); #endif - /* - * Process the command after marking the scb as free - * and adding it to the free list. - */ - cmd = scb->cmd; - p->device_status[TARGET_INDEX(cmd)].flags = 0; - aic7xxx_free_scb(p, scb); - cmd->scsi_done(cmd); /* call the done function */ + aic7xxx_done(p, scb); } } -} - -/*+F************************************************************************* - * Function: - * aic7xxx_add_waiting_scb - * - * Description: - * Add this SCB to the head of the "waiting for selection" list. - *-F*************************************************************************/ -static void -aic7xxx_add_waiting_scb(u_long base, struct aic7xxx_scb *scb) -{ - unsigned char next; - unsigned char curscb; - - curscb = inb(SCBPTR + base); - next = inb(WAITING_SCBH + base); - - outb(scb->position, SCBPTR + base); - outb(next, SCB_NEXT + base); - outb(scb->position, WAITING_SCBH + base); - - outb(curscb, SCBPTR + base); + if (complete) + { + aic7xxx_done_cmds_complete(p); + } } /*+F************************************************************************* @@ -1726,26 +2074,23 @@ aic7xxx_add_waiting_scb(u_long base, struct aic7xxx_scb *scb) *-F*************************************************************************/ static unsigned char aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, - unsigned char prev) + unsigned char scbpos, unsigned char prev) { unsigned char curscb, next; - int target = (scb->target_channel_lun >> 4) & 0x0F; - char channel = (scb->target_channel_lun & SELBUSB) ? 'B' : 'A'; - int base = p->base; /* * Select the SCB we want to abort and pull the next pointer out of it. */ - curscb = inb(SCBPTR + base); - outb(scb->position, SCBPTR + base); - next = inb(SCB_NEXT + base); + curscb = inb(p->base + SCBPTR); + outb(scbpos, p->base + SCBPTR); + next = inb(p->base + SCB_NEXT); /* * Clear the necessary fields */ - outb(0, SCB_CONTROL + base); - outb(SCB_LIST_NULL, SCB_NEXT + base); - aic7xxx_unbusy_target(target, channel, base); + outb(0, p->base + SCB_CONTROL); + + aic7xxx_add_curscb_to_free_list(p); /* * Update the waiting list @@ -1755,22 +2100,23 @@ aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, /* * First in the list */ - outb(next, WAITING_SCBH + base); + outb(next, p->base + WAITING_SCBH); } else { /* * Select the scb that pointed to us and update its next pointer. */ - outb(prev, SCBPTR + base); - outb(next, SCB_NEXT + base); + outb(prev, p->base + SCBPTR); + outb(next, p->base + SCB_NEXT); } /* * Point us back at the original scb position and inform the SCSI * system that the command has been aborted. */ - outb(curscb, SCBPTR + base); - scb->state |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; + outb(curscb, p->base + SCBPTR); + scb->flags |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; + scb->flags &= ~SCB_ACTIVE; scb->cmd->result = (DID_RESET << 16); return (next); @@ -1778,6 +2124,75 @@ aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, /*+F************************************************************************* * Function: + * aic7xxx_search_qinfifo + * + * Description: + * Search the queue-in FIFO for matching SCBs and conditionally + * requeue. Returns the number of matching SCBs. + *-F*************************************************************************/ +static int +aic7xxx_search_qinfifo(struct aic7xxx_host *p, int target, char channel, + int lun, unsigned char tag, int flags, int requeue) +{ + unsigned char saved_queue[AIC7XXX_MAXSCB]; + int queued = inb(p->base + QINCNT) & p->qcntmask; + int i; + int found; + struct aic7xxx_scb *scbp; + scb_queue_type removed_scbs; + + found = 0; + scbq_init (&removed_scbs); + for (i = 0; i < (queued - found); i++) + { + saved_queue[i] = inb(p->base + QINFIFO); + scbp = p->scb_data->scb_array[saved_queue[i]]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) + { + /* + * We found an scb that needs to be removed. + */ + if (requeue) + { + scbq_insert_head(&removed_scbs, scbp); + } + else + { + scbp->flags = flags; + scbp->flags &= ~SCB_ACTIVE; + /* + * XXX - Don't know what error to use here. + */ + aic7xxx_error(scbp->cmd) = DID_RESET; + } + i--; + found++; + } + } + /* Now put the saved scbs back. */ + for (queued = 0; queued < i; queued++) + outb(saved_queue[queued], p->base + QINFIFO); + + if (requeue) + { + scbp = removed_scbs.head; + while (scbp != NULL) + { + scbq_remove_head(&removed_scbs); + /* + * XXX - Shouldn't we be adding this to the free list? + */ + scbq_insert_head(&p->waiting_scbs, scbp); + scbp->flags |= SCB_WAITINGQ; + scbp = removed_scbs.head; + } + } + + return (found); +} + +/*+F************************************************************************* + * Function: * aic7xxx_reset_device * * Description: @@ -1785,131 +2200,280 @@ aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, * all active and queued scbs for that target/channel. *-F*************************************************************************/ static int -aic7xxx_reset_device(struct aic7xxx_host *p, int target, char channel) +aic7xxx_reset_device(struct aic7xxx_host *p, int target, char channel, + int lun, unsigned char tag) { - int base = p->base; - struct aic7xxx_scb *scb; + struct aic7xxx_scb *scbp; unsigned char active_scb; int i = 0; - int found = 0; + int found; /* * Restore this when we're done */ - active_scb = inb(SCBPTR + base); + active_scb = inb(p->base + SCBPTR); #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_device) target/channel %d/%c, active_scb %d\n", - target, channel, active_scb); + printk("(scsi%d:%d:%d) Reset device, active_scb %d\n", + p->host_no, target, CHAN_TO_INT(channel), active_scb); #endif + /* - * Search the QINFIFO. + * Deal with the busy target and linked next issues. */ { - int saved_queue[AIC7XXX_MAXSCB]; - int queued = inb(QINCNT + base) & p->qcntmask; + int min_target, max_target; + unsigned char busy_scbid; - for (i = 0; i < (queued - found); i++) + /* Make all targets 'relative' to bus A. */ + if (target == ALL_TARGETS) { - saved_queue[i] = inb(QINFIFO + base); - outb(saved_queue[i], SCBPTR + base); - scb = (p->scb_array[inb(SCB_TAG + base)]); - if (aic7xxx_match_scb(scb, target, channel)) + switch (channel) { - /* - * We found an scb that needs to be aborted. - */ -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_device) aborting SCB %d, TCL=%d/%d/%d\n", - saved_queue[i], TCL_OF_SCB(scb)); -#endif - scb->state |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; - scb->cmd->result = (DID_RESET << 16); - outb(0, SCB_CONTROL + base); - i--; - found++; + case 'A': + min_target = 0; + max_target = (p->bus_type == AIC_SINGLE) ? 7 : 15; + break; + case 'B': + min_target = 8; + max_target = 15; + break; + case ALL_CHANNELS: + default: + min_target = 0; + max_target = (p->bus_type == AIC_SINGLE) ? 7 : 15; + break; } } - /* - * Now put the saved scbs back. - */ - for (queued = 0; queued < i; queued++) + else + { + min_target = target + channel == 'B' ? 8 : 0; + max_target = min_target; + } + + for (i = min_target; i <= max_target; i++) { - outb(saved_queue[queued], QINFIFO + base); + busy_scbid = aic7xxx_index_busy_target(p, i, 'A', /*unbusy*/FALSE); + if (busy_scbid < p->scb_data->numscbs) + { + struct aic7xxx_scb *busy_scb; + struct aic7xxx_scb *next_scb; + unsigned char next_scbid; + + busy_scb = p->scb_data->scb_array[busy_scbid]; + + next_scbid = busy_scb->hscb->data_count >> 24; + + if (next_scbid == SCB_LIST_NULL) + { + busy_scbid = aic7xxx_find_scb(p, busy_scb); + + if (busy_scbid != SCB_LIST_NULL) + { + outb(busy_scbid, p->base + SCBPTR); + next_scbid = inb(p->base + SCB_LINKED_NEXT); + } + } + + if (aic7xxx_match_scb(busy_scb, target, channel, lun, tag)) + { + aic7xxx_index_busy_target(p, i, 'A', /*unbusy*/TRUE); + } + + if (next_scbid != SCB_LIST_NULL) + { + next_scb = p->scb_data->scb_array[next_scbid]; + if (aic7xxx_match_scb(next_scb, target, channel, lun, tag)) + { + continue; + } + /* Requeue for later processing */ + scbq_insert_head(&p->waiting_scbs, next_scb); + next_scb->flags |= SCB_WAITINGQ; + } + } } } + found = aic7xxx_search_qinfifo(p, target, channel, lun, tag, + SCB_ABORTED | SCB_QUEUED_FOR_DONE, /* requeue */ FALSE); + /* * Search waiting for selection list. */ { - unsigned char next, prev; + unsigned char next, prev, scb_index; - next = inb(WAITING_SCBH + base); /* Start at head of list. */ + next = inb(p->base + WAITING_SCBH); /* Start at head of list. */ prev = SCB_LIST_NULL; while (next != SCB_LIST_NULL) { - outb(next, SCBPTR + base); - scb = (p->scb_array[inb(SCB_TAG + base)]); - /* - * Select the SCB. - */ - if (aic7xxx_match_scb(scb, target, channel)) + outb(next, p->base + SCBPTR); + scb_index = inb(p->base + SCB_TAG); + if (scb_index >= p->scb_data->numscbs) + { + panic("aic7xxx: Waiting List inconsistency; SCB index=%d, numscbs=%d\n", + scb_index, p->scb_data->numscbs); + } + scbp = p->scb_data->scb_array[scb_index]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) { - next = aic7xxx_abort_waiting_scb(p, scb, prev); + unsigned char linked_next; + + next = aic7xxx_abort_waiting_scb(p, scbp, next, prev); + linked_next = inb(p->base + SCB_LINKED_NEXT); + if (linked_next != SCB_LIST_NULL) + { + struct aic7xxx_scb *next_scb; + /* + * Requeue the waiting SCB via the waiting list. + */ + next_scb = p->scb_data->scb_array[linked_next]; + if (! aic7xxx_match_scb(next_scb, target, channel, lun, tag)) + { + scbq_insert_head(&p->waiting_scbs, next_scb); + next_scb->flags |= SCB_WAITINGQ; + } + } found++; } else { prev = next; - next = inb(SCB_NEXT + base); + next = inb(p->base + SCB_NEXT); + } + } + } + + /* + * Go through disconnected list and remove any entries we have queued + * for completion, zeroing their control byte too. + */ + { + unsigned char next, prev, scb_index; + + next = inb(p->base + DISCONNECTED_SCBH); + prev = SCB_LIST_NULL; + + while (next != SCB_LIST_NULL) + { + outb(next, p->base + SCBPTR); + scb_index = inb(p->base + SCB_TAG); + if (scb_index > p->scb_data->numscbs) + { + panic("aic7xxx: Disconnected List inconsistency, SCB index = %d, " + "num scbs = %d.\n", scb_index, p->scb_data->numscbs); + } + scbp = p->scb_data->scb_array[scb_index]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) + { + next = aic7xxx_rem_scb_from_disc_list(p, next); + } + else + { + prev = next; + next = inb(p->base + SCB_NEXT); + } + } + } + + /* + * Go through the hardware SCB array looking for commands that + * were active but not on any list. + */ + for (i = 0; i < p->scb_data->maxhscbs; i++) + { + unsigned char scbid; + + outb(i, p->base + SCBPTR); + scbid = inb(p->base + SCB_TAG); + if (scbid < p->scb_data->numscbs) + { + scbp = p->scb_data->scb_array[scbid]; + if (aic7xxx_match_scb(scbp, target, channel, lun, tag)) + { + aic7xxx_add_curscb_to_free_list(p); } } } /* * Go through the entire SCB array now and look for commands for - * for this target that are active. These are other (most likely + * for this target that are stillactive. These are other (most likely * tagged) commands that were disconnected when the reset occurred. */ - for (i = 0; i < p->scb_link->numscbs; i++) + for (i = 0; i < p->scb_data->numscbs; i++) { - scb = (p->scb_array[i]); - if ((scb->state & SCB_ACTIVE) && aic7xxx_match_scb(scb, target, channel)) + scbp = p->scb_data->scb_array[i]; + if (((scbp->flags & SCB_ACTIVE) != 0) && + aic7xxx_match_scb(scbp, target, channel, lun, tag)) { - /* - * Ensure the target is "free" - */ - aic7xxx_unbusy_target(target, channel, base); - if (! (scb->state & SCB_PAGED_OUT)) + scbp->flags |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; + scbp->flags &= ~SCB_ACTIVE; + aic7xxx_error(scbp->cmd) = DID_RESET; + + found++; + + if ((scbp->flags & SCB_WAITINGQ) != 0) { - outb(scb->position, SCBPTR + base); - outb(0, SCB_CONTROL + base); + scbq_remove(&p->waiting_scbs, scbp); + scbp->flags &= ~SCB_WAITINGQ; } - scb->state |= SCB_ABORTED | SCB_QUEUED_FOR_DONE; - scb->cmd->result = (DID_RESET << 16); - found++; } } - outb(active_scb, SCBPTR + base); + outb(active_scb, p->base + SCBPTR); return (found); } /*+F************************************************************************* * Function: + * aic7xxx_clear_intstat + * + * Description: + * Clears the interrupt status. + *-F*************************************************************************/ +static void +aic7xxx_clear_intstat(struct aic7xxx_host *p) +{ + /* Clear any interrupt conditions this may have caused. */ + outb(CLRSELDO | CLRSELDI | CLRSELINGO, p->base + CLRSINT0); + outb(CLRSELTIMEO | CLRATNO | CLRSCSIRSTI | CLRBUSFREE | CLRSCSIPERR | + CLRPHASECHG | CLRREQINIT, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); +} + +/*+F************************************************************************* + * Function: * aic7xxx_reset_current_bus * * Description: * Reset the current SCSI bus. *-F*************************************************************************/ static void -aic7xxx_reset_current_bus(int base) +aic7xxx_reset_current_bus(struct aic7xxx_host *p) { - outb(SCSIRSTO, SCSISEQ + base); + unsigned char scsiseq; + + /* Disable reset interrupts. */ + outb(inb(p->base + SIMODE1) & ~ENSCSIRST, p->base + SIMODE1); + + /* Turn on the bus reset. */ + scsiseq = inb(p->base + SCSISEQ); + outb(scsiseq | SCSIRSTO, p->base + SCSISEQ); + + udelay(1000); + + /* Turn off the bus reset. */ + outb(scsiseq & ~SCSIRSTO, p->base + SCSISEQ); + + aic7xxx_clear_intstat(p); + + /* Re-enable reset interrupts. */ + outb(inb(p->base + SIMODE1) | ENSCSIRST, p->base + SIMODE1); + udelay(1000); - outb(0, SCSISEQ + base); } /*+F************************************************************************* @@ -1922,25 +2486,24 @@ aic7xxx_reset_current_bus(int base) static int aic7xxx_reset_channel(struct aic7xxx_host *p, char channel, int initiate_reset) { - int base = p->base; - unsigned char sblkctl; - char cur_channel; unsigned long offset, offset_max; int found; + unsigned char sblkctl; + char cur_channel; + pause_sequencer(p); /* - * Clean up all the state information for the - * pending transactions on this bus. + * Clean up all the state information for the pending transactions + * on this bus. */ - found = aic7xxx_reset_device(p, ALL_TARGETS, channel); + found = aic7xxx_reset_device(p, ALL_TARGETS, channel, ALL_LUNS, SCB_LIST_NULL); if (channel == 'B') { p->needsdtr |= (p->needsdtr_copy & 0xFF00); p->sdtr_pending &= 0x00FF; - outb(0, ACTIVE_B + base); - offset = TARG_SCRATCH + base + 8; - offset_max = TARG_SCRATCH + base + 16; + offset = TARG_SCRATCH + 8; + offset_max = TARG_SCRATCH + 16; } else { @@ -1950,132 +2513,100 @@ aic7xxx_reset_channel(struct aic7xxx_host *p, char channel, int initiate_reset) p->needwdtr = p->needwdtr_copy; p->sdtr_pending = 0x0; p->wdtr_pending = 0x0; - outb(0, ACTIVE_A + base); - outb(0, ACTIVE_B + base); - offset = TARG_SCRATCH + base; - offset_max = TARG_SCRATCH + base + 16; + offset = TARG_SCRATCH; + offset_max = TARG_SCRATCH + 16; } else { + /* Channel A */ p->needsdtr |= (p->needsdtr_copy & 0x00FF); p->sdtr_pending &= 0xFF00; - outb(0, ACTIVE_A + base); - offset = TARG_SCRATCH + base; - offset_max = TARG_SCRATCH + base + 8; + offset = TARG_SCRATCH; + offset_max = TARG_SCRATCH + 8; } } + while (offset < offset_max) { /* - * Revert to async/narrow transfers - * until we renegotiate. + * Revert to async/narrow transfers until we renegotiate. */ u_char targ_scratch; - targ_scratch = inb(offset); + + targ_scratch = inb(p->base + offset); targ_scratch &= SXFR; - outb(targ_scratch, offset); + outb(targ_scratch, p->base + offset); offset++; } /* * Reset the bus and unpause/restart the controller */ - - /* - * Case 1: Command for another bus is active - */ - sblkctl = inb(SBLKCTL + base); + sblkctl = inb(p->base + SBLKCTL); cur_channel = (sblkctl & SELBUSB) ? 'B' : 'A'; if (cur_channel != channel) { + /* + * Case 1: Command for another bus is active + */ #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_channel) Stealthily resetting channel %c\n", - channel); + printk("scsi%d: Stealthily resetting channel %c\n", + p->host_no, channel); #endif /* - * Stealthily reset the other bus without upsetting the current bus + * Stealthily reset the other bus without upsetting the current bus. */ - outb(sblkctl ^ SELBUSB, SBLKCTL + base); + outb(sblkctl ^ SELBUSB, p->base + SBLKCTL); + outb(inb(p->base + SIMODE1) & ~ENBUSFREE, p->base + SIMODE1); if (initiate_reset) { - aic7xxx_reset_current_bus(base); + aic7xxx_reset_current_bus(p); + /* + * Cause the mid-level SCSI code to delay any further + * queueing by the bus settle time for us. + */ + p->host->last_reset = (jiffies + (AIC7XXX_RESET_DELAY * HZ)); } - outb(CLRSCSIRSTI | CLRSELTIMEO, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); - outb(sblkctl, SBLKCTL + base); - - UNPAUSE_SEQUENCER(p); + outb(0, p->base + SCSISEQ); + aic7xxx_clear_intstat(p); + outb(sblkctl, p->base + SBLKCTL); + unpause_sequencer(p, /* unpause_always */ FALSE); } - /* - * Case 2: A command from this bus is active or we're idle - */ else { + /* + * Case 2: A command from this bus is active or we're idle. + */ #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_channel) Resetting current channel %c\n", - channel); + printk("scsi%d: Resetting current channel %c\n", + p->host_no, channel); #endif + outb(inb(p->base + SIMODE1) & ~ENBUSFREE, p->base + SIMODE1); if (initiate_reset) { - aic7xxx_reset_current_bus(base); + aic7xxx_reset_current_bus(p); + /* + * Cause the mid-level SCSI code to delay any further + * queueing by the bus settle time for us. + */ +#if 0 + p->host->last_reset = (jiffies + (AIC7XXX_RESET_DELAY * HZ)); +#endif } - outb(CLRSCSIRSTI | CLRSELTIMEO, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); - RESTART_SEQUENCER(p); + outb(0, p->base + SCSISEQ); + aic7xxx_clear_intstat(p); + restart_sequencer(p); #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset_channel) Channel reset, sequencer restarted\n"); + printk("scsi%d: Channel reset, sequencer restarted\n", p->host_no); #endif } /* - * Cause the mid-level SCSI code to delay any further - * queueing by the bus settle time for us. - */ - p->host->last_reset = (jiffies + (AIC7XXX_RESET_DELAY * HZ)); - - /* * Now loop through all the SCBs that have been marked for abortion, * and call the scsi_done routines. */ - aic7xxx_done_aborted_scbs(p); - return found; -} - -/*+F************************************************************************* - * Function: - * aic7xxx_page_scb - * - * Description: - * Swap in_scbp for out_scbp down in the cards SCB array. - * We assume that the SCB for out_scbp is already selected in SCBPTR. - * - *-F*************************************************************************/ -static inline void -aic7xxx_page_scb(struct aic7xxx_host *p, struct aic7xxx_scb *out_scbp, - struct aic7xxx_scb *in_scbp) -{ - int index; - - /* Page-out */ -#if 0 -printk("aic7xxx: Paging out target %d SCB and paging in target %d SCB\n", - out_scbp->cmd->target, in_scbp->cmd->target); -#endif - aic7xxx_getscb(p, out_scbp); - out_scbp->state |= SCB_PAGED_OUT; - if (!(out_scbp->control & TAG_ENB)) - { - /* Stick in non-tagged array */ - index = (out_scbp->target_channel_lun >> 4) | - (out_scbp->target_channel_lun & SELBUSB); - p->pagedout_ntscbs[index] = out_scbp; - } - - /* Page-in */ - in_scbp->position = out_scbp->position; - out_scbp->position = SCB_LIST_NULL; - aic7xxx_putscb(p, in_scbp); - in_scbp->state &= ~SCB_PAGED_OUT; + aic7xxx_run_done_queue(p, /*complete*/ TRUE); + return (found); } /*+F************************************************************************* @@ -2083,1159 +2614,1326 @@ printk("aic7xxx: Paging out target %d SCB and paging in target %d SCB\n", * aic7xxx_run_waiting_queues * * Description: - * Scan the assigned_scbs and waiting_scbs queues. For scbs in the - * assigned_scbs queue, we download and start them. For scbs in the - * waiting_scbs queue, we page in as many as we can being careful - * not to cause a deadlock for a reconnecting target. - * + * Scan the awaiting_scbs queue downloading and starting as many + * scbs as we can. *-F*************************************************************************/ static inline void aic7xxx_run_waiting_queues(struct aic7xxx_host *p) { struct aic7xxx_scb *scb; - u_char cur_scb, intstat; - u_long base = p->base; - long flags; - if ((p->assigned_scbs.head == NULL) && (p->waiting_scbs.head == NULL)) + if (p->waiting_scbs.head == NULL) return; - save_flags(flags); - cli(); - - PAUSE_SEQUENCER(p); - cur_scb = inb(SCBPTR + base); - intstat = inb(INTSTAT + base); - + pause_sequencer(p); /* * First handle SCBs that are waiting but have been assigned a slot. */ - scb = p->assigned_scbs.head; - while (scb != NULL) - { - scbq_remove_head(&(p->assigned_scbs)); - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - /* Mark this as an active command. */ - scb->state = (scb->state & ~SCB_ASSIGNEDQ) | SCB_ACTIVE; - outb(scb->position, QINFIFO + base); - scb = p->assigned_scbs.head; - } - - /* Now deal with SCBs that require paging. */ scb = p->waiting_scbs.head; - if (scb != NULL) + while (scb != NULL) { - u_char disc_scb = inb(DISCONNECTED_SCBH + base); - u_char active = inb(FLAGS + base) & (SELECTED | IDENTIFY_SEEN); - int count = 0; - u_char next_scb; - - while (scb != NULL) + if (p->curqincnt >= p->qfullcount) { - /* Attempt to page this SCB in */ - if (disc_scb == SCB_LIST_NULL) - break; - - /* - * Advance disc_scb to the next one in the list. - */ - outb(disc_scb, SCBPTR + base); - next_scb = inb(SCB_NEXT + base); - - /* - * We have to be careful about when we allow an SCB to be paged out. - * There must always be at least one slot availible for a reconnecting - * target in case it references an SCB that has been paged out. Our - * heuristic is that either the disconnected list has at least two - * entries in it or there is one entry and the sequencer is activily - * working on an SCB which implies that it will either complete or - * disconnect before another reconnection can occur. - */ - if ((next_scb != SCB_LIST_NULL) || active) + p->curqincnt = inb(p->base + QINCNT) & p->qcntmask; + if (p->curqincnt >= p->qfullcount) { - u_char out_scbi; - struct aic7xxx_scb *out_scbp; - - scbq_remove_head(&(p->waiting_scbs)); - - /* - * Find the in-core SCB for the one we're paging out. - */ - out_scbi = inb(SCB_TAG + base); - out_scbp = (p->scb_array[out_scbi]); - - /* Do the page out and mark the paged in SCB as active. */ - aic7xxx_page_scb(p, out_scbp, scb); - - /* Mark this as an active command. */ - scb->state = (scb->state & ~SCB_WAITINGQ) | SCB_ACTIVE; - - /* Queue the command */ - outb(scb->position, QINFIFO + base); - count++; - - /* Advance to the next disconnected SCB */ - disc_scb = next_scb; - scb = p->waiting_scbs.head; + break; } - else - scb = NULL; } - if (count) + /* + * We have some space. + */ + scbq_remove_head(&(p->waiting_scbs)); + scb->flags &= ~SCB_WAITINGQ; + + outb(scb->hscb->tag, p->base + QINFIFO); + + if ((p->flags & PAGE_ENABLED) != 0) { - /* - * Update the head of the disconnected list. + /* + * We only care about this statistic when paging + * since it's impossible to overflow the qinfifo + * in the non-paging case. */ - outb(disc_scb, DISCONNECTED_SCBH + base); - if (disc_scb != SCB_LIST_NULL) - { - outb(disc_scb, SCBPTR + base); - outb(SCB_LIST_NULL, SCB_PREV + base); - } + p->curqincnt++; } + scb = p->waiting_scbs.head; } - /* Restore old position */ - outb(cur_scb, SCBPTR + base); - /* - * Guard against unpausing the sequencer if there is an interrupt - * waiting to happen. - */ - if (!(intstat & (BRKADRINT | SEQINT | SCSIINT))) - { - UNPAUSE_SEQUENCER(p); - } + unpause_sequencer(p, FALSE); +} - restore_flags(flags); +/*+F************************************************************************* + * Function: + * aic7xxx_construct_sdtr + * + * Description: + * Constucts a synchronous data transfer message in the message + * buffer on the sequencer. + *-F*************************************************************************/ +static void +aic7xxx_construct_sdtr(struct aic7xxx_host *p, int start_byte, + unsigned char period, unsigned char offset) +{ + outb(MSG_EXTENDED, p->base + MSG_OUT + start_byte); + outb(MSG_EXT_SDTR_LEN, p->base + MSG_OUT + 1 + start_byte); + outb(MSG_EXT_SDTR, p->base + MSG_OUT + 2 + start_byte); + outb(period, p->base + MSG_OUT + 3 + start_byte); + outb(offset, p->base + MSG_OUT + 4 + start_byte); + outb(start_byte + 5, p->base + MSG_LEN); } /*+F************************************************************************* * Function: - * aic7xxx_isr + * aic7xxx_construct_wdtr * * Description: - * SCSI controller interrupt handler. + * Constucts a wide data transfer message in the message buffer + * on the sequencer. + *-F*************************************************************************/ +static void +aic7xxx_construct_wdtr(struct aic7xxx_host *p, int start_byte, + unsigned char bus_width) +{ + outb(MSG_EXTENDED, p->base + MSG_OUT + start_byte); + outb(MSG_EXT_WDTR_LEN, p->base + MSG_OUT + 1 + start_byte); + outb(MSG_EXT_WDTR, p->base + MSG_OUT + 2 + start_byte); + outb(bus_width, p->base + MSG_OUT + 3 + start_byte); + outb(start_byte + 4, p->base + MSG_LEN); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_calc_residual * - * NOTE: Since we declared this using SA_INTERRUPT, interrupts should - * be disabled all through this function unless we say otherwise. + * Description: + * Calculate the residual data not yet transferred. *-F*************************************************************************/ static void -aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) +aic7xxx_calculate_residual (struct aic7xxx_host *p, struct aic7xxx_scb *scb) { - int base, intstat, actual, scb_index, run_aborted_queue = FALSE; - struct aic7xxx_host *p; - struct aic7xxx_scb *scb = NULL; - short transfer; - unsigned char ha_flags, scsi_id, bus_width; - unsigned char offset, rate, scratch, scratch_offset; - unsigned char max_offset, rej_byte; - unsigned short target_mask; - char channel; - unsigned int addr; /* must be 32 bits */ + struct aic7xxx_hwscb *hscb; Scsi_Cmnd *cmd; + int actual; - p = (struct aic7xxx_host *) aic7xxx_boards[irq]->hostdata; + cmd = scb->cmd; + hscb = scb->hscb; /* - * Search for the host with a pending interrupt. If we can't find - * one, then we've encountered a spurious interrupt. + * Don't destroy valid residual information with + * residual coming from a check sense operation. */ - while ((p != NULL) && !(inb(INTSTAT + p->base) & INT_PEND)) + if (((scb->hscb->control & DISCONNECTED) == 0) && + (scb->flags & SCB_SENSE) == 0) { - if (p->next == NULL) - { - p = NULL; - } - else + /* + * We had an underflow. At this time, there's only + * one other driver that bothers to check for this, + * and cmd->underflow seems to be set rather half- + * heartedly in the higher-level SCSI code. + */ + actual = aic7xxx_length(cmd, hscb->residual_SG_segment_count); + + actual -= (hscb->residual_data_count[2] << 16) | + (hscb->residual_data_count[1] << 8) | + hscb->residual_data_count[0]; + + if (actual < cmd->underflow) { - p = (struct aic7xxx_host *) p->next->hostdata; + printk(KERN_WARNING "(scsi%d:%d:%d) Underflow - " + "Wanted at least %u, got %u, residual SG count %d.\n", + p->host_no, TC_OF_SCB(scb), cmd->underflow, actual, + hscb->residual_SG_segment_count); + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + aic7xxx_status(cmd) = hscb->target_status; } } - if (p == NULL) - return; - /* - * Keep track of interrupts for /proc/scsi + * Clean out the residual information in the SCB for the + * next consumer. */ - p->isr_count++; + hscb->residual_data_count[2] = 0; + hscb->residual_data_count[1] = 0; + hscb->residual_data_count[0] = 0; + hscb->residual_SG_segment_count = 0; +} - if (!(p->flags & A_SCANNED) && (p->isr_count == 1)) +/*+F************************************************************************* + * Function: + * aic7xxx_handle_device_reset + * + * Description: + * Interrupt handler for sequencer interrupts (SEQINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_device_reset(struct aic7xxx_host *p, int target, char channel) +{ + unsigned short targ_mask; + unsigned char targ_scratch; + int scratch_offset = target; + int found; + + if (channel == 'B') { - /* - * We must only have one card at this IRQ and it must have been - * added to the board data before the spurious interrupt occurred. - * It is sufficient that we check isr_count and not the spurious - * interrupt count. - */ - printk("aic7xxx: (aic7xxx_isr) Encountered spurious interrupt.\n"); - return; + scratch_offset += 8; } - - base = p->base; + targ_mask = (0x01 << scratch_offset); /* - * Handle all the interrupt sources - especially for SCSI - * interrupts, we won't get a second chance at them. + * Go back to async/narrow transfers and renegotiate. */ - intstat = inb(INTSTAT + base); + p->needsdtr |= p->needsdtr_copy & targ_mask; + p->needwdtr |= p->needwdtr_copy & targ_mask; + p->sdtr_pending &= ~targ_mask; + p->wdtr_pending &= ~targ_mask; + targ_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + targ_scratch &= SXFR; + outb(targ_scratch, p->base + TARG_SCRATCH + scratch_offset); + found = aic7xxx_reset_device(p, target, channel, ALL_LUNS, SCB_LIST_NULL); + printk(KERN_WARNING "(scsi%d:%d:%d) Bus Device Reset delivered, " + "%d SCBs aborted.\n", p->host_no, target, CHAN_TO_INT(channel), found); + aic7xxx_run_done_queue(p, /*complete*/ TRUE); +} - /* - * Indicate that we're in the interrupt handler. - */ - p->flags |= IN_ISR; +/*+F************************************************************************* + * Function: + * aic7xxx_handle_seqint + * + * Description: + * Interrupt handler for sequencer interrupts (SEQINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_seqint(struct aic7xxx_host *p, unsigned char intstat) +{ + struct aic7xxx_scb *scb; + unsigned short target_mask; + unsigned char target, scratch_offset; + char channel; - if (intstat & BRKADRINT) + if ((inb(p->base + SEQ_FLAGS) & RESELECTED) != 0) { - int i; - unsigned char errno = inb(ERROR + base); - - printk(KERN_ERR "scsi%d: BRKADRINT error(0x%x):\n", p->host_no, errno); - for (i = 0; i < NUMBER(hard_error); i++) - { - if (errno & hard_error[i].errno) - { - printk(KERN_ERR " %s\n", hard_error[i].errmesg); - } - } - panic("scsi%d: BRKADRINT, error 0x%x, seqaddr 0x%x.\n", p->host_no, - inb(ERROR + base), (inb(SEQADDR1 + base) << 8) | inb(SEQADDR0 + base)); + target = (inb(p->base + SELID) >> 4) & 0x0F; } + else + { + target = (inb(p->base + SCSIID) >> 4) & 0x0F; + } + scratch_offset = target; + channel = 'A'; + if (inb(p->base + SBLKCTL) & SELBUSB) + { + channel = 'B'; + scratch_offset += 8; + } + target_mask = (0x01 << scratch_offset); - if (intstat & SEQINT) + switch (intstat & SEQINT_MASK) { - /* - * Although the sequencer is paused immediately on - * a SEQINT, an interrupt for a SCSIINT condition will - * unpaused the sequencer before this point. - */ - PAUSE_SEQUENCER(p); + case NO_MATCH: + { + /* + * This could be for a normal abort request. Figure out + * which SCB we were trying to find and only give an error + * if we didn't ask for this to happen. + */ + unsigned char scb_index; + unsigned char busy_scbid; + unsigned char arg1; - scsi_id = (inb(SCSIID + base) >> 4) & 0x0F; - scratch_offset = scsi_id; - channel = 'A'; - if (inb(SBLKCTL + base) & SELBUSB) - { - channel = 'B'; - scratch_offset += 8; - } - target_mask = (0x01 << scratch_offset); + busy_scbid = aic7xxx_index_busy_target(p, target, channel, + /*unbusy*/ FALSE); + arg1 = inb(p->base + ARG_1); - switch (intstat & SEQINT_MASK) - { - case NO_MATCH: - if (p->flags & PAGE_ENABLED) + if (arg1 == SCB_LIST_NULL) { - /* SCB Page-in request */ - struct aic7xxx_scb *outscb; - u_char arg_1 = inb(ARG_1 + base); - int use_disconnected = FALSE; - - /* - * The sequencer expects this value upon return. Assume - * we will find the paged out SCB and set the value now. - * If we don't, and one of the methods used to acquire an - * SCB calls aic7xxx_done(), we will end up in our queue - * routine and unpause the sequencer without giving it the - * correct return value, which causes a hang. - */ - outb(SCB_PAGEDIN, RETURN_1 + base); - if (arg_1 == SCB_LIST_NULL) - { - /* Non-tagged command */ - int index = scsi_id; - if (channel == 'B') - { - index |= SELBUSB; - } - scb = p->pagedout_ntscbs[index]; - } - else - scb = (p->scb_array[arg_1]); + /* untagged request */ + scb_index = busy_scbid; + } + else + { + scb_index = arg1; + } - if (!(scb->state & SCB_PAGED_OUT)) + if (scb_index < p->scb_data->numscbs) + { + scb = p->scb_data->scb_array[scb_index]; + if (scb->hscb->control & ABORT_SCB) { - printk(KERN_WARNING "scsi%d: No active paged-out SCB for reconnecting " - "target %d, channel %c - Issuing ABORT. SAVED_TCL(0x%x).\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - aic7xxx_unbusy_target(scsi_id, channel, base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(0, RETURN_1 + base); + /* + * We expected this. Let the busfree handler take care + * of this when we the abort is finially sent. Set + * IDENTIFY_SEEN so that the busfree handler knows that + * there is an SCB to cleanup. + */ + outb(inb(p->base + SEQ_FLAGS) | IDENTIFY_SEEN, p->base + SEQ_FLAGS); + printk(KERN_INFO "(scsi%d:%d:%d) reconnect SCB abort successful\n", + p->host_no, TC_OF_SCB(scb)); break; } + } + printk(KERN_WARNING "(scsi%d:%d:%d) No active SCB for reconnecting " + "target - Issuing BUS DEVICE RESET.\n", + p->host_no, target, CHAN_TO_INT(channel)); + + printk(KERN_WARNING " SAVED_TCL=0x%x, ARG_1=0x%x, SEQADDR=0x%x\n", + inb(p->base + SAVED_TCL), arg1, + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + aic7xxx_handle_device_reset(p, target, channel); + } + break; - /* - * Now to pick the SCB to page out. Either take a free SCB, an - * assigned SCB, an SCB that just completed, or the first one - * on the disconnected SCB list. - */ - if (p->scb_link->free_scbs.head != NULL) - { - outscb = p->scb_link->free_scbs.head; - scbq_remove_head(&p->scb_link->free_scbs); - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - scbq_insert_head(&p->page_scbs, outscb); - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; - } - else if (p->assigned_scbs.head != NULL) - { - outscb = p->assigned_scbs.head; - scbq_remove_head(&p->assigned_scbs); - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - scbq_insert_head(&p->waiting_scbs, outscb); - outscb->state = (outscb->state & ~SCB_ASSIGNEDQ) | SCB_WAITINGQ; - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; - } - else if (intstat & CMDCMPLT) - { - int scb_index; + case NO_MATCH_BUSY: + { + /* + * XXX - Leave this as a panic for the time being since it + * indicates a bug in the timeout code for this to happen. + */ + unsigned char scb_index; - outb(CLRCMDINT, CLRINT + base); - scb_index = inb(QOUTFIFO + base); - if (!(inb(QOUTCNT + base) & p->qcntmask)) - { - intstat &= ~CMDCMPLT; - } - outscb = (p->scb_array[scb_index]); - if (!(outscb->state & SCB_ACTIVE)) + scb_index = inb(p->base + CUR_SCBID); + scb = p->scb_data->scb_array[scb_index]; + + panic("scsi%d: Target %d, channel %c, Target busy link failure, " + "but busy SCB exists!\n", + p->host_no, target, channel); + } + break; + + case SEND_REJECT: + { + unsigned char rej_byte; + + rej_byte = inb(p->base + REJBYTE); + printk(KERN_WARNING "(scsi%d:%d:%d) Rejecting unknown message (0x%x) " + "received from target, SEQ_FLAGS=0x%x\n", + p->host_no, target, CHAN_TO_INT(channel), rej_byte, + inb(p->base + SEQ_FLAGS)); + } + break; + + case NO_IDENT: + { + /* + * The reconnecting target either did not send an identify + * message, or did, but we didn't find and SCB to match and + * before it could respond to our ATN/abort, it hit a dataphase. + * The only safe thing to do is to blow it away with a bus + * reset. + */ + int found; + + printk(KERN_WARNING "(scsi%d:%d:%d): Target did not send an IDENTIFY " + "message; LASTPHASE 0x%x, SAVED_TCL 0x%x\n", + p->host_no, target, CHAN_TO_INT(channel), + inb(p->base + LASTPHASE), inb(p->base + SAVED_TCL)); + + found = aic7xxx_reset_channel(p, channel, /*initiate reset*/ TRUE); + + printk(KERN_WARNING "scsi%d: Issued channel %c bus reset; " + "%d SCBs aborted\n", p->host_no, channel, found); + } + break; + + case BAD_PHASE: + if (inb(p->base + LASTPHASE) == P_BUSFREE) + { + printk(KERN_WARNING "(scsi%d:%d:%d): Missed busfree.\n", + p->host_no, CHAN_TO_INT(channel), target); + restart_sequencer(p); + } + else + { + printk(KERN_WARNING "(scsi%d:%d:%d): Unknown scsi bus phase, attempting " + "to continue\n", p->host_no, CHAN_TO_INT(channel), target); + } + break; + + case EXTENDED_MSG: + { + unsigned char message_length; + unsigned char message_code; + unsigned char scb_index; + + message_length = inb(p->base + MSGIN_EXT_LEN); + message_code = inb(p->base + MSGIN_EXT_OPCODE); + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; + + switch (message_code) + { + case MSG_EXT_SDTR: + { + unsigned char period; + unsigned char offset; + unsigned char saved_offset; + unsigned char targ_scratch; + unsigned char max_offset; + unsigned char rate; + + if (message_length != MSG_EXT_SDTR_LEN) { - printk(KERN_WARNING "scsi%d: No command for completed SCB %d " - "during NO_MATCH interrupt\n", scb_index, p->host_no); - use_disconnected = TRUE; + outb(SEND_REJ, p->base + RETURN_1); + break; } + + period = inb(p->base + MSGIN_EXT_BYTES); + saved_offset = inb(p->base + MSGIN_EXT_BYTES + 1); + targ_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + + if (targ_scratch & WIDEXFER) + max_offset = MAX_OFFSET_16BIT; else + max_offset = MAX_OFFSET_8BIT; + offset = MIN(saved_offset, max_offset); + + aic7xxx_scsirate(p, &rate, &period, &offset, target, channel); + + /* + * Preserve the WideXfer flag. + */ + targ_scratch = rate | (targ_scratch & WIDEXFER); + + /* + * Update both the target scratch area and current SCSIRATE. + */ + outb(targ_scratch, p->base + TARG_SCRATCH + scratch_offset); + outb(targ_scratch, p->base + SCSIRATE); + + /* + * See if we initiated Sync Negotiation and didn't have + * have to fall down to async transfers. + */ + if ((scb->flags & SCB_MSGOUT_SDTR) != 0) { - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - outb(scb->position, SCBPTR + base); - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; - outscb->cmd->result |= (aic7xxx_error(outscb->cmd) << 16); - if ((outscb->cmd->flags & WAS_SENSE) && - !(outscb->cmd->flags & ASKED_FOR_SENSE)) + /* We started it. */ + if (saved_offset == offset) { - /* - * Got sense information. - */ - outscb->cmd->flags &= ASKED_FOR_SENSE; + /* + * Don't send an SDTR back to the target. + */ + outb(0, p->base + RETURN_1); + } + else + { + /* We went too low - force async. */ + outb(SEND_REJ, p->base + RETURN_1); } - p->device_status[TARGET_INDEX(outscb->cmd)].flags - |= DEVICE_SUCCESS; - aic7xxx_done(p, outscb); } + else + { + /* + * Send our own SDTR in reply. + * + * We want to see this message as we don't expect a target + * to send us a SDTR request first. + */ + printk(KERN_WARNING "scsi%d: Sending SDTR!!\n", p->host_no); + aic7xxx_construct_sdtr(p, /* start byte */ 0, period, offset); + outb(SEND_MSG, p->base + RETURN_1); + } + /* + * Clear the flags. + */ + p->needsdtr &= ~target_mask; + break; } - else - { - use_disconnected = TRUE; - } - if (use_disconnected) + + case MSG_EXT_WDTR: { - u_char tag; - u_char next; - u_char disc_scb = inb(DISCONNECTED_SCBH + base); - if (disc_scb != SCB_LIST_NULL) + unsigned char scratch, bus_width; + + if (message_length != MSG_EXT_WDTR_LEN) { - outb(disc_scb, SCBPTR + base); - tag = inb(SCB_TAG + base); - outscb = (p->scb_array[tag]); - next = inb(SCB_NEXT + base); - if (next != SCB_LIST_NULL) - { - outb(next, SCBPTR + base); - outb(SCB_LIST_NULL, SCB_PREV + base); - outb(disc_scb, SCBPTR + base); - } - outb(next, DISCONNECTED_SCBH + base); - aic7xxx_page_scb(p, outscb, scb); - } - else if (inb(QINCNT + base) & p->qcntmask) + outb(SEND_REJ, p->base + RETURN_1); + break; + } + + bus_width = inb(p->base + MSGIN_EXT_BYTES); + scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + + if ((scb->flags & SCB_MSGOUT_WDTR) != 0) { - /* Pull one of our queued commands as a last resort. */ - disc_scb = inb(QINFIFO + base); - outb(disc_scb, SCBPTR + base); - tag = inb(SCB_TAG + base); - outscb = (p->scb_array[tag]); - if ((outscb->control & 0x23) != TAG_ENB) + /* + * Don't send an WDTR back to the target, since we asked first. + */ + outb(0, p->base + RETURN_1); + switch (bus_width) { - /* - * This is not a simple tagged command so its position - * in the queue matters. Take the command at the end of - * the queue instead. - */ - int i; - int saved_queue[AIC7XXX_MAXSCB]; - int queued = inb(QINCNT + base) & p->qcntmask; - - /* Count the command we removed already */ - saved_queue[0] = disc_scb; - queued++; - - /* Empty the input queue. */ - for (i = 1; i < queued; i++) - { - saved_queue[i] = inb(QINFIFO + base); - } - - /* Put everyone back but the last entry. */ - queued--; - for (i = 0; i < queued; i++) - { - outb(saved_queue[i], QINFIFO + base); - } - - outb(saved_queue[queued], SCBPTR + base); - tag = inb(SCB_TAG + base); - outscb = (p->scb_array[tag]); + case BUS_8_BIT: + scratch &= 0x7F; + break; + + case BUS_16_BIT: + if (aic7xxx_verbose) + { + printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 " + "bit transfers.\n", p->host_no, target, channel); + } + scratch |= WIDEXFER; + break; + + case BUS_32_BIT: + outb(SEND_REJ, p->base + RETURN_1); + /* No verbose here! We want to see this condition. */ + printk(KERN_WARNING "scsi%d: Target %d, channel %c, " + "requesting 32 bit transfers, rejecting...\n", + p->host_no, target, channel); + break; + + default: + break; } - scb->position = outscb->position; - outscb->position = SCB_LIST_NULL; - scbq_insert_head(&p->waiting_scbs, outscb); - outscb->state |= SCB_WAITINGQ; - aic7xxx_putscb(p, scb); - scb->state &= ~SCB_PAGED_OUT; } else { - printk(KERN_WARNING "scsi%d: Page-in request with no candidates " - "target %d, channel %c - Issuing ABORT. SAVED_TCL(0x%x).\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - aic7xxx_unbusy_target(scsi_id, channel, base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(0, RETURN_1 + base); + /* + * Send our own WDTR in reply. + */ + switch (bus_width) + { + case BUS_8_BIT: + scratch &= 0x7F; + break; + + case BUS_32_BIT: + case BUS_16_BIT: + if (p->bus_type == AIC_WIDE) + { + printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 " + "bit transfers.\n", p->host_no, target, channel); + bus_width = BUS_16_BIT; + scratch |= WIDEXFER; + } + else + { + bus_width = BUS_8_BIT; + scratch &= 0x7F; /* XXX - FreeBSD doesn't do this. */ + } + break; + + default: + break; + } + aic7xxx_construct_wdtr(p, /* start byte */ 0, bus_width); + outb(SEND_MSG, p->base + RETURN_1); } - } - } - else - { - printk(KERN_WARNING "scsi%d: No active SCB for reconnecting " - "target %d, channel %c - Issuing ABORT. SAVED_TCL(0x%x).\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - aic7xxx_unbusy_target(scsi_id, channel, base); - outb(0, SCB_CONTROL + base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(0, RETURN_1 + base); - } - break; - - case BAD_PHASE: - panic("scsi%d: Unknown scsi bus phase.\n", p->host_no); - break; - - case SEND_REJECT: - rej_byte = inb(REJBYTE + base); - if ((rej_byte & 0xF0) == 0x20) - { - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - printk(KERN_WARNING "scsi%d: Tagged message received without identify." - "Disabling tagged commands for target %d channel %c.\n", - p->host_no, scsi_id, channel); - scb->cmd->device->tagged_supported = 0; - scb->cmd->device->tagged_queue = 0; - } - else - { - printk(KERN_WARNING "scsi%d: Rejecting unknown message (0x%x) received " - "from target %d channel %c.\n", - p->host_no, rej_byte, scsi_id, channel); - } - break; + p->needwdtr &= ~target_mask; + outb(scratch, p->base + TARG_SCRATCH + scratch_offset); + outb(scratch, p->base + SCSIRATE); + break; + } /* case MSG_EXT_WDTR */ - case NO_IDENT: - panic("scsi%d: Target %d, channel %c, did not send an IDENTIFY " - "message. SAVED_TCL 0x%x.\n", - p->host_no, scsi_id, channel, inb(SAVED_TCL + base)); - break; + default: + /* + * Unknown extended message - reject it. + */ + outb(SEND_REJ, p->base + RETURN_1); + break; + } /* switch (message_code) */ + } /* case EXTENDED_MSG */ + break; - case SDTR_MSG: - /* - * Help the sequencer to translate the negotiated - * transfer rate. Transfer is 1/4 the period - * in ns as is returned by the sync negotiation - * message. So, we must multiply by four. - */ - transfer = (inb(ARG_1 + base) << 2); - offset = inb(ACCUM + base); - scratch = inb(TARG_SCRATCH + base + scratch_offset); + case REJECT_MSG: + { /* - * The maximum offset for a wide device is 0x08; for a - * 8-bit bus device the maximum offset is 0x0F. + * What we care about here is if we had an outstanding SDTR + * or WDTR message for this target. If we did, this is a + * signal that the target is refusing negotiation. */ - if (scratch & WIDEXFER) + unsigned char targ_scratch; + unsigned char scb_index; + + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; + targ_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + + if ((scb->flags & SCB_MSGOUT_WDTR) != 0) { - max_offset = 0x08; + /* + * note 8bit xfers and clear flag + */ + targ_scratch &= 0x7F; + p->needwdtr &= ~target_mask; + printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing WIDE " + "negotiation; using 8 bit transfers.\n", + p->host_no, target, channel); } else { - max_offset = 0x0F; - } - aic7xxx_scsirate(p, &rate, transfer, MIN(offset, max_offset), - scsi_id, channel); - /* - * Preserve the wide transfer flag. - */ - scratch = rate | (scratch & WIDEXFER); - outb(scratch, TARG_SCRATCH + base + scratch_offset); - outb(scratch, SCSIRATE + base); - if ((scratch & 0x0F) == 0) - { - /* - * One of two things happened. Either the device requested - * asynchronous data transfers, or it requested a synchronous - * data transfer rate that was so low that asynchronous - * transfers are faster (not to mention the controller won't - * support them). In both cases the synchronous data transfer - * rate and the offset are set to 0 indicating asynchronous - * transfers. - * - * If the device requested an asynchronous transfer, then - * accept the request. If the device is being forced to - * asynchronous data transfers and this is the first time - * we've seen the request, accept the request. If we've - * already seen the request, then attempt to force - * asynchronous data transfers by rejecting the message. - */ - if ((offset == 0) || (p->sdtr_pending & target_mask)) + if ((scb->flags & SCB_MSGOUT_SDTR) != 0) { /* - * Device requested asynchronous transfers or we're - * forcing asynchronous transfers for the first time. + * note asynch xfers and clear flag */ - outb(0, RETURN_1 + base); + targ_scratch &= 0xF0; + p->needsdtr &= ~target_mask; + printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing " + "synchronous negotiation; using asynchronous transfers.\n", + p->host_no, target, channel); } - else - { - /* - * The first time in forcing asynchronous transfers - * failed, so we try sending a reject message. - */ - outb(SEND_REJ, RETURN_1 + base); - } - } - else - { - /* - * See if we initiated Sync Negotiation - */ - if (p->sdtr_pending & target_mask) - { - /* - * Don't send an SDTR back to the target. - */ - outb(0, RETURN_1 + base); - } - else - { - /* - * Send our own SDTR in reply. - */ - printk("aic7xxx: Sending SDTR!!\n"); - outb(SEND_SDTR, RETURN_1 + base); - } - } - /* - * Clear the flags. - */ - p->needsdtr &= ~target_mask; - p->sdtr_pending &= ~target_mask; - break; - - case WDTR_MSG: - { - bus_width = inb(ARG_1 + base); - printk(KERN_INFO "scsi%d: Received MSG_WDTR, Target %d, channel %c " - "needwdtr(0x%x).\n", p->host_no, scsi_id, channel, p->needwdtr); - scratch = inb(TARG_SCRATCH + base + scratch_offset); - - if (p->wdtr_pending & target_mask) - { - /* - * Don't send an WDTR back to the target, since we asked first. - */ - outb(0, RETURN_1 + base); - switch (bus_width) - { - case BUS_8_BIT: - scratch &= 0x7F; - break; - - case BUS_16_BIT: - printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 bit " - "transfers.\n", p->host_no, scsi_id, channel); - scratch |= 0x80; - break; - - case BUS_32_BIT: - outb(SEND_REJ, RETURN_1 + base); - printk(KERN_INFO "scsi%d: Target %d, channel %c, requesting 32 bit " - "transfers, rejecting...\n", p->host_no, scsi_id, channel); - break; - } - } - else - { - /* - * Send our own WDTR in reply. - */ - printk(KERN_INFO "scsi%d: Will send WDTR!!\n", p->host_no); - switch (bus_width) - { - case BUS_8_BIT: - scratch &= 0x7F; - break; - - case BUS_32_BIT: - /* - * Negotiate 16 bits. - */ - bus_width = BUS_16_BIT; - /* Yes, we mean to fall thru here. */ - - case BUS_16_BIT: - printk(KERN_INFO "scsi%d: Target %d, channel %c, using 16 bit " - "transfers.\n", p->host_no, scsi_id, channel); - scratch |= 0x80; - break; - } - outb(bus_width | SEND_WDTR, RETURN_1 + base); + /* + * Otherwise, we ignore it. + */ } - p->needwdtr &= ~target_mask; - p->wdtr_pending &= ~target_mask; - outb(scratch, TARG_SCRATCH + base + scratch_offset); - outb(scratch, SCSIRATE + base); - break; + outb(targ_scratch, p->base + TARG_SCRATCH + scratch_offset); + outb(targ_scratch, p->base + SCSIRATE); } + break; - case REJECT_MSG: + case BAD_STATUS: { - /* - * What we care about here is if we had an - * outstanding SDTR or WDTR message for this - * target. If we did, this is a signal that - * the target is refusing negotiation. + unsigned char scb_index; + struct aic7xxx_hwscb *hscb; + Scsi_Cmnd *cmd; + + /* The sequencer will notify us when a command has an error that + * would be of interest to the kernel. This allows us to leave + * the sequencer running in the common case of command completes + * without error. The sequencer will have DMA'd the SCB back + * up to us, so we can reference the drivers SCB array. */ + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; + hscb = scb->hscb; - scratch = inb(TARG_SCRATCH + base + scratch_offset); - - if (p->wdtr_pending & target_mask) - { - /* - * note 8bit xfers and clear flag - */ - scratch &= 0x7F; - p->needwdtr &= ~target_mask; - p->wdtr_pending &= ~target_mask; - printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing WIDE " - "negotiation; using 8 bit transfers.\n", - p->host_no, scsi_id, channel); - } - else - { - if (p->sdtr_pending & target_mask) - { - /* - * note asynch xfers and clear flag - */ - scratch &= 0xF0; - p->needsdtr &= ~target_mask; - p->sdtr_pending &= ~target_mask; - printk(KERN_WARNING "scsi%d: Target %d, channel %c, refusing " - "synchronous negotiation; using asynchronous transfers.\n", - p->host_no, scsi_id, channel); - } - /* - * Otherwise, we ignore it. - */ - } - outb(scratch, TARG_SCRATCH + base + scratch_offset); - outb(scratch, SCSIRATE + base); - break; - } - - case BAD_STATUS: - /* The sequencer will notify us when a command has an error that - * would be of interest to the kernel. This allows us to leave - * the sequencerrunning in the common case of command completes - * without error. - */ - - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - outb(0, RETURN_1 + base); /* CHECK_CONDITION may change this */ - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + /* + * Set the default return value to 0 indicating not to send + * sense. The sense code will change this if needed and this + * reduces code duplication. + */ + outb(0, p->base + RETURN_1); + if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL)) { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx.\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); + printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " + "SEQINT 0x%x, scb %d, flags 0x%x, cmd 0x%x.\n", p->host_no, + intstat, scb_index, scb->flags, (unsigned int) scb->cmd); } else { - cmd = scb->cmd; - scb->target_status = inb(SCB_TARGET_STATUS + base); - aic7xxx_status(cmd) = scb->target_status; + cmd = scb->cmd; + hscb->target_status = inb(p->base + SCB_TARGET_STATUS); + aic7xxx_status(cmd) = hscb->target_status; - cmd->result |= scb->target_status; + cmd->result |= hscb->target_status; - switch (status_byte(scb->target_status)) - { - case GOOD: - printk(KERN_WARNING "aic7xxx: Interrupted for status of GOOD???\n"); - break; - - case CHECK_CONDITION: - if ((aic7xxx_error(cmd) == 0) && !(cmd->flags & WAS_SENSE)) - { - unsigned char tcl; - unsigned int req_buf; /* must be 32 bits */ + switch (status_byte(hscb->target_status)) + { + case GOOD: + printk(KERN_WARNING "(scsi%d:%d:%d) Interrupted for status of " + "GOOD???\n", p->host_no, TC_OF_SCB(scb)); + break; - tcl = scb->target_channel_lun; + case CHECK_CONDITION: + if ((aic7xxx_error(cmd) == 0) && !(scb->flags & SCB_SENSE)) + { + unsigned int addr; /* must be 32 bits */ + /* + * XXX - How do we save the residual (if there is one). + */ + aic7xxx_calculate_residual(p, scb); + + /* + * Send a sense command to the requesting target. + * XXX - revisit this and get rid of the memcopys. + */ + memcpy((void *) scb->sense_cmd, (void *) generic_sense, + sizeof(generic_sense)); + + scb->sense_cmd[1] = (cmd->lun << 5); + scb->sense_cmd[4] = sizeof(cmd->sense_buffer); + + scb->sg_list[0].address = VIRT_TO_BUS(&cmd->sense_buffer); + scb->sg_list[0].length = sizeof(cmd->sense_buffer); + cmd->cmd_len = COMMAND_SIZE(cmd->cmnd[0]); - /* - * Send a sense command to the requesting target. + /* + * XXX - We should allow disconnection, but can't as it + * might allow overlapped tagged commands. */ - cmd->flags |= WAS_SENSE; - memcpy((void *) scb->sense_cmd, (void *) generic_sense, - sizeof(generic_sense)); - - scb->sense_cmd[1] = (cmd->lun << 5); - scb->sense_cmd[4] = sizeof(cmd->sense_buffer); - - scb->sg_list[0].address = VIRT_TO_BUS(&cmd->sense_buffer); - scb->sg_list[0].length = sizeof(cmd->sense_buffer); - req_buf = VIRT_TO_BUS(&scb->sg_list[0]); - cmd->cmd_len = COMMAND_SIZE(cmd->cmnd[0]); - - scb->control = scb->control & DISCENB; - scb->target_channel_lun = tcl; - addr = VIRT_TO_BUS(scb->sense_cmd); - scb->SCSI_cmd_length = COMMAND_SIZE(scb->sense_cmd[0]); - memcpy(scb->SCSI_cmd_pointer, &addr, - sizeof(scb->SCSI_cmd_pointer)); - scb->SG_segment_count = 1; - memcpy(scb->SG_list_pointer, &req_buf, - sizeof(scb->SG_list_pointer)); - scb->data_count = scb->sg_list[0].length; - memcpy(scb->data_pointer, &(scb->sg_list[0].address), - sizeof(scb->data_pointer)); - - aic7xxx_putscb(p, scb); + /* hscb->control &= DISCENB; */ + hscb->control = 0; + hscb->target_status = 0; + hscb->SG_segment_count = 1; + + addr = VIRT_TO_BUS(&scb->sg_list[0]); + memcpy(&hscb->SG_list_pointer, &addr, + sizeof(hscb->SG_list_pointer)); + + memcpy(&hscb->data_pointer, &(scb->sg_list[0].address), + sizeof(hscb->data_pointer)); + /* Maintain SCB_LINKED_NEXT */ + hscb->data_count &= 0xFF000000; + hscb->data_count |= scb->sg_list[0].length; + + addr = VIRT_TO_BUS(scb->sense_cmd); + memcpy(&hscb->SCSI_cmd_pointer, &addr, + sizeof(hscb->SCSI_cmd_pointer)); + hscb->SCSI_cmd_length = COMMAND_SIZE(scb->sense_cmd[0]); + + scb->sg_count = hscb->SG_segment_count; + scb->flags |= SCB_SENSE; /* - * Ensure that the target is "BUSY" so we don't get overlapping - * commands if we happen to be doing tagged I/O. + * Ensure the target is busy since this will be an + * an untagged request. */ - aic7xxx_busy_target(scsi_id, channel, base); + aic7xxx_busy_target(p, target, channel, hscb->tag); + outb(SEND_SENSE, p->base + RETURN_1); + } /* first time sense, no errors */ + else + { + if (aic7xxx_error(cmd) == 0) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + } + break; - aic7xxx_add_waiting_scb(base, scb); - outb(SEND_SENSE, RETURN_1 + base); - } /* first time sense, no errors */ - else + case QUEUE_FULL: +#ifdef NOT_YET + if (scb->hscb->control & TAG_ENB) { - cmd->flags &= ~ASKED_FOR_SENSE; - if (aic7xxx_error(cmd) == 0) - { - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - } + if (cmd->device->queue_depth > 2) + { + cmd->device->queue_depth--; /* Not correct */ + printk(KERN_WARNING "(scsi%d:%d:%d) Tagged queue depth " + "reduced to %d\n", p->host_no, + TC_OF_SCB(scb), cmd->device->queue_depth); + } + /* + * XXX - Requeue this unconditionally? + */ + + /* + * We'd like to be able to give the SCB some more time + * (untimeout, then timeout). + */ + break; } - break; - - case BUSY: - printk(KERN_WARNING "scsi%d: Target busy, TCL=0x%x.\n", - p->host_no, scb->target_channel_lun); - if (!aic7xxx_error(cmd)) - { - /* The error code here used to be DID_BUS_BUSY, - * but after extensive testing, it has been determined - * that a DID_BUS_BUSY return is a waste of time. If - * the problem is something that will go away, then it - * will, if it isn't, then you don't want the endless - * looping that you get with a DID_BUS_BUSY. Better - * to be on the safe side and specify an error condition - * that will eventually lead to a reset or abort of some - * sort instead of an endless loop. - */ - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - } - break; - - case QUEUE_FULL: - printk(KERN_WARNING "scsi%d: Queue full.\n", p->host_no); - scb->state |= SCB_ASSIGNEDQ; - scbq_insert_tail(&p->assigned_scbs, scb); - break; - - default: - printk(KERN_WARNING "scsi%d: Unexpected target status 0x%x.\n", - p->host_no, scb->target_status); - if (!aic7xxx_error(cmd)) - { - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - } - break; - } /* end switch */ +#endif + printk(KERN_WARNING "(scsi%d:%d:%d) Queue full received; " + "queue depth %d, active %d\n", p->host_no, + TC_OF_SCB(scb), cmd->device->queue_depth, + p->device_status[TARGET_INDEX(cmd)].active_cmds); + + /* Else treat this as if it was a BUSY condition. */ + scb->hscb->target_status = (BUSY << 1) | + (scb->hscb->target_status & 0x01); + /* Fall through to the BUSY case. */ + + case BUSY: + printk(KERN_WARNING "(scsi%d:%d:%d) Target busy\n", + p->host_no, TC_OF_SCB(scb)); + if (!aic7xxx_error(cmd)) + { + /* + * The mid-level SCSI code should be fixed to + * retry the command at a later time instead of + * trying right away. + */ + aic7xxx_error(cmd) = DID_BUS_BUSY | (SUGGEST_RETRY << 8); + } + udelay(1000); /* A small pause (1ms) to help the drive */ + break; + + default: + printk(KERN_WARNING "(scsi%d:%d:%d) Unexpected target " + "status 0x%x.\n", p->host_no, + TC_OF_SCB(scb), scb->hscb->target_status); + if (!aic7xxx_error(cmd)) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + break; + } /* end switch */ } /* end else of */ - break; + } + break; - case RESIDUAL: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) - { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx.\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); - } - else - { - cmd = scb->cmd; - /* - * Don't destroy valid residual information with - * residual coming from a check sense operation. - */ - if (!(cmd->flags & WAS_SENSE)) - { - /* - * We had an underflow. At this time, there's only - * one other driver that bothers to check for this, - * and cmd->underflow seems to be set rather half- - * heartedly in the higher-level SCSI code. - */ - actual = aic7xxx_length(cmd, scb->residual_SG_segment_count); - - actual -= (inb(SCB_RESID_DCNT2 + base) << 16) | - (inb(SCB_RESID_DCNT1 + base) << 8) | - inb(SCB_RESID_DCNT0 + base); - - if (actual < cmd->underflow) - { - printk(KERN_WARNING "scsi%d: Target %d underflow - " - "Wanted at least %u, got %u, residual SG count %d.\n", - p->host_no, cmd->target, cmd->underflow, actual, - inb(SCB_RESID_SGCNT + base)); - aic7xxx_error(cmd) = DID_RETRY_COMMAND; - aic7xxx_status(cmd) = scb->target_status; - } - } - } - break; + case AWAITING_MSG: + { + unsigned char scb_index; + unsigned char message_offset; - case ABORT_TAG: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) - { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); - } - else - { - cmd = scb->cmd; - /* - * We didn't receive a valid tag back from the target - * on a reconnect. - */ - printk("scsi%d: Invalid tag received on target %d, channel %c, " - "lun %d - Sending ABORT_TAG.\n", p->host_no, - scsi_id, channel, cmd->lun & 0x07); - - cmd->result = (DID_RETRY_COMMAND << 16); - aic7xxx_done(p, scb); - } - break; + scb_index = inb(p->base + SCB_TAG); + scb = p->scb_data->scb_array[scb_index]; - case AWAITING_MSG: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + /* + * This SCB had a MK_MESSAGE set in its control byte informing + * the sequencer that we wanted to send a special message to + * this target. + */ + message_offset = inb(p->base + MSG_LEN); + if (scb->flags & SCB_DEVICE_RESET) { - printk(KERN_WARNING "scsi%d: Referenced SCB not valid during " - "SEQINT 0x%x, scb %d, state 0x%x, cmd 0x%lx.\n", p->host_no, - intstat, scb_index, scb->state, (unsigned long) scb->cmd); + outb(MSG_BUS_DEV_RESET, p->base + MSG_OUT); + outb(1, p->base + MSG_LEN); + printk(KERN_INFO "(scsi%d:%d:%d) Bus device reset sent\n", + p->host_no, TC_OF_SCB(scb)); } - else + else if (scb->flags & SCB_ABORT) + { + if ((scb->hscb->control & TAG_ENB) != 0) + { + outb(MSG_ABORT_TAG, p->base + MSG_OUT + message_offset); + } + else + { + outb(MSG_ABORT, p->base + MSG_OUT + message_offset); + } + outb(message_offset + 1, p->base + MSG_LEN); + printk(KERN_WARNING "(scsi%d:%d:%d): Abort message sent.\n", + p->host_no, TC_OF_SCB(scb)); + } + else if (scb->flags & SCB_MSGOUT_WDTR) { - /* - * This SCB had a zero length command, informing the sequencer - * that we wanted to send a special message to this target. - * We only do this for BUS_DEVICE_RESET messages currently. - */ - if (scb->state & SCB_DEVICE_RESET) - { -#ifdef AIC7XXX_DEBUG_ABORT - printk ("aic7xxx: (isr) sending bus device reset to target %d\n", - scsi_id); -#endif - outb(MSG_BUS_DEVICE_RESET, MSG0 + base); - outb(1, MSG_LEN + base); - } - else - { - panic("scsi%d: AWAITING_SCB for an SCB that does " - "not have a waiting message.\n", p->host_no); - } - } - break; - - case IMMEDDONE: - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: received IMMEDDONE for target %d, scb %d, state %d\n", - scsi_id, scb_index, scb->state); -#endif - if (scb->state & SCB_DEVICE_RESET) + aic7xxx_construct_wdtr(p, message_offset, BUS_16_BIT); + } + else if (scb->flags & SCB_MSGOUT_SDTR) { - int found; + unsigned char target_scratch; + unsigned short ultra_enable; + int i, sxfr; /* - * Go back to async/narrow transfers and renegotiate. + * Pull the user defined setting from scratch RAM. */ - aic7xxx_unbusy_target(scsi_id, channel, base); - p->needsdtr |= (p->needsdtr_copy & target_mask); - p->needwdtr |= (p->needwdtr_copy & target_mask); - p->sdtr_pending &= ~target_mask; - p->wdtr_pending &= ~target_mask; - scratch = inb(TARG_SCRATCH + base + scratch_offset); - scratch &= SXFR; - outb(scratch, TARG_SCRATCH + base + scratch_offset); - found = aic7xxx_reset_device(p, (int) scsi_id, channel); - printk(KERN_INFO "scsi%d: Bus Device Reset delivered, %d SCBs " - "aborted.\n", p->host_no, found); - /* Indicate that we want to call aic7xxx_done_aborted_scbs() */ - run_aborted_queue = TRUE; + target_scratch = inb(p->base + TARG_SCRATCH + scratch_offset); + sxfr = target_scratch & SXFR; + ultra_enable = inb(p->base + ULTRA_ENB) | + (inb(p->base + ULTRA_ENB + 1) << 8); + if (ultra_enable & target_mask) + { + sxfr |= 0x100; + } + for (i = 0; i < num_aic7xxx_syncrates; i++) + { + if (sxfr == aic7xxx_syncrates[i].rate) + break; + } + aic7xxx_construct_sdtr(p, message_offset, + aic7xxx_syncrates[i].period, + target_scratch & WIDEXFER ? + MAX_OFFSET_16BIT : MAX_OFFSET_8BIT); } - else + else { - panic("scsi%d: Immediate complete for unknown operation.\n", - p->host_no); - } - break; + panic("aic7xxx: AWAITING_MSG for an SCB that does " + "not have a waiting message."); + } + } + break; - case DATA_OVERRUN: + case DATA_OVERRUN: { - unsigned int overrun; - - scb = (p->scb_array[inb(base + SCB_TAG)]); - overrun = inb(base + STCNT0) | (inb(base + STCNT1) << 8) | - (inb(base + STCNT2) << 16); - overrun =0x00FFFFFF - overrun; - printk(KERN_WARNING "scsi%d: data overrun of %d bytes detected; forcing " - "a retry.\n", p->host_no, overrun); - aic7xxx_error(scb->cmd) = DID_RETRY_COMMAND; - break; + unsigned char scb_index = inb(p->base + SCB_TAG); + unsigned char lastphase = inb(p->base + LASTPHASE); + unsigned int i, overrun; + + scb = (p->scb_data->scb_array[scb_index]); + overrun = inb(p->base + STCNT) | (inb(p->base + STCNT + 1) << 8) | + (inb(p->base + STCNT + 2) << 16); + overrun = 0x00FFFFFF - overrun; + printk(KERN_WARNING "(scsi%d:%d:%d) Data overrun of %d bytes detected " + "in %s phase, tag %d; forcing a retry.\n", + p->host_no, TC_OF_SCB(scb), overrun, + lastphase == P_DATAIN ? "Data-In" : "Data-Out", + scb->hscb->tag); + printk(KERN_WARNING "%s seen Data Phase. Length = %d, NumSGs = %d.\n", + inb(p->base + SEQ_FLAGS) & DPHASE ? "Have" : "Haven't", + aic7xxx_length(scb->cmd, 0), scb->sg_count); + for (i = 0; i < scb->sg_count; i++) + { + printk(KERN_INFO " sg[%d] - Addr 0x%x : Length %d\n", + i, scb->sg_list[i].address, scb->sg_list[i].length); + } + /* + * XXX - What do we really want to do on an overrun? The + * mid-level SCSI code should handle this, but for now, + * we'll just indicate that the command should retried. + */ + aic7xxx_error(scb->cmd) = DID_RETRY_COMMAND; } + break; -#if AIC7XXX_NOT_YET - /* XXX Fill these in later */ - case MESG_BUFFER_BUSY: - break; - case MSGIN_PHASEMIS: - break; -#endif +/* #if AIC7XXX_NOT_YET */ + /* XXX Fill these in later */ + case MSG_BUFFER_BUSY: + printk("aic7xxx: Message buffer busy.\n"); + break; + case MSGIN_PHASEMIS: + printk("aic7xxx: Message-in phasemis.\n"); + break; +/*#endif */ + + case ABORT_CMDCMPLT: + /* This interrupt serves to pause the sequencer until we can clean + * up the QOUTFIFO allowing us to handle any abort SCBs that may + * completed yet still have an SCB in the QINFIFO or waiting for + * selection queue. By the time we get here, we should have + * already cleaned up the queues, so all we need to do is unpause + * the sequencer. + */ + break; + + default: /* unknown */ + printk(KERN_WARNING "scsi%d: SEQINT, INTSTAT 0x%x, SCSISIGI 0x%x.\n", + p->host_no, intstat, inb(p->base + SCSISIGI)); + break; + } + + /* + * Clear the sequencer interrupt and unpause the sequencer. + */ + outb(CLRSEQINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); +} - default: /* unknown */ - printk(KERN_WARNING "scsi%d: SEQINT, INTSTAT 0x%x, SCSISIGI 0x%x.\n", - p->host_no, intstat, inb(SCSISIGI + base)); - break; +/*+F************************************************************************* + * Function: + * aic7xxx_handle_scsiint + * + * Description: + * Interrupt handler for SCSI interrupts (SCSIINT). + *-F*************************************************************************/ +static void +aic7xxx_handle_scsiint(struct aic7xxx_host *p, unsigned char intstat) +{ + unsigned char scb_index; + unsigned char status; + struct aic7xxx_scb *scb; + + scb_index = inb(p->base + SCB_TAG); + status = inb(p->base + SSTAT1); + + if (scb_index < p->scb_data->numscbs) + { + scb = p->scb_data->scb_array[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) + { + scb = NULL; } + } + else + { + scb = NULL; + } + + if ((status & SCSIRSTI) != 0) + { + char channel; + channel = (inb(p->base + SBLKCTL) & SELBUSB) ? 'B' : 'A'; + + printk(KERN_WARNING "scsi%d: SCSIINT - Someone reset channel %c.\n", + p->host_no, channel); /* - * Clear the sequencer interrupt and unpause the sequencer. + * Go through and abort all commands for the channel, but do not + * reset the channel again. */ - outb(CLRSEQINT, CLRINT + base); - UNPAUSE_SEQUENCER(p); + aic7xxx_reset_channel(p, channel, /* Initiate Reset */ FALSE); + scb = NULL; } - - if (intstat & SCSIINT) + else if ( ((status & BUSFREE) != 0) && ((status & SELTO) == 0) ) { - int status = inb(SSTAT1 + base); - scsi_id = (inb(SCSIID + base) >> 4) & 0x0F; - channel = 'A'; - if (inb(SBLKCTL + base) & SELBUSB) - { - channel = 'B'; - } + /* + * First look at what phase we were last in. If it's message-out, + * chances are pretty good that the bus free was in response to + * one of our abort requests. + */ + unsigned char lastphase = inb(p->base + LASTPHASE); + unsigned char target = (inb(p->base + SAVED_TCL) >> 4) & 0x0F; + char channel = (inb(p->base + SBLKCTL) & SELBUSB) ? 'B' : 'A'; + int printerror = TRUE; - scb_index = inb(SCB_TAG + base); - scb = (p->scb_array[scb_index]); - if (status & SCSIRSTI) - { - PAUSE_SEQUENCER(p); - printk(KERN_WARNING "scsi%d: SCSIINT - Someone reset channel %c.\n", - p->host_no, channel); - /* - * Go through and abort all commands for the channel, but do not - * reset the channel again. - */ - aic7xxx_reset_channel(p, channel, FALSE); - run_aborted_queue = TRUE; - } - else if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + outb(0, p->base + SCSISEQ); + if (lastphase == P_MESGOUT) { - printk(KERN_WARNING "scsi%d: SCSIINT - No command for SCB.\n", p->host_no); - /* - * Turn off the interrupt and set status to zero, so that it - * falls through the rest of the SCSIINT code. - */ - outb(status, CLRSINT1 + base); - UNPAUSE_SEQUENCER(p); - outb(CLRSCSIINT, CLRINT + base); - scb = NULL; + unsigned char sindex; + unsigned char message; + + sindex = inb(p->base + SINDEX); + message = inb(p->base + sindex - 1); + + if (message == MSG_ABORT) + { + printk(KERN_WARNING "(scsi%d:%d:%d) SCB %d abort completed.\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag); + aic7xxx_reset_device(p, target, channel, SCB_LUN(scb), SCB_LIST_NULL); + aic7xxx_run_done_queue(p, /* complete */ TRUE); + scb = NULL; + printerror = 0; + } + else if (message == MSG_ABORT_TAG) + { + printk(KERN_WARNING "(scsi%d:%d:%d) SCB %d abort Tag completed.\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag); + aic7xxx_reset_device(p, target, channel, SCB_LUN(scb), scb->hscb->tag); + aic7xxx_run_done_queue(p, /* complete */ TRUE); + scb = NULL; + printerror = 0; + } + else if (message == MSG_BUS_DEV_RESET) + { + aic7xxx_handle_device_reset(p, target, channel); + scb = NULL; + printerror = 0; + } } - else if (status & SCSIPERR) + if (printerror != 0) { - char *phase; - unsigned char mesg_out = MSG_NOP; - unsigned char lastphase = inb(LASTPHASE + base); + if (scb != NULL) + { + unsigned char tag; - cmd = scb->cmd; - switch (lastphase) + if ((scb->hscb->control & TAG_ENB) != 0) + { + tag = scb->hscb->tag; + } + else + { + tag = SCB_LIST_NULL; + } + aic7xxx_reset_device(p, target, channel, SCB_LUN(scb), tag); + } + else { - case P_DATAOUT: - phase = "Data-Out"; - break; - case P_DATAIN: - phase = "Data-In"; - mesg_out = MSG_INITIATOR_DET_ERROR; - break; - case P_COMMAND: - phase = "Command"; - break; - case P_MESGOUT: - phase = "Message-Out"; - break; - case P_STATUS: - phase = "Status"; - mesg_out = MSG_INITIATOR_DET_ERROR; - break; - case P_MESGIN: - phase = "Message-In"; - mesg_out = MSG_MSG_PARITY_ERROR; - break; - default: - phase = "unknown"; - break; + aic7xxx_reset_device(p, target, channel, ALL_LUNS, SCB_LIST_NULL); } + printk(KERN_WARNING "scsi%d: Unexpected busfree, LASTPHASE = 0x%x, " + "SEQADDR = 0x%x\n", p->host_no, lastphase, + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + } + outb(inb(p->base + SIMODE1) & ~ENBUSFREE, p->base + SIMODE1); + outb(CLRBUSFREE, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + restart_sequencer(p); + } + else if ((status & SELTO) != 0) + { + unsigned char scbptr; + unsigned char nextscb; + Scsi_Cmnd *cmd; - /* - * A parity error has occurred during a data - * transfer phase. Flag it and continue. - */ - printk(KERN_WARNING "scsi%d: Parity error during phase %s on target %d, " - "channel %d, lun %d.\n", p->host_no, phase, - cmd->target, cmd->channel & 0x01, cmd->lun & 0x07); + scbptr = inb(p->base + WAITING_SCBH); + outb(scbptr, p->base + SCBPTR); + scb_index = inb(p->base + SCB_TAG); - /* - * We've set the hardware to assert ATN if we get a parity - * error on "in" phases, so all we need to do is stuff the - * message buffer with the appropriate message. In phases - * have set mesg_out to something other than MSG_NOP. - */ - if (mesg_out != MSG_NOP) - { - outb(mesg_out, MSG0 + base); - outb(1, MSG_LEN + base); - cmd->result = DID_PARITY << 16; - } - else + scb = NULL; + if (scb_index < p->scb_data->numscbs) + { + scb = p->scb_data->scb_array[scb_index]; + if ((scb->flags & SCB_ACTIVE) == 0) { - /* - * Should we allow the target to make this decision for us? - */ - cmd->result = DID_RETRY_COMMAND << 16; + scb = NULL; } - aic7xxx_done(p, scb); } - else if (status & SELTO) + if (scb == NULL) { - unsigned char waiting; - + printk(KERN_WARNING "scsi%d: Referenced SCB %d not valid during SELTO.\n", + p->host_no, scb_index); + printk(KERN_WARNING " SCSISEQ = 0x%x SEQADDR = 0x%x SSTAT0 = 0x%x " + "SSTAT1 = 0x%x\n", inb(p->base + SCSISEQ), + inb(p->base + SEQADDR0) | (inb(p->base + SEQADDR1) << 8), + inb(p->base + SSTAT0), inb(p->base + SSTAT1)); + } + else + { + /* + * XXX - If we queued an abort tag, go clean up the disconnected list. + */ cmd = scb->cmd; - cmd->result = (DID_TIME_OUT << 16); + /* * Clear an pending messages for the timed out * target and mark the target as free. */ - ha_flags = inb(FLAGS + base); - outb(0, MSG_LEN + base); - aic7xxx_unbusy_target(scsi_id, channel, base); + outb(0, p->base + MSG_LEN); + aic7xxx_index_busy_target(p, cmd->target, + cmd->channel ? 'B': 'A', /*unbusy*/ TRUE); + outb(0, p->base + SCB_CONTROL); + /* - * Stop the selection. + * Shift the waiting for selection queue forward */ - outb(0, SCSISEQ + base); - outb(0, SCB_CONTROL + base); - outb(CLRSELTIMEO, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); + nextscb = inb(p->base + SCB_NEXT); + outb(nextscb, p->base + WAITING_SCBH); /* - * Shift the waiting for selection queue forward + * Put this SCB back on the free list. */ - waiting = inb(WAITING_SCBH + base); - outb(waiting, SCBPTR + base); - waiting = inb(SCB_NEXT + base); - outb(waiting, WAITING_SCBH + base); + aic7xxx_add_curscb_to_free_list(p); + } + /* + * Stop the selection. + */ + outb(0, p->base + SCSISEQ); + outb(CLRSELTIMEO | CLRBUSFREE, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + restart_sequencer(p); + } + else if (scb == NULL) + { + printk(KERN_WARNING "scsi%d: aic7xxx_isr - referenced scb not valid " + "during scsiint 0x%x scb(%d)\n" + " SIMODE0 0x%x, SIMODE1 0x%x, SSTAT0 0x%x, SEQADDR 0x%x\n", + p->host_no, status, scb_index, inb(p->base + SIMODE0), + inb(p->base + SIMODE1), inb(p->base + SSTAT0), + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + /* + * Turn off the interrupt and set status to zero, so that it + * falls through the rest of the SCSIINT code. + */ + outb(status, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); + scb = NULL; + } + else if (status & SCSIPERR) + { + /* + * Determine the bus phase and queue an appropriate message. + */ + char *phase; + Scsi_Cmnd *cmd; + unsigned char mesg_out = MSG_NOOP; + unsigned char lastphase = inb(p->base + LASTPHASE); - RESTART_SEQUENCER(p); - aic7xxx_done(p, scb); + cmd = scb->cmd; + switch (lastphase) + { + case P_DATAOUT: + phase = "Data-Out"; + break; + case P_DATAIN: + phase = "Data-In"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_COMMAND: + phase = "Command"; + break; + case P_MESGOUT: + phase = "Message-Out"; + break; + case P_STATUS: + phase = "Status"; + mesg_out = MSG_INITIATOR_DET_ERR; + break; + case P_MESGIN: + phase = "Message-In"; + mesg_out = MSG_PARITY_ERROR; + break; + default: + phase = "unknown"; + break; + } + + /* + * A parity error has occurred during a data + * transfer phase. Flag it and continue. + */ + printk(KERN_WARNING "(scsi%d:%d:%d) Parity error during phase %s.\n", + p->host_no, TC_OF_SCB(scb), phase); + + /* + * We've set the hardware to assert ATN if we get a parity + * error on "in" phases, so all we need to do is stuff the + * message buffer with the appropriate message. "In" phases + * have set mesg_out to something other than MSG_NOP. + */ + if (mesg_out != MSG_NOOP) + { + outb(mesg_out, p->base + MSG_OUT); + outb(1, p->base + MSG_LEN); + scb = NULL; } - else if (!(status & BUSFREE)) + else { /* - * We don't know what's going on. Turn off the - * interrupt source and try to continue. + * Should we allow the target to make this decision for us? */ - printk(KERN_WARNING "aic7xxx: SSTAT1(0x%x).\n", status); - outb(status, CLRSINT1 + base); - UNPAUSE_SEQUENCER(p); - outb(CLRSCSIINT, CLRINT + base); + cmd->result = DID_RETRY_COMMAND << 16; + } + outb(CLRSCSIPERR, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause_always */ TRUE); + } + else + { + /* + * We don't know what's going on. Turn off the + * interrupt source and try to continue. + */ + printk(KERN_WARNING "aic7xxx: SSTAT1(0x%x).\n", status); + outb(status, p->base + CLRSINT1); + outb(CLRSCSIINT, p->base + CLRINT); + unpause_sequencer(p, /* unpause always */ TRUE); + scb = NULL; + } + if (scb != NULL) + { + aic7xxx_done(p, scb); + aic7xxx_done_cmds_complete(p); + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_isr + * + * Description: + * SCSI controller interrupt handler. + * + * NOTE: Since we declared this using SA_INTERRUPT, interrupts should + * be disabled all through this function unless we say otherwise. + *-F*************************************************************************/ +static void +aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct aic7xxx_host *p; + unsigned char intstat; + unsigned long flags; + + p = (struct aic7xxx_host *) aic7xxx_boards[irq]->hostdata; + + /* + * Search for the host with a pending interrupt. If we can't find + * one, then we've encountered a spurious interrupt. + */ + while ((p != NULL) && !(inb(p->base + INTSTAT) & INT_PEND)) + { + if (p->next == NULL) + { + p = NULL; + } + else + { + p = (struct aic7xxx_host *) p->next->hostdata; + } + } + + if (p == NULL) + return; + + /* + * Handle all the interrupt sources - especially for SCSI + * interrupts, we won't get a second chance at them. + */ + intstat = inb(p->base + INTSTAT); + + /* + * Keep track of interrupts for /proc/scsi + */ + p->isr_count++; + + if (!(p->flags & A_SCANNED) && (p->isr_count == 1)) + { + /* + * We must only have one card at this IRQ and it must have been + * added to the board data before the spurious interrupt occurred. + * It is sufficient that we check isr_count and not the spurious + * interrupt count. + */ + printk("scsi%d: Encountered spurious interrupt.\n", p->host_no); + if (intstat) + { + /* Try clearing all interrupts. */ + outb(CLRBRKADRINT | CLRSCSIINT | CLRCMDINT | CLRSEQINT, p->base + CLRINT); } + return; } - if (run_aborted_queue) - aic7xxx_done_aborted_scbs(p); + if (p->flags & IN_ISR) + { + printk(KERN_WARNING "scsi%d: Warning!! Interrupt routine called reentrantly!\n", + p->host_no); + return; + } + + /* + * Indicate that we're in the interrupt handler. + */ + save_flags(flags); + cli(); + p->flags |= IN_ISR; if (intstat & CMDCMPLT) { - int complete; + struct aic7xxx_scb *scb = NULL; + Scsi_Cmnd *cmd; + unsigned char qoutcnt; + unsigned char scb_index; + int i, interrupts_cleared = 0; /* * The sequencer will continue running when it * issues this interrupt. There may be >1 commands * finished, so loop until we've processed them all. */ - do { - complete = inb(QOUTFIFO + base); + qoutcnt = inb(p->base + QOUTCNT) & p->qcntmask; - scb = (p->scb_array[complete]); - if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) - { - printk(KERN_WARNING "scsi%d: CMDCMPLT without command for SCB %d.\n" - " QOUTCNT %d, QINCNT %d, SCB state 0x%x, cmd 0x%lx, " - "pos(%d).\n", p->host_no, complete, inb(QOUTCNT + base), - inb(QINCNT + base), scb->state, (unsigned long) scb->cmd, - scb->position); - outb(CLRCMDINT, CLRINT + base); - continue; - } - cmd = scb->cmd; - cmd->result |= (aic7xxx_error(cmd) << 16); - if ((cmd->flags & WAS_SENSE) && !(cmd->flags & ASKED_FOR_SENSE)) +#if 1 + if (qoutcnt >= p->qfullcount - 1) + printk(KERN_WARNING "aic7xxx: Command complete near Qfull count, " + "qoutcnt = %d.\n", qoutcnt); +#endif + while (qoutcnt > 0) + { + for (i = 0; i < qoutcnt; i++) { - /* - * Got sense information. - */ - cmd->flags &= ASKED_FOR_SENSE; + scb_index = inb(p->base + QOUTFIFO); + scb = p->scb_data->scb_array[scb_index]; + if (scb == NULL) + { + printk(KERN_WARNING "scsi%d: CMDCMPLT with invalid SCB index %d, " + "QOUTCNT %d, QINCNT %d\n", p->host_no, scb_index, + inb(p->base + QOUTCNT), inb(p->base + QINCNT)); + continue; + } + else if (!(scb->flags & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk(KERN_WARNING "scsi%d: CMDCMPLT without command for SCB %d, " + "QOUTCNT %d, QINCNT %d, SCB flags 0x%x, cmd 0x%lx\n", + p->host_no, scb_index, inb(p->base + QOUTCNT), + inb(p->base + QINCNT), scb->flags, (unsigned long) scb->cmd); + continue; + } + cmd = scb->cmd; + if (scb->hscb->residual_SG_segment_count != 0) + { + aic7xxx_calculate_residual(p, scb); + } + if ((scb->flags & SCB_QUEUED_ABORT) != 0) + { + /* + * Have to clean up any possible entries in the + * waiting queue and the QINFIFO. + */ + int target; + char channel; + int lun; + unsigned char tag; + + tag = SCB_LIST_NULL; + target = cmd->target; + lun = cmd->lun; + channel = (scb->hscb->target_channel_lun & SELBUSB) ? 'B' : 'A'; + if (scb->hscb->control & TAG_ENB) + { + tag = scb->hscb->tag; + } + aic7xxx_reset_device(p, target, channel, lun, tag); + /* + * Run the done queue, but don't complete the commands; we + * do this once at the end of the loop. + */ + aic7xxx_run_done_queue(p, /*complete*/ FALSE); + } + cmd->result |= (aic7xxx_error(cmd) << 16); + p->device_status[TARGET_INDEX(cmd)].flags |= DEVICE_SUCCESS; + aic7xxx_done(p, scb); } - p->device_status[TARGET_INDEX(cmd)].flags |= DEVICE_SUCCESS; - /* * Clear interrupt status before checking the output queue again. * This eliminates a race condition whereby a command could @@ -3243,56 +3941,152 @@ aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) * so notification of the command being complete never made it * back up to the kernel. */ - outb(CLRCMDINT, CLRINT + base); - aic7xxx_done(p, scb); + outb(CLRCMDINT, p->base + CLRINT); + interrupts_cleared++; + qoutcnt = inb(p->base + QOUTCNT) & p->qcntmask; + } -#ifdef AIC7XXX_PROC_STATS - /* - * XXX: we should actually know how much actually transferred - * XXX: for each command, but apparently that's too difficult. - */ - actual = aic7xxx_length(cmd, 0); - if (!(cmd->flags & WAS_SENSE) && (actual > 0)) + if (interrupts_cleared == 0) + { + outb(CLRCMDINT, p->base + CLRINT); + } + + aic7xxx_done_cmds_complete(p); + } + + if (intstat & BRKADRINT) + { + int i; + unsigned char errno = inb(p->base + ERROR); + + printk(KERN_ERR "scsi%d: BRKADRINT error(0x%x):\n", p->host_no, errno); + for (i = 0; i < NUMBER(hard_error); i++) + { + if (errno & hard_error[i].errno) { - struct aic7xxx_xferstats *sp; - long *ptr; - int x; + printk(KERN_ERR " %s\n", hard_error[i].errmesg); + } + } + printk("scsi%d: BRKADRINT, error 0x%x, seqaddr 0x%x.\n", p->host_no, + inb(p->base + ERROR), + (inb(p->base + SEQADDR1) << 8) | inb(p->base + SEQADDR0)); + aic7xxx_reset_device(p, ALL_TARGETS, ALL_CHANNELS, ALL_LUNS, SCB_LIST_NULL); + aic7xxx_run_done_queue(p, /*complete*/ TRUE); + } + + if (intstat & SEQINT) + { + aic7xxx_handle_seqint(p, intstat); + } - sp = &p->stats[cmd->channel & 0x01][cmd->target & 0x0F][cmd->lun & 0x07]; - sp->xfers++; + if (intstat & SCSIINT) + { + aic7xxx_handle_scsiint(p, intstat); + } - if (cmd->request.cmd == WRITE) + if (p->waiting_scbs.head != NULL) + { + aic7xxx_run_waiting_queues(p); + } + + p->flags &= ~IN_ISR; + restore_flags(flags); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_device_queue_depth + * + * Description: + * Determines the queue depth for a given device. There are two ways + * a queue depth can be obtained for a tagged queueing device. One + * way is the default queue depth which is determined by whether + * AIC7XXX_CMDS_PER_LUN is defined. If it is defined, then it is used + * as the default queue depth. Otherwise, we use either 4 or 8 as the + * default queue depth (dependent on the number of hardware SCBs). + * The other way we determine queue depth is through the use of the + * aic7xxx_tag_info array which is enabled by defining + * AIC7XXX_TAGGED_QUEUEING_BY_DEVICE. This array can be initialized + * with queue depths for individual devices. It also allows tagged + * queueing to be [en|dis]abled for a specific adapter. + *-F*************************************************************************/ +static void +aic7xxx_device_queue_depth(struct aic7xxx_host *p, Scsi_Device *device) +{ + int default_depth = 2; + + device->queue_depth = default_depth; +#ifdef AIC7XXX_TAGGED_QUEUEING + if (device->tagged_supported) + { + unsigned short target_mask; + int tag_enabled = TRUE; + + target_mask = (1 << (device->id | (device->channel << 3))); + +#ifdef AIC7XXX_CMDS_PER_LUN + default_depth = AIC7XXX_CMDS_PER_LUN; +#else + if (p->scb_data->maxhscbs <= 4) + { + default_depth = 4; /* Not many SCBs to work with. */ + } + else + { + default_depth = 8; + } +#endif + + if (!(p->discenable & target_mask)) + { + printk(KERN_INFO "(scsi%d:%d:%d) Disconnection disabled, unable to " + "enable tagged queueing.\n", + p->host_no, device->id, device->channel); + } + else + { +#ifndef AIC7XXX_TAGGED_QUEUEING_BY_DEVICE + device->queue_depth = default_depth; +#else + if (p->instance > NUMBER(aic7xxx_tag_info)) + { + device->queue_depth = default_depth; + } + else + { + unsigned char tindex; + + tindex = device->id | (device->channel << 3); + if (aic7xxx_tag_info[p->instance].tag_commands[tindex] < 0) { - sp->w_total++; - sp->w_total512 += (actual >> 9); - ptr = sp->w_bins; + tag_enabled = FALSE; + device->queue_depth = 2; /* Tagged queueing is disabled. */ } - else + else if (aic7xxx_tag_info[p->instance].tag_commands[tindex] == 0) { - sp->r_total++; - sp->r_total512 += (actual >> 9); - ptr = sp->r_bins; + device->queue_depth = default_depth; } - for (x = 9; x <= 17; x++) + else { - if (actual < (1 << x)) - { - ptr[x - 9]++; - break; - } + device->queue_depth = + aic7xxx_tag_info[p->instance].tag_commands[tindex]; } - if (x > 17) + } +#endif + if ((device->tagged_queue == 0) && tag_enabled) + { + if (aic7xxx_verbose) { - ptr[x - 9]++; + printk(KERN_INFO "(scsi%d:%d:%d) Enabled tagged queuing, " + "queue depth %d.\n", p->host_no, + device->id, device->channel, device->queue_depth); } + device->tagged_queue = 1; + device->current_tag = SCB_LIST_NULL; } -#endif /* AIC7XXX_PROC_STATS */ - - } while (inb(QOUTCNT + base) & p->qcntmask); + } } - aic7xxx_done_cmds_complete(p); - p->flags &= ~IN_ISR; - aic7xxx_run_waiting_queues(p); +#endif } /*+F************************************************************************* @@ -3307,59 +4101,18 @@ aic7xxx_isr(int irq, void *dev_id, struct pt_regs *regs) * algorithm for determining the queue depth based on the maximum * SCBs for the controller. *-F*************************************************************************/ -static void aic7xxx_select_queue_depth(struct Scsi_Host *host, +static void +aic7xxx_select_queue_depth(struct Scsi_Host *host, Scsi_Device *scsi_devs) { - Scsi_Device *device = scsi_devs; - int tq_depth = 2; + Scsi_Device *device; struct aic7xxx_host *p = (struct aic7xxx_host *) host->hostdata; -#ifdef AIC7XXX_CMDS_PER_LUN - tq_depth = AIC7XXX_CMDS_PER_LUN; -#else - { - if (p->maxhscbs <= 4) - { - tq_depth = 4; /* Not many SCBs to work with. */ - } - else - { - tq_depth = 8; - } - } -#endif - for (device = scsi_devs; device != NULL; device = device->next) { if (device->host == host) { - device->queue_depth = 2; -#ifdef AIC7XXX_TAGGED_QUEUEING - if (device->tagged_supported) - { - unsigned short target_mask = (1 << device->id) | device->channel; - - if (!(p->discenable & target_mask)) - { - printk(KERN_INFO "scsi%d: Disconnection disabled, unable to enable " - "tagged queueing for target %d, channel %d, LUN %d.\n", - host->host_no, device->id, device->channel, device->lun); - } - else - { - device->queue_depth = tq_depth; - if (device->tagged_queue == 0) - { - printk(KERN_INFO "scsi%d: Enabled tagged queuing for target %d, " - "channel %d, LUN %d, queue depth %d.\n", host->host_no, - device->id, device->channel, device->lun, - device->queue_depth); - device->tagged_queue = 1; - device->current_tag = SCB_LIST_NULL; - } - } - } -#endif + aic7xxx_device_queue_depth(p, device); } } } @@ -3386,7 +4139,7 @@ static void aic7xxx_select_queue_depth(struct Scsi_Host *host, * The fourth byte's lowest bit seems to be an enabled/disabled * flag (rest of the bits are reserved?). *-F*************************************************************************/ -static aha_type +static aha_chip_type aic7xxx_probe(int slot, int base, aha_status_type *bios) { int i; @@ -3395,7 +4148,7 @@ aic7xxx_probe(int slot, int base, aha_status_type *bios) static struct { int n; unsigned char signature[sizeof(buf)]; - aha_type type; + aha_chip_type type; int bios_disabled; } AIC7xxx[] = { { 4, { 0x04, 0x90, 0x77, 0x71 }, AIC_7771, FALSE }, /* host adapter 274x */ @@ -3434,7 +4187,8 @@ aic7xxx_probe(int slot, int base, aha_status_type *bios) return (AIC7xxx[i].type); } - printk("aic7xxx: Disabled at slot %d, ignored.\n", slot); + printk("aic7xxx: <Adaptec 7770 SCSI Host Adapter> " + "disabled at slot %d, ignored.\n", slot); } } @@ -3461,10 +4215,9 @@ aic7xxx_probe(int slot, int base, aha_status_type *bios) * useful in that it gives us an 800 nsec timer. After a read from the * SEECTL_2840 register the timing flag is cleared and goes high 800 nsec * later. - * *-F*************************************************************************/ static int -read_2840_seeprom(int base, struct seeprom_config *sc) +read_284x_seeprom(struct aic7xxx_host *p, struct seeprom_config *sc) { int i = 0, k = 0; unsigned char temp; @@ -3477,11 +4230,11 @@ read_2840_seeprom(int base, struct seeprom_config *sc) struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; #define CLOCK_PULSE(p) \ - while ((inb(STATUS_2840 + base) & EEPROM_TF) == 0) \ + while ((inb(p->base + STATUS_2840) & EEPROM_TF) == 0) \ { \ ; /* Do nothing */ \ } \ - (void) inb(SEECTL_2840 + base); + (void) inb(p->base + SEECTL_2840); /* * Read the first 32 registers of the seeprom. For the 2840, @@ -3494,8 +4247,8 @@ read_2840_seeprom(int base, struct seeprom_config *sc) /* * Send chip select for one clock cycle. */ - outb(CK_2840 | CS_2840, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(CK_2840 | CS_2840, p->base + SEECTL_2840); + CLOCK_PULSE(p); /* * Now we're ready to send the read command followed by the @@ -3504,11 +4257,11 @@ read_2840_seeprom(int base, struct seeprom_config *sc) for (i = 0; i < seeprom_read.len; i++) { temp = CS_2840 | seeprom_read.bits[i]; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); temp = temp ^ CK_2840; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); } /* * Send the 6 bit address (MSB first, LSB last). @@ -3518,11 +4271,11 @@ read_2840_seeprom(int base, struct seeprom_config *sc) temp = k; temp = (temp >> i) & 1; /* Mask out all but lower bit. */ temp = CS_2840 | temp; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); temp = temp ^ CK_2840; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); } /* @@ -3534,12 +4287,12 @@ read_2840_seeprom(int base, struct seeprom_config *sc) for (i = 0; i <= 16; i++) { temp = CS_2840; - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); temp = temp ^ CK_2840; - seeprom[k] = (seeprom[k] << 1) | (inb(STATUS_2840 + base) & DI_2840); - outb(temp, SEECTL_2840 + base); - CLOCK_PULSE(base); + seeprom[k] = (seeprom[k] << 1) | (inb(p->base + STATUS_2840) & DI_2840); + outb(temp, p->base + SEECTL_2840); + CLOCK_PULSE(p); } /* * The serial EEPROM has a checksum in the last word. Keep a @@ -3555,12 +4308,12 @@ read_2840_seeprom(int base, struct seeprom_config *sc) /* * Reset the chip select for the next command cycle. */ - outb(0, SEECTL_2840 + base); - CLOCK_PULSE(base); - outb(CK_2840, SEECTL_2840 + base); - CLOCK_PULSE(base); - outb(0, SEECTL_2840 + base); - CLOCK_PULSE(base); + outb(0, p->base + SEECTL_2840); + CLOCK_PULSE(p); + outb(CK_2840, p->base + SEECTL_2840); + CLOCK_PULSE(p); + outb(0, p->base + SEECTL_2840); + CLOCK_PULSE(p); } #if 0 @@ -3589,6 +4342,53 @@ read_2840_seeprom(int base, struct seeprom_config *sc) /*+F************************************************************************* * Function: + * acquire_seeprom + * + * Description: + * Acquires access to the memory port on PCI controllers. + *-F*************************************************************************/ +static inline int +acquire_seeprom(struct aic7xxx_host *p) +{ + int wait; + + /* + * Request access of the memory port. When access is + * granted, SEERDY will go high. We use a 1 second + * timeout which should be near 1 second more than + * is needed. Reason: after the 7870 chip reset, there + * should be no contention. + */ + outb(SEEMS, p->base + SEECTL); + wait = 1000; /* 1000 msec = 1 second */ + while ((wait > 0) && ((inb(p->base + SEECTL) & SEERDY) == 0)) + { + wait--; + udelay(1000); /* 1 msec */ + } + if ((inb(p->base + SEECTL) & SEERDY) == 0) + { + outb(0, p->base + SEECTL); + return (0); + } + return (1); +} + +/*+F************************************************************************* + * Function: + * release_seeprom + * + * Description: + * Releases access to the memory port on PCI controllers. + *-F*************************************************************************/ +static inline void +release_seeprom(struct aic7xxx_host *p) +{ + outb(0, p->base + SEECTL); +} + +/*+F************************************************************************* + * Function: * read_seeprom * * Description: @@ -3626,7 +4426,7 @@ read_2840_seeprom(int base, struct seeprom_config *sc) * first). The clock cycling from low to high initiates the next data * bit to be sent from the chip. * - * The 7870 interface to the 93C46 serial EEPROM is through the SEECTL + * The 78xx interface to the 93C46 serial EEPROM is through the SEECTL * register. After successful arbitration for the memory port, the * SEECS bit of the SEECTL register is connected to the chip select. * The SEECK, SEEDO, and SEEDI are connected to the clock, data out, @@ -3636,17 +4436,14 @@ read_2840_seeprom(int base, struct seeprom_config *sc) * to this is when we first request access to the memory port. The * SEERDY goes high to signify that access has been granted and, for * this case, has no implied timing. - * *-F*************************************************************************/ static int -read_seeprom(int base, int offset, struct seeprom_config *sc, - seeprom_chip_type chip) +read_seeprom(struct aic7xxx_host *p, int offset, unsigned short *scarray, + unsigned int len, seeprom_chip_type chip) { int i = 0, k; - unsigned long timeout; unsigned char temp; unsigned short checksum = 0; - unsigned short *seeprom = (unsigned short *) sc; struct seeprom_cmd { unsigned char len; unsigned char bits[3]; @@ -3654,43 +4451,33 @@ read_seeprom(int base, int offset, struct seeprom_config *sc, struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; #define CLOCK_PULSE(p) \ - while ((inb(SEECTL + base) & SEERDY) == 0) \ + while ((inb(p->base + SEECTL) & SEERDY) == 0) \ { \ ; /* Do nothing */ \ } /* - * Request access of the memory port. When access is - * granted, SEERDY will go high. We use a 1 second - * timeout which should be near 1 second more than - * is needed. Reason: after the 7870 chip reset, there - * should be no contention. + * Request access of the memory port. */ - outb(SEEMS, SEECTL + base); - timeout = jiffies + 100; /* 1 second timeout */ - while ((jiffies < timeout) && ((inb(SEECTL + base) & SEERDY) == 0)) - { - ; /* Do nothing! Wait for access to be granted. */ - } - if ((inb(SEECTL + base) & SEERDY) == 0) + if (acquire_seeprom(p) == 0) { - outb(0, SEECTL + base); return (0); } /* - * Read the first 32 registers of the seeprom. For the 7870, - * the 93C46 SEEPROM is a 1024-bit device with 64 16-bit registers - * but only the first 32 are used by Adaptec BIOS. The loop - * will range from 0 to 31. + * Read 'len' registers of the seeprom. For the 7870, the 93C46 + * SEEPROM is a 1024-bit device with 64 16-bit registers but only + * the first 32 are used by Adaptec BIOS. Some adapters use the + * 93C56 SEEPROM which is a 2048-bit device. The loop will range + * from 0 to 'len' - 1. */ - for (k = 0; k < (sizeof(*sc) / 2); k++) + for (k = 0; k < len; k++) { /* * Send chip select for one clock cycle. */ - outb(SEEMS | SEECK | SEECS, SEECTL + base); - CLOCK_PULSE(base); + outb(SEEMS | SEECK | SEECS, p->base + SEECTL); + CLOCK_PULSE(p); /* * Now we're ready to send the read command followed by the @@ -3699,25 +4486,25 @@ read_seeprom(int base, int offset, struct seeprom_config *sc, for (i = 0; i < seeprom_read.len; i++) { temp = SEEMS | SEECS | (seeprom_read.bits[i] << 1); - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); temp = temp ^ SEECK; - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); } /* - * Send the 6 bit address (MSB first, LSB last). + * Send the 6 or 8 bit address (MSB first, LSB last). */ for (i = ((int) chip - 1); i >= 0; i--) { temp = k + offset; temp = (temp >> i) & 1; /* Mask out all but lower bit. */ temp = SEEMS | SEECS | (temp << 1); - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); temp = temp ^ SEECK; - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); } /* @@ -3729,56 +4516,57 @@ read_seeprom(int base, int offset, struct seeprom_config *sc, for (i = 0; i <= 16; i++) { temp = SEEMS | SEECS; - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); temp = temp ^ SEECK; - seeprom[k] = (seeprom[k] << 1) | (inb(SEECTL + base) & SEEDI); - outb(temp, SEECTL + base); - CLOCK_PULSE(base); + scarray[k] = (scarray[k] << 1) | (inb(p->base + SEECTL) & SEEDI); + outb(temp, p->base + SEECTL); + CLOCK_PULSE(p); } /* - * The serial EEPROM has a checksum in the last word. Keep a - * running checksum for all words read except for the last - * word. We'll verify the checksum after all words have been - * read. + * The serial EEPROM should have a checksum in the last word. + * Keep a running checksum for all words read except for the + * last word. We'll verify the checksum after all words have + * been read. */ - if (k < (sizeof(*sc) / 2) - 1) + if (k < (len - 1)) { - checksum = checksum + seeprom[k]; + checksum = checksum + scarray[k]; } /* * Reset the chip select for the next command cycle. */ - outb(SEEMS, SEECTL + base); - CLOCK_PULSE(base); - outb(SEEMS | SEECK, SEECTL + base); - CLOCK_PULSE(base); - outb(SEEMS, SEECTL + base); - CLOCK_PULSE(base); + outb(SEEMS, p->base + SEECTL); + CLOCK_PULSE(p); + outb(SEEMS | SEECK, p->base + SEECTL); + CLOCK_PULSE(p); + outb(SEEMS, p->base + SEECTL); + CLOCK_PULSE(p); } /* * Release access to the memory port and the serial EEPROM. */ - outb(0, SEECTL + base); + release_seeprom(p); #if 0 - printk("Computed checksum 0x%x, checksum read 0x%x\n", checksum, sc->checksum); + printk("Computed checksum 0x%x, checksum read 0x%x\n", + checksum, scarray[len - 1]); printk("Serial EEPROM:"); - for (k = 0; k < (sizeof(*sc) / 2); k++) + for (k = 0; k < len; k++) { if (((k % 8) == 0) && (k != 0)) { printk("\n "); } - printk(" 0x%x", seeprom[k]); + printk(" 0x%x", scarray[k]); } printk("\n"); #endif - if (checksum != sc->checksum) + if (checksum != scarray[len - 1]) { return (0); } @@ -3789,563 +4577,452 @@ read_seeprom(int base, int offset, struct seeprom_config *sc, /*+F************************************************************************* * Function: - * detect_maxscb + * write_brdctl * * Description: - * Detects the maximum number of SCBs for the controller and returns - * the count and a mask in config (config->maxscbs, config->qcntmask). + * Writes a value to the BRDCTL register. *-F*************************************************************************/ -static void -detect_maxscb(struct aic7xxx_host_config *config) +static inline void +write_brdctl(struct aic7xxx_host *p, unsigned char value) { - unsigned char sblkctl_reg; - int base, i; - -#ifdef AIC7XXX_PAGE_ENABLE - config->flags |= PAGE_ENABLED; -#endif - base = config->base; - switch (config->type) - { - case AIC_7770: - case AIC_7771: - case AIC_284x: - /* - * Check for Rev C or E boards. Rev E boards can supposedly have - * more than 4 SCBs, while the Rev C boards are limited to 4 SCBs. - * It's still not clear extactly what is different about the Rev E - * boards, but we think it allows 8 bit entries in the QOUTFIFO to - * support "paging" SCBs (more than 4 commands can be active at once). - * - * The Rev E boards have a read/write autoflush bit in the - * SBLKCTL register, while in the Rev C boards it is read only. - */ - sblkctl_reg = inb(SBLKCTL + base) ^ AUTOFLUSHDIS; - outb(sblkctl_reg, SBLKCTL + base); - if (inb(SBLKCTL + base) == sblkctl_reg) - { - /* - * We detected a Rev E board, we allow paging on this board. - */ - printk(KERN_INFO "aic7xxx: %s Rev E and subsequent.\n", - board_names[config->type]); - outb(sblkctl_reg ^ AUTOFLUSHDIS, SBLKCTL + base); - } - else - { - /* Do not allow paging. */ - config->flags &= ~PAGE_ENABLED; - printk(KERN_INFO "aic7xxx: %s Rev C and previous.\n", - board_names[config->type]); - } - break; - - default: - break; - } - - /* - * Walk the SCBs to determine how many there are. - */ - i = 1; - outb(0, SCBPTR + base); - outb(0, SCBARRAY + base); - - while (i < AIC7XXX_MAXSCB) - { - outb(i, SCBPTR + base); - outb(i, SCBARRAY + base); - if (inb(SCBARRAY + base) != i) - break; - outb(0, SCBPTR + base); - if (inb(SCBARRAY + base) != 0) - break; - - outb(i, SCBPTR + base); /* Clear the control byte. */ - outb(0, SCBARRAY + base); - - config->qcntmask |= i; /* Update the count mask. */ - i++; - } - outb(i, SCBPTR + base); /* Ensure we clear the control bytes. */ - outb(0, SCBARRAY + base); - outb(0, SCBPTR + base); - outb(0, SCBARRAY + base); - - config->maxhscbs = i; - config->qcntmask |= i; - if ((config->flags & PAGE_ENABLED) && (config->maxhscbs < AIC7XXX_MAXSCB)) - { - config->maxscbs = AIC7XXX_MAXSCB; - } - else - { - config->flags &= ~PAGE_ENABLED; /* Disable paging if we have 255 SCBs!. */ - config->maxscbs = config->maxhscbs; - } - - printk(KERN_INFO "aic7xxx: Memory check yields %d SCBs", config->maxhscbs); - if (config->flags & PAGE_ENABLED) - printk(", %d page-enabled SCBs.\n", config->maxscbs); - else - printk(", paging not enabled.\n"); - + unsigned char brdctl; + + brdctl = BRDCS | BRDSTB; + outb(brdctl, p->base + BRDCTL); + brdctl |= value; + outb(brdctl, p->base + BRDCTL); + brdctl &= ~BRDSTB; + outb(brdctl, p->base + BRDCTL); + brdctl &= ~BRDCS; + outb(brdctl, p->base + BRDCTL); } /*+F************************************************************************* * Function: - * aic7xxx_register + * read_brdctl * * Description: - * Register a Adaptec aic7xxx chip SCSI controller with the kernel. + * Reads the BRDCTL register. *-F*************************************************************************/ -static int -aic7xxx_register(Scsi_Host_Template *template, - struct aic7xxx_host_config *config) +static inline unsigned char +read_brdctl(struct aic7xxx_host *p) { - int i; - unsigned char sblkctl, flags = 0; - int max_targets; - int found = 1; - unsigned int sram, base; - unsigned char target_settings; - unsigned char scsi_conf, host_conf; - unsigned short ultraenable = 0; - int have_seeprom = FALSE; - struct Scsi_Host *host; - struct aic7xxx_host *p; - struct seeprom_config sc; - - base = config->base; - - /* - * Lock out other contenders for our i/o space. - */ - request_region(base, MAXREG - MINREG, "aic7xxx"); + outb(BRDRW | BRDCS, p->base + BRDCTL); + return (inb(p->base + BRDCTL)); +} - switch (config->type) +/*+F************************************************************************* + * Function: + * configure_termination + * + * Description: + * Configures the termination settings on PCI adapters that have + * SEEPROMs available. + *-F*************************************************************************/ +static void +configure_termination(struct aic7xxx_host *p, unsigned char *sxfrctl1, + unsigned short adapter_control, unsigned char max_targ) +{ + unsigned char brdctl_int, brdctl_ext; + int internal50_present; + int internal68_present = 0; + int external_present = 0; + int eprom_present; + int high_on; + int low_on; + int old_verbose; + + if (acquire_seeprom(p)) { - case AIC_7770: - case AIC_7771: - /* - * Use the boot-time option for the interrupt trigger type. If not - * supplied (-1), then we use BIOS settings to determine the interrupt - * trigger type (level or edge) and use this value for pausing and - * unpausing the sequencer. - */ - switch (aic7xxx_irq_trigger) - { - case 0: config->unpause = INTEN; /* Edge */ - break; - case 1: config->unpause = IRQMS | INTEN; /* Level */ - break; - case -1: - default: config->unpause = (inb(HCNTRL + base) & IRQMS) | INTEN; - break; - } - config->pause = config->unpause | PAUSE; + if (adapter_control & CFAUTOTERM) + { + old_verbose = aic7xxx_verbose; + printk(KERN_INFO "aic7xxx: Warning - detected auto-termination. Please " + "verify driver"); + printk(KERN_INFO " detected settings and use manual termination " + "if necessary."); + + /* Configure auto termination. */ + outb(SEECS | SEEMS, p->base + SEECTL); /* - * For some 274x boards, we must clear the CHIPRST bit and pause - * the sequencer. For some reason, this makes the driver work. - * For 284x boards, we give it a CHIPRST just like the 294x boards. + * First read the status of our cables. Set the rom bank to + * 0 since the bank setting serves as a multiplexor for the + * cable detection logic. BRDDAT5 controls the bank switch. */ - outb(config->pause | CHIPRST, HCNTRL + base); - aic7xxx_delay(1); - if (inb(HCNTRL + base) & CHIPRST) - { - printk(KERN_INFO "aic7xxx: Chip reset not cleared; clearing manually.\n"); - } - outb(config->pause, HCNTRL + base); + write_brdctl(p, 0); /* - * Just to be on the safe side with the 274x, we will re-read the irq - * since there was some issue about resetting the board. + * Now read the state of the internal connectors. The + * bits BRDDAT6 and BRDDAT7 are 0 when cables are present + * set when cables are not present (BRDDAT6 is INT50 and + * BRDDAT7 is INT68). */ - config->irq = inb(INTDEF + base) & 0x0F; - if ((config->type == AIC_7771) && - (inb(HA_274_BIOSCTRL + base) & BIOSMODE) == BIOSDISABLED) + brdctl_int = read_brdctl(p); + internal50_present = (brdctl_int & BRDDAT6) ? 0 : 1; + if (max_targ > 8) { - config->bios = AIC_DISABLED; - config->flags |= USE_DEFAULTS; - } - else - { - host_conf = inb(HOSTCONF + base); - config->bus_speed = host_conf & DFTHRSH; - config->busrtime = (host_conf << 2) & BOFF; + internal68_present = (brdctl_int & BRDDAT7) ? 0 : 1; } /* - * Setup the FIFO threshold and the bus off time + * Set the rom bank to 1 and determine + * the other signals. */ - outb(config->bus_speed & DFTHRSH, BUSSPD + base); - outb(config->busrtime, BUSTIME + base); + write_brdctl(p, BRDDAT5); /* - * A reminder until this can be detected automatically. + * Now read the state of the external connectors. BRDDAT6 is + * 0 when an external cable is present, and BRDDAT7 (EPROMPS) is + * set when the eprom is present. */ - printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", - (config->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); - break; - - case AIC_284x: - outb(CHIPRST, HCNTRL + base); - config->unpause = UNPAUSE_284X; - config->pause = REQ_PAUSE; /* DWG would like to be like the rest */ - aic7xxx_delay(1); - outb(config->pause, HCNTRL + base); - - config->parity = AIC_ENABLED; - config->irq = inb(INTDEF + base) & 0x0F; - host_conf = inb(HOSTCONF + base); - - printk(KERN_INFO "aic7xxx: Reading SEEPROM..."); - have_seeprom = read_2840_seeprom(base, &sc); - if (!have_seeprom) + brdctl_ext = read_brdctl(p); + external_present = (brdctl_ext & BRDDAT6) ? 0 : 1; + eprom_present = brdctl_ext & BRDDAT7; + if (aic7xxx_verbose) { - printk("aic7xxx: Unable to read SEEPROM.\n"); - } - else - { - printk("done.\n"); - config->flags |= HAVE_SEEPROM; - if (sc.bios_control & CF284XEXTEND) - config->flags |= EXTENDED_TRANSLATION; - if (!(sc.bios_control & CFBIOSEN)) + if (max_targ > 8) { - /* - * The BIOS is disabled; the values left over in scratch - * RAM are still valid. Do not use defaults as in the - * AIC-7770 case. - */ - config->bios = AIC_DISABLED; + printk(KERN_INFO "aic7xxx: Cables present (Int-50 %s, Int-68 %s, " + "Ext-68 %s)\n", + internal50_present ? "YES" : "NO", + internal68_present ? "YES" : "NO", + external_present ? "YES" : "NO"); } else { - config->parity = (sc.adapter_control & CFSPARITY) ? - AIC_ENABLED : AIC_DISABLED; - config->low_term = (sc.adapter_control & CF284XSTERM) ? - AIC_ENABLED : AIC_DISABLED; - /* - * XXX - Adaptec *does* make 284x wide controllers, but the - * documents do not say where the high byte termination - * enable bit is located. - */ + printk(KERN_INFO "aic7xxx: Cables present (Int-50 %s, Ext-50 %s)\n", + internal50_present ? "YES" : "NO", + external_present ? "YES" : "NO"); } + printk(KERN_INFO "aic7xxx: eprom %s present, brdctl_int=0x%x, " + "brdctl_ext=0x%x\n", + eprom_present ? "is" : "not", brdctl_int, brdctl_ext); } - host_conf = inb(HOSTCONF + base); - config->bus_speed = host_conf & DFTHRSH; - config->busrtime = (host_conf << 2) & BOFF; - - /* - * Setup the FIFO threshold and the bus off time - */ - outb(config->bus_speed & DFTHRSH, BUSSPD + base); - outb(config->busrtime, BUSTIME + base); - - printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", - (config->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); - break; - - case AIC_7860: - case AIC_7861: - case AIC_7880: - case AIC_7881: - case AIC_7882: - case AIC_7883: - case AIC_7884: - /* - * Remember if Ultra was enabled in case there is no SEEPROM. - * Fall through to the rest of the AIC_78xx code. - */ - if ((inb(SXFRCTL0 + base) & ULTRAEN) || aic7xxx_enable_ultra) - config->flags |= ULTRA_ENABLED; - - case AIC_7850: - case AIC_7855: - case AIC_7870: - case AIC_7871: - case AIC_7872: - case AIC_7873: - case AIC_7874: /* - * Grab the SCSI ID before chip reset in case there is no SEEPROM. + * Now set the termination based on what we found. BRDDAT6 + * controls wide termination enable. */ - config->scsi_id = inb(SCSIID + base) & OID; - outb(CHIPRST, HCNTRL + base); - config->unpause = UNPAUSE_294X; - config->pause = config->unpause | PAUSE; - aic7xxx_delay(1); - outb(config->pause, HCNTRL + base); - - config->parity = AIC_ENABLED; + high_on = FALSE; + low_on = FALSE; + if ((max_targ > 8) && + ((external_present == 0) || (internal68_present == 0))) + { + high_on = TRUE; + } - printk(KERN_INFO "aic7xxx: Reading SEEPROM..."); - if ((config->type == AIC_7873) || (config->type == AIC_7883)) + if ((internal50_present + internal68_present + external_present) <= 1) { - have_seeprom = read_seeprom(base, config->chan_num * (sizeof(sc) / 2), - &sc, c56_66); + low_on = TRUE; } - else + + if (internal50_present && internal68_present && external_present) { - have_seeprom = read_seeprom(base, config->chan_num * (sizeof(sc) / 2), - &sc, c46); + printk(KERN_WARNING "aic7xxx: Illegal cable configuration!!\n" + " Only two connectors on the adapter may be " + "used at a time!\n"); } - if (!have_seeprom) + + if (high_on == TRUE) + write_brdctl(p, BRDDAT6); + else + write_brdctl(p, 0); + + if (low_on == TRUE) + *sxfrctl1 |= STPWEN; + + if (aic7xxx_verbose) { - for (sram = base + TARG_SCRATCH; sram < base + 0x60; sram++) + if (max_targ > 8) { - if (inb(sram) != 0x00) - break; - } - if (sram == base + TARG_SCRATCH) - { - for (sram = base + TARG_SCRATCH; sram < base + 0x60; sram++) - { - if (inb(sram) != 0xFF) - break; - } - } - if ((sram != base + 0x60) && (config->scsi_id != 0)) - { - config->flags &= ~USE_DEFAULTS; - printk("\naic7xxx: Unable to read SEEPROM; " - "using leftover BIOS values.\n"); + printk(KERN_INFO "aic7xxx: Termination (Low %s, High %s)\n", + low_on ? "ON" : "OFF", + high_on ? "ON" : "OFF"); } else { - printk("\n"); - printk(KERN_INFO "aic7xxx: Unable to read SEEPROM; using default " - "settings.\n"); - config->flags |= USE_DEFAULTS; - config->flags &= ~ULTRA_ENABLED; - config->scsi_id = 7; + printk(KERN_INFO "aic7xxx: Termination %s\n", low_on ? "ON" : "OFF"); } - scsi_conf = ENSPCHK | RESET_SCSI; + } + aic7xxx_verbose = old_verbose; + } + else + { + if (adapter_control & CFSTERM) + { + *sxfrctl1 |= STPWEN; + } + outb(SEEMS | SEECS, p->base + SEECTL); + /* + * Configure high byte termination. + */ + if (adapter_control & CFWSTERM) + { + write_brdctl(p, BRDDAT6); } else { - printk("done.\n"); - config->flags |= HAVE_SEEPROM; - if (!(sc.bios_control & CFBIOSEN)) - { - /* - * The BIOS is disabled; the values left over in scratch - * RAM are still valid. Do not use defaults as in the - * AIC-7770 case. - */ - config->bios = AIC_DISABLED; - scsi_conf = ENSPCHK | RESET_SCSI; - } - else - { - scsi_conf = 0; - if (sc.adapter_control & CFRESETB) - scsi_conf |= RESET_SCSI; - if (sc.adapter_control & CFSPARITY) - scsi_conf |= ENSPCHK; - if (sc.bios_control & CFEXTEND) - config->flags |= EXTENDED_TRANSLATION; - config->scsi_id = (sc.brtime_id & CFSCSIID); - config->parity = (sc.adapter_control & CFSPARITY) ? - AIC_ENABLED : AIC_DISABLED; - config->low_term = (sc.adapter_control & CFSTERM) ? - AIC_ENABLED : AIC_DISABLED; - config->high_term = (sc.adapter_control & CFWSTERM) ? - AIC_ENABLED : AIC_DISABLED; - config->busrtime = ((sc.brtime_id & CFBRTIME) >> 8); - if (((config->type == AIC_7880) || (config->type == AIC_7881) || - (config->type == AIC_7882) || (config->type == AIC_7883) || - (config->type == AIC_7884)) && (sc.adapter_control & CFULTRAEN)) - { - printk(KERN_INFO "aic7xxx: Enabling support for Ultra SCSI " - "speed.\n"); - config->flags |= ULTRA_ENABLED; - } - } + write_brdctl(p, 0); } + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Termination (Low %s, High %s)\n", + (adapter_control & CFSTERM) ? "ON" : "OFF", + (adapter_control & CFWSTERM) ? "ON" : "OFF"); + } + } + release_seeprom(p); + } +} - outb(scsi_conf | (config->scsi_id & 0x07), SCSICONF + base); - config->bus_speed = DFTHRSH_100; - outb(config->bus_speed, DSPCISTATUS + base); +/*+F************************************************************************* + * Function: + * detect_maxscb + * + * Description: + * Detects the maximum number of SCBs for the controller and returns + * the count and a mask in p (p->maxscbs, p->qcntmask). + *-F*************************************************************************/ +static void +detect_maxscb(struct aic7xxx_host *p) +{ + int i; + unsigned char max_scbid = 255; - /* - * In case we are a wide card... - */ - outb(config->scsi_id, SCSICONF + base + 1); + /* + * It's possible that we've already done this for multichannel + * adapters. + */ + if (p->scb_data->maxhscbs == 0) + { + /* + * We haven't initialized the SCB settings yet. Walk the SCBs to + * determince how many there are. + */ + outb(0, p->base + FREE_SCBH); - printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", - (config->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); - break; + for (i = 0; i < AIC7XXX_MAXSCB; i++) + { + outb(i, p->base + SCBPTR); + outb(i, p->base + SCB_CONTROL); + if (inb(p->base + SCB_CONTROL) != i) + break; + outb(0, p->base + SCBPTR); + if (inb(p->base + SCB_CONTROL) != 0) + break; - default: - panic(KERN_WARNING "aic7xxx: (aic7xxx_register) Internal error.\n"); + outb(i, p->base + SCBPTR); + outb(0, p->base + SCB_CONTROL); /* Clear the control byte. */ + outb(i + 1, p->base + SCB_NEXT); /* Set the next pointer. */ + outb(SCB_LIST_NULL, p->base + SCB_TAG); /* Make the tag invalid. */ + + /* Make the non-tagged targets not busy. */ + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS); + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS + 1); + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS + 2); + outb(SCB_LIST_NULL, p->base + SCB_BUSYTARGETS + 3); + } + + /* Make sure the last SCB terminates the free list. */ + outb(i - 1, p->base + SCBPTR); + outb(SCB_LIST_NULL, p->base + SCB_NEXT); + + /* Ensure we clear the first (0) SCBs control byte. */ + outb(0, p->base + SCBPTR); + outb(0, p->base + SCB_CONTROL); + + p->scb_data->maxhscbs = i; } - detect_maxscb(config); + if ((p->flags & PAGE_ENABLED) && (p->scb_data->maxhscbs < AIC7XXX_MAXSCB)) + { + /* Determine the number of valid bits in the FIFOs. */ + outb(max_scbid, p->base + QINFIFO); + max_scbid = inb(p->base + QINFIFO); + p->scb_data->maxscbs = MIN(AIC7XXX_MAXSCB, max_scbid + 1); + } + else + { + p->scb_data->maxscbs = p->scb_data->maxhscbs; + } + if (p->scb_data->maxscbs == p->scb_data->maxhscbs) + { + /* + * Disable paging if the QINFIFO doesn't allow more SCBs than + * we have in hardware. + */ + p->flags &= ~PAGE_ENABLED; + } - if (config->chip_type == AIC_777x) + /* + * Set the Queue Full Count. Some cards have more queue space than + * SCBs. + */ + switch (p->chip_class) { - if (config->pause & IRQMS) - { - printk(KERN_INFO "aic7xxx: Using level sensitive interrupts.\n"); - } - else - { - printk(KERN_INFO "aic7xxx: Using edge triggered interrupts.\n"); - } + case AIC_777x: + p->qfullcount = 4; + p->qcntmask = 0x07; + break; + case AIC_785x: + case AIC_786x: + p->qfullcount = 8; + p->qcntmask = 0x0f; + break; + case AIC_787x: + case AIC_788x: + if (p->scb_data->maxhscbs == AIC7XXX_MAXSCB) + { + p->qfullcount = AIC7XXX_MAXSCB; + p->qcntmask = 0xFF; + } + else + { + p->qfullcount = 16; + p->qcntmask = 0x1F; + } + break; } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_register + * + * Description: + * Register a Adaptec aic7xxx chip SCSI controller with the kernel. + *-F*************************************************************************/ +static int +aic7xxx_register(Scsi_Host_Template *template, struct aic7xxx_host *p) +{ + int i; + unsigned char sblkctl, flags = 0; + int max_targets; + int found = 1; + char channel_ids[] = {'A', 'B', 'C'}; + unsigned char target_settings; + unsigned char scsi_conf, sxfrctl1; + unsigned short ultraenable = 0; + struct Scsi_Host *host; + + /* + * Lock out other contenders for our i/o space. + */ + request_region(p->base, MAXREG - MINREG, "aic7xxx"); /* * Read the bus type from the SBLKCTL register. Set the FLAGS * register in the sequencer for twin and wide bus cards. */ - sblkctl = inb(SBLKCTL + base); - if (config->flags & PAGE_ENABLED) + sblkctl = inb(p->base + SBLKCTL); + if (p->flags & PAGE_ENABLED) flags = PAGESCBS; switch (sblkctl & SELBUS_MASK) { case SELNARROW: /* narrow/normal bus */ - config->scsi_id = inb(SCSICONF + base) & 0x07; - config->bus_type = AIC_SINGLE; - outb(flags | SINGLE_BUS, FLAGS + base); + p->scsi_id = inb(p->base + SCSICONF) & 0x07; + p->bus_type = AIC_SINGLE; + p->flags &= ~FLAGS_CHANNEL_B_PRIMARY; + if (p->flags & MULTI_CHANNEL) + { + printk(KERN_INFO "aic7xxx: Channel %c, SCSI ID %d, ", + channel_ids[p->chan_num], p->scsi_id); + } + else + { + printk (KERN_INFO "aic7xxx: Single Channel, SCSI ID %d, ", + p->scsi_id); + } + outb(flags | SINGLE_BUS, p->base + SEQ_FLAGS); break; case SELWIDE: /* Wide bus */ - config->scsi_id = inb(SCSICONF + base + 1) & 0x0F; - config->bus_type = AIC_WIDE; - printk("aic7xxx: Enabling wide channel of %s-Wide.\n", - board_names[config->type]); - outb(flags | WIDE_BUS, FLAGS + base); + p->scsi_id = inb(p->base + SCSICONF + 1) & HWSCSIID; + p->bus_type = AIC_WIDE; + p->flags &= ~FLAGS_CHANNEL_B_PRIMARY; + if (p->flags & MULTI_CHANNEL) + { + printk(KERN_INFO "aic7xxx: Wide Channel %c, SCSI ID %d, ", + channel_ids[p->chan_num], p->scsi_id); + } + else + { + printk (KERN_INFO "aic7xxx: Wide Channel, SCSI ID %d, ", + p->scsi_id); + } + outb(flags | WIDE_BUS, p->base + SEQ_FLAGS); break; case SELBUSB: /* Twin bus */ - config->scsi_id = inb(SCSICONF + base) & 0x07; -#ifdef AIC7XXX_TWIN_SUPPORT - config->scsi_id_b = inb(SCSICONF + base + 1) & 0x07; - config->bus_type = AIC_TWIN; - printk(KERN_INFO "aic7xxx: Enabled channel B of %s-Twin.\n", - board_names[config->type]); - outb(flags | TWIN_BUS, FLAGS + base); -#else - config->bus_type = AIC_SINGLE; - printk(KERN_INFO "aic7xxx: Channel B of %s-Twin will be ignored.\n", - board_names[config->type]); - outb(flags, FLAGS + base); -#endif + p->scsi_id = inb(p->base + SCSICONF) & HSCSIID; + p->scsi_id_b = inb(p->base + SCSICONF + 1) & HSCSIID; + p->bus_type = AIC_TWIN; + printk(KERN_INFO "aic7xxx: Twin Channel, A SCSI ID %d, B SCSI ID %d, ", + p->scsi_id, p->scsi_id_b); + outb(flags | TWIN_BUS, p->base + SEQ_FLAGS); break; default: printk(KERN_WARNING "aic7xxx: Unsupported type 0x%x, please " - "mail deang@teleport.com\n", inb(SBLKCTL + base)); - outb(0, FLAGS + base); + "mail deang@teleport.com\n", inb(p->base + SBLKCTL)); + outb(0, p->base + SEQ_FLAGS); return (0); } /* - * For the 294x cards, clearing DIAGLEDEN and DIAGLEDON, will - * take the card out of diagnostic mode and make the host adapter - * LED follow bus activity (will not always be on). + * Detect SCB parameters and initialize the SCB array. */ - outb(sblkctl & ~(DIAGLEDEN | DIAGLEDON), SBLKCTL + base); + detect_maxscb(p); + printk("%d/%d SCBs, QFull %d, QMask 0x%x\n", + p->scb_data->maxhscbs, p->scb_data->maxscbs, + p->qfullcount, p->qcntmask); - /* - * The IRQ level in i/o port 4 maps directly onto the real - * IRQ number. If it's ok, register it with the kernel. - * - * NB. the Adaptec documentation says the IRQ number is only - * in the lower four bits; the ECU information shows the - * high bit being used as well. Which is correct? - * - * The PCI cards get their interrupt from PCI BIOS. - */ - if ((config->chip_type == AIC_777x) && ((config->irq < 9) || (config->irq > 15))) - { - printk(KERN_WARNING "aic7xxx: Host adapter uses unsupported IRQ level, " - "ignoring.\n"); - return (0); - } + host = p->host; - /* - * Print out debugging information before re-enabling - * the card - a lot of registers on it can't be read - * when the sequencer is active. - */ - debug_config(config); - - /* - * Register each "host" and fill in the returned Scsi_Host - * structure as best we can. Some of the parameters aren't - * really relevant for bus types beyond ISA, and none of the - * high-level SCSI code looks at it anyway. Why are the fields - * there? Also save the pointer so that we can find the - * information when an IRQ is triggered. - */ - host = scsi_register(template, sizeof(struct aic7xxx_host)); - host->can_queue = config->maxscbs; + host->can_queue = p->scb_data->maxscbs; host->cmd_per_lun = 2; + host->sg_tablesize = AIC7XXX_MAX_SG; host->select_queue_depths = aic7xxx_select_queue_depth; - host->this_id = config->scsi_id; - host->io_port = config->base; + host->this_id = p->scsi_id; + host->io_port = p->base; host->n_io_port = 0xFF; - host->base = (unsigned char *)config->mbase; - host->irq = config->irq; - if (config->bus_type == AIC_WIDE) + host->base = (unsigned char *) p->mbase; + host->irq = p->irq; + if (p->bus_type == AIC_WIDE) { host->max_id = 16; } - if (config->bus_type == AIC_TWIN) + if (p->bus_type == AIC_TWIN) { host->max_channel = 1; } - p = (struct aic7xxx_host *) host->hostdata; - p->host = host; - p->host_no = (int)host->host_no; + p->host_no = host->host_no; p->isr_count = 0; - p->base = base; - p->maxscbs = config->maxscbs; - p->maxhscbs = config->maxhscbs; - p->qcntmask = config->qcntmask; - p->mbase = (char *)config->mbase; - p->type = config->type; - p->chip_type = config->chip_type; - p->flags = config->flags; - p->chan_num = config->chan_num; - p->scb_link = &(p->scb_usage); -#if defined(CONFIG_PCI) && defined(AIC7XXX_SHARE_SCBS) - if ((p->chan_num == 0) && ((p->type == AIC_7873) | (p->type == AIC_7883))) - { - shared_3985_scbs = &(p->scb_usage); - p->scb_link = &(p->scb_usage); - } -#endif - p->scb_link->numscbs = 0; - p->bus_type = config->bus_type; - p->seeprom = sc; p->next = NULL; p->completeq.head = NULL; p->completeq.tail = NULL; - scbq_init(&p->scb_link->free_scbs); - scbq_init(&p->page_scbs); + scbq_init(&p->scb_data->free_scbs); scbq_init(&p->waiting_scbs); - scbq_init(&p->assigned_scbs); - p->unpause = config->unpause; - p->pause = config->pause; - - for (i = 0; i <= 15; i++) + for (i = 0; i <= NUMBER(p->device_status); i++) { p->device_status[i].commands_sent = 0; p->device_status[i].flags = 0; + p->device_status[i].active_cmds = 0; p->device_status[i].last_reset = 0; } - if (aic7xxx_boards[config->irq] == NULL) + if (aic7xxx_boards[p->irq] == NULL) { + int result; + int irq_flags = 0; + +#ifdef AIC7XXX_OLD_ISR_TYPE + irg_flags = SA_INTERRUPT; +#endif /* * Warning! This must be done before requesting the irq. It is * possible for some boards to raise an interrupt as soon as @@ -4353,17 +5030,26 @@ aic7xxx_register(Scsi_Host_Template *template, * kernel, an interrupt is triggered immediately. Therefore, we * must ensure the board data is correctly set before the request. */ - aic7xxx_boards[config->irq] = host; + aic7xxx_boards[p->irq] = host; /* - * Register IRQ with the kernel. + * Register IRQ with the kernel. Only allow sharing IRQs with + * PCI devices. */ - if (request_irq(config->irq, aic7xxx_isr, SA_INTERRUPT | SA_SHIRQ, - "aic7xxx", NULL)) + if (p->chip_class == AIC_777x) + { + result = (request_irq(p->irq, aic7xxx_isr, irq_flags, "aic7xxx", NULL)); + } + else + { + result = (request_irq(p->irq, aic7xxx_isr, irq_flags | SA_SHIRQ, + "aic7xxx", NULL)); + } + if (result < 0) { printk(KERN_WARNING "aic7xxx: Couldn't register IRQ %d, ignoring.\n", - config->irq); - aic7xxx_boards[config->irq] = NULL; + p->irq); + aic7xxx_boards[p->irq] = NULL; return (0); } } @@ -4374,79 +5060,74 @@ aic7xxx_register(Scsi_Host_Template *template, * registered host adapter. Add this host adapter's Scsi_Host * to the beginning of the linked list of hosts at the same IRQ. */ - p->next = aic7xxx_boards[config->irq]; - aic7xxx_boards[config->irq] = host; - } - - /* - * Load the sequencer program, then re-enable the board - - * resetting the AIC-7770 disables it, leaving the lights - * on with nobody home. On the PCI bus you *may* be home, - * but then your mailing address is dynamically assigned - * so no one can find you anyway :-) - */ - printk(KERN_INFO "aic7xxx: Downloading sequencer code..."); - aic7xxx_loadseq(base); - - /* - * Set Fast Mode and Enable the board - */ - outb(FASTMODE, SEQCTL + base); - - if (p->chip_type == AIC_777x) - { - outb(ENABLE, BCTL + base); + p->next = aic7xxx_boards[p->irq]; + aic7xxx_boards[p->irq] = host; } - printk("done.\n"); - /* * Set the SCSI Id, SXFRCTL0, SXFRCTL1, and SIMODE1, for both channels */ if (p->bus_type == AIC_TWIN) { /* - * Select Channel B. + * The controller is gated to channel B after a chip reset; set + * bus B values first. */ - outb((sblkctl & ~SELBUS_MASK) | SELBUSB, SBLKCTL + base); - - outb(config->scsi_id_b, SCSIID + base); - scsi_conf = inb(SCSICONF + base + 1) & (ENSPCHK | STIMESEL); - outb(scsi_conf | ENSTIMER | ACTNEGEN | STPWEN, SXFRCTL1 + base); -#if EXPERIMENTAL_FLAGS - outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, SIMODE1 + base); -#else - outb(ENSELTIMO, SIMODE1 + base); -#endif + outb(p->scsi_id_b, p->base + SCSIID); + scsi_conf = inb(p->base + SCSICONF + 1); + sxfrctl1 = inb(p->base + SXFRCTL1); + outb((scsi_conf & (ENSPCHK | STIMESEL)) | (sxfrctl1 & STPWEN) | + ENSTIMER | ACTNEGEN, p->base + SXFRCTL1); + outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, p->base + SIMODE1); if (p->flags & ULTRA_ENABLED) { - outb(DFON | SPIOEN | ULTRAEN, SXFRCTL0 + base); + outb(DFON | SPIOEN | FAST20, p->base + SXFRCTL0); } else { - outb(DFON | SPIOEN, SXFRCTL0 + base); + outb(DFON | SPIOEN, p->base + SXFRCTL0); } - /* - * Select Channel A - */ - outb((sblkctl & ~SELBUS_MASK) | SELNARROW, SBLKCTL + base); + if ((scsi_conf & RESET_SCSI) && (aic7xxx_no_reset == 0)) + { + /* Reset SCSI bus B. */ + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Resetting channel B\n"); + + aic7xxx_reset_current_bus(p); + } + + /* Select channel A */ + outb(SELNARROW, p->base + SBLKCTL); } - outb(config->scsi_id, SCSIID + base); - scsi_conf = inb(SCSICONF + base) & (ENSPCHK | STIMESEL); - outb(scsi_conf | ENSTIMER | ACTNEGEN | STPWEN, SXFRCTL1 + base); -#if EXPERIMENTAL_FLAGS - outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, SIMODE1 + base); -#else - outb(ENSELTIMO, SIMODE1 + base); -#endif + + outb(p->scsi_id, p->base + SCSIID); + scsi_conf = inb(p->base + SCSICONF); + sxfrctl1 = inb(p->base + SXFRCTL1); + outb((scsi_conf & (ENSPCHK | STIMESEL)) | (sxfrctl1 & STPWEN) | + ENSTIMER | ACTNEGEN, p->base + SXFRCTL1); + outb(ENSELTIMO | ENSCSIRST | ENSCSIPERR, p->base + SIMODE1); if (p->flags & ULTRA_ENABLED) { - outb(DFON | SPIOEN | ULTRAEN, SXFRCTL0 + base); + outb(DFON | SPIOEN | FAST20, p->base + SXFRCTL0); } else { - outb(DFON | SPIOEN, SXFRCTL0 + base); + outb(DFON | SPIOEN, p->base + SXFRCTL0); + } + + if ((scsi_conf & RESET_SCSI) && (aic7xxx_no_reset == 0)) + { + /* Reset SCSI bus A. */ + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Resetting channel A\n"); + + aic7xxx_reset_current_bus(p); + + /* + * Delay for the reset delay. + */ + aic7xxx_delay(AIC7XXX_RESET_DELAY); } /* @@ -4473,67 +5154,47 @@ aic7xxx_register(Scsi_Host_Template *template, /* * Grab the disconnection disable table and invert it for our needs */ - if (have_seeprom) + if (p->flags & USE_DEFAULTS) { - p->discenable = 0x0; + printk(KERN_INFO "aic7xxx: Host adapter BIOS disabled. Using default SCSI " + "device parameters.\n"); + p->discenable = 0xFFFF; } else { - if (config->bios == AIC_DISABLED) - { - printk(KERN_INFO "aic7xxx : Host adapter BIOS disabled. Using default SCSI " - "device parameters.\n"); - p->discenable = 0xFFFF; - } - else - { - p->discenable = ~((inb(DISC_DSB + base + 1) << 8) | - inb(DISC_DSB + base)); - } + p->discenable = ~((inb(p->base + DISC_DSB + 1) << 8) | + inb(p->base + DISC_DSB)); } for (i = 0; i < max_targets; i++) { - if (config->flags & USE_DEFAULTS) + if (p->flags & USE_DEFAULTS) { - target_settings = 0; /* 10 MHz */ + target_settings = 0; /* 10 or 20 MHz depending on Ultra enable */ p->needsdtr_copy |= (0x01 << i); p->needwdtr_copy |= (0x01 << i); + if ((p->chip_class == AIC_786x) || (p->chip_class == AIC_788x)) + ultraenable |= (0x01 << i); } else { - if (have_seeprom) + target_settings = inb(p->base + TARG_SCRATCH + i); + if (target_settings & 0x0F) { - target_settings = ((sc.device_flags[i] & CFXFER) << 4); - if (sc.device_flags[i] & CFSYNCH) - { - p->needsdtr_copy |= (0x01 << i); - } - if (sc.device_flags[i] & CFWIDEB) - { - p->needwdtr_copy |= (0x01 << i); - } - if (sc.device_flags[i] & CFDISC) - { - p->discenable |= (0x01 << i); - } + p->needsdtr_copy |= (0x01 << i); + /* + * Default to asynchronous transfers (0 offset) + */ + target_settings &= 0xF0; } - else + if (target_settings & 0x80) { - target_settings = inb(TARG_SCRATCH + base + i); - if (target_settings & 0x0F) - { - p->needsdtr_copy |= (0x01 << i); - /* - * Default to asynchronous transfers (0 offset) - */ - target_settings &= 0xF0; - } - if (target_settings & 0x80) - { - p->needwdtr_copy |= (0x01 << i); - target_settings &= 0x7F; - } + p->needwdtr_copy |= (0x01 << i); + /* + * Clear the wide flag. When wide negotiation is successful, + * we'll enable it. + */ + target_settings &= 0x7F; } if (p->flags & ULTRA_ENABLED) { @@ -4544,7 +5205,7 @@ aic7xxx_register(Scsi_Host_Template *template, case 0x20: ultraenable |= (0x01 << i); break; - case 0x40: + case 0x40: /* treat 10MHz as 10MHz without Ultra enabled */ target_settings &= ~(0x70); break; default: @@ -4552,7 +5213,7 @@ aic7xxx_register(Scsi_Host_Template *template, } } } - outb(target_settings, (TARG_SCRATCH + base + i)); + outb(target_settings, p->base + TARG_SCRATCH + i); } /* @@ -4567,103 +5228,448 @@ aic7xxx_register(Scsi_Host_Template *template, p->needsdtr = p->needsdtr_copy; p->needwdtr = p->needwdtr_copy; p->orderedtag = 0; -#if 0 - printk("NeedSdtr = 0x%x, 0x%x\n", p->needsdtr_copy, p->needsdtr); - printk("NeedWdtr = 0x%x, 0x%x\n", p->needwdtr_copy, p->needwdtr); -#endif - outb(ultraenable & 0xFF, ULTRA_ENB + base); - outb((ultraenable >> 8) & 0xFF, ULTRA_ENB + base + 1); + outb(ultraenable & 0xFF, p->base + ULTRA_ENB); + outb((ultraenable >> 8) & 0xFF, p->base + ULTRA_ENB + 1); /* - * Set the number of available SCBs. + * Set the number of available hardware SCBs. */ - outb(config->maxhscbs, SCBCOUNT + base); + outb(p->scb_data->maxhscbs, p->base + SCBCOUNT); /* * 2s compliment of maximum tag value. */ - i = p->maxscbs; - outb(-i & 0xFF, COMP_SCBCOUNT + base); + i = p->scb_data->maxscbs; + outb(-i & 0xFF, p->base + COMP_SCBCOUNT); /* - * Set the QCNT (queue count) mask to deal with broken aic7850s that - * sporatically get garbage in the upper bits of their QCNT registers. + * Allocate enough hardware scbs to handle the maximum number of + * concurrent transactions we can have. We have to make sure that + * the allocated memory is contiguous memory. The Linux kmalloc + * routine should only allocate contiguous memory, but note that + * this could be a problem if kmalloc() is changed. */ - outb(config->qcntmask, QCNTMASK + base); + if (p->scb_data->hscbs == NULL) + { + size_t array_size; + unsigned int hscb_physaddr; + + array_size = p->scb_data->maxscbs * sizeof(struct aic7xxx_hwscb); + p->scb_data->hscbs = kmalloc(array_size, GFP_ATOMIC); + if (p->scb_data->hscbs == NULL) + { + printk("aic7xxx: Unable to allocate hardware SCB array; " + "failing detection.\n"); + release_region(p->base, MAXREG - MINREG); + /* + * Ensure that we only free the IRQ when there is _not_ another + * aic7xxx adapter sharing this IRQ. The adapters are always + * added to the beginning of the list, so we can grab the next + * pointer and place it back in the board array. + */ + if (p->next == NULL) + { + free_irq(p->irq, aic7xxx_isr); + } + aic7xxx_boards[p->irq] = p->next; + return(0); + } + /* At least the control byte of each SCB needs to be 0. */ + memset(p->scb_data->hscbs, 0, array_size); + + /* Tell the sequencer where it can find the hardware SCB array. */ + hscb_physaddr = VIRT_TO_BUS(p->scb_data->hscbs); + outb(hscb_physaddr & 0xFF, p->base + HSCB_ADDR); + outb((hscb_physaddr >> 8) & 0xFF, p->base + HSCB_ADDR + 1); + outb((hscb_physaddr >> 16) & 0xFF, p->base + HSCB_ADDR + 2); + outb((hscb_physaddr >> 24) & 0xFF, p->base + HSCB_ADDR + 3); + } /* - * Clear the active flags - no targets are busy. - */ - outb(0, ACTIVE_A + base); - outb(0, ACTIVE_B + base); + * QCount mask to deal with broken aic7850s that sporadically get + * garbage in the upper bits of their QCNT registers. + */ + outb(p->qcntmask, p->base + QCNTMASK); /* * We don't have any waiting selections or disconnected SCBs. */ - outb(SCB_LIST_NULL, WAITING_SCBH + base); - outb(SCB_LIST_NULL, DISCONNECTED_SCBH + base); + outb(SCB_LIST_NULL, p->base + WAITING_SCBH); + outb(SCB_LIST_NULL, p->base + DISCONNECTED_SCBH); /* * Message out buffer starts empty */ - outb(0, MSG_LEN + base); + outb(0, p->base + MSG_LEN); /* - * Reset the SCSI bus. Is this necessary? - * There may be problems for a warm boot without resetting - * the SCSI bus. Either BIOS settings in scratch RAM - * will not get reinitialized, or devices may stay at - * previous negotiated settings (SDTR and WDTR) while - * the driver will think that no negotiations have been - * performed. - * - * Some devices need a long time to "settle" after a SCSI - * bus reset. + * Load the sequencer program, then re-enable the board - + * resetting the AIC-7770 disables it, leaving the lights + * on with nobody home. On the PCI bus you *may* be home, + * but then your mailing address is dynamically assigned + * so no one can find you anyway :-) + */ + aic7xxx_loadseq(p); + + if (p->chip_class == AIC_777x) + { + outb(ENABLE, p->base + BCTL); /* Enable the boards BUS drivers. */ + } + + /* + * Unpause the sequencer before returning and enable + * interrupts - we shouldn't get any until the first + * command is sent to us by the high-level SCSI code. + */ + unpause_sequencer(p, /* unpause_always */ TRUE); + + return (found); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_chip_reset + * + * Description: + * Perform a chip reset on the aic7xxx SCSI controller. The controller + * is paused upon return. + *-F*************************************************************************/ +static void +aic7xxx_chip_reset(struct aic7xxx_host *p) +{ + unsigned char hcntrl; + int wait; + + /* Retain the IRQ type across the chip reset. */ + hcntrl = (inb(p->base + HCNTRL) & IRQMS) | INTEN; + + /* + * For some 274x boards, we must clear the CHIPRST bit and pause + * the sequencer. For some reason, this makes the driver work. + */ + outb(PAUSE | CHIPRST, p->base + HCNTRL); + + /* + * In the future, we may call this function as a last resort for + * error handling. Let's be nice and not do any unecessary delays. + */ + wait = 1000; /* 1 second (1000 * 1000 usec) */ + while ((wait > 0) && ((inb(p->base + HCNTRL) & CHIPRSTACK) == 0)) + { + udelay(1000); /* 1 msec = 1000 usec */ + wait = wait - 1; + } + + if ((inb(p->base + HCNTRL) & CHIPRSTACK) == 0) + { + printk(KERN_INFO "aic7xxx: Chip reset not cleared; clearing manually.\n"); + } + + outb(hcntrl | PAUSE, p->base + HCNTRL); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_alloc + * + * Description: + * Allocate and initialize a host structure. Returns NULL upon error + * and a pointer to a aic7xxx_host struct upon success. + *-F*************************************************************************/ +static struct aic7xxx_host * +aic7xxx_alloc(Scsi_Host_Template *sht, unsigned int base, unsigned int mbase, + aha_chip_type chip_type, int flags, scb_data_type *scb_data) +{ + struct aic7xxx_host *p = NULL; + struct Scsi_Host *host; + + /* + * Allocate a storage area by registering us with the mid-level + * SCSI layer. */ - if (!aic7xxx_no_reset) + host = scsi_register(sht, sizeof(struct aic7xxx_host)); + + if (host != NULL) { - printk("aic7xxx: Resetting the SCSI bus..."); - if (p->bus_type == AIC_TWIN) + p = (struct aic7xxx_host *) host->hostdata; + memset(p, 0, sizeof(struct aic7xxx_host)); + p->host = host; + + if (scb_data != NULL) + { + /* + * We are sharing SCB data areas; use the SCB data pointer + * provided. + */ + p->scb_data = scb_data; + p->flags |= SHARED_SCBDATA; + } + else { /* - * Select Channel B. + * We are not sharing SCB data; allocate one. */ - outb((sblkctl & ~SELBUS_MASK) | SELBUSB, SBLKCTL + base); + p->scb_data = kmalloc(sizeof(scb_data_type), GFP_ATOMIC); + if (p->scb_data != NULL) + { + memset(p->scb_data, 0, sizeof(scb_data_type)); + scbq_init (&p->scb_data->free_scbs); + } + else + { + /* + * For some reason we don't have enough memory. Free the + * allocated memory for the aic7xxx_host struct, and return NULL. + */ + scsi_unregister(host); + p = NULL; + } + } + if (p != NULL) + { + p->host_no = host->host_no; + p->base = base; + p->mbase = mbase; + p->maddr = NULL; + p->flags = flags; + p->chip_type = chip_type; + p->unpause = (inb(p->base + HCNTRL) & IRQMS) | INTEN; + p->pause = p->unpause | PAUSE; + } + } + return (p); +} - outb(SCSIRSTO, SCSISEQ + base); - udelay(1000); - outb(0, SCSISEQ + base); +/*+F************************************************************************* + * Function: + * aic7xxx_free + * + * Description: + * Frees and releases all resources associated with an instance of + * the driver (struct aic7xxx_host *). + *-F*************************************************************************/ +static void +aic7xxx_free (struct aic7xxx_host *p) +{ + int i; - /* Ensure we don't get a RSTI interrupt from this. */ - outb(CLRSCSIRSTI, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); + /* + * We should be careful in freeing the scb_data area. For those + * adapters sharing external SCB RAM(398x), there will be only one + * scb_data area allocated. The flag SHARED_SCBDATA indicates if + * one adapter is sharing anothers SCB RAM. + */ + if (!(p->flags & SHARED_SCBDATA)) + { + /* + * Free the allocated hardware SCB space. + */ + if (p->scb_data->hscbs != NULL) + { + kfree(p->scb_data->hscbs); + } + /* + * Free the driver SCBs. These were allocated on an as-need + * basis. + */ + for (i = 0; i < p->scb_data->numscbs; i++) + { + kfree(p->scb_data->scb_array[i]); + } + /* + * Free the hardware SCBs. + */ + if (p->scb_data->hscbs != NULL) + { + kfree(p->scb_data->hscbs); + } - /* - * Select Channel A. + /* + * Free the SCB data area. + */ + kfree(p->scb_data); + } + /* + * Free the instance of the device structure. + */ + scsi_unregister(p->host); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_load_seeprom + * + * Description: + * Load the seeprom and configure adapter and target settings. + * Returns 1 if the load was successful and 0 otherwise. + *-F*************************************************************************/ +static int +load_seeprom (struct aic7xxx_host *p, unsigned char *sxfrctl1) +{ + int have_seeprom = 0; + int i, max_targets; + unsigned char target_settings, scsi_conf; + unsigned short scarray[128]; + struct seeprom_config *sc = (struct seeprom_config *) scarray; + + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Loading serial EEPROM..."); + } + switch (p->chip_type) + { + case AIC_7770: /* None of these adapters have seeproms. */ + case AIC_7771: + case AIC_7850: + case AIC_7855: + break; + + case AIC_284x: + have_seeprom = read_284x_seeprom(p, (struct seeprom_config *) scarray); + break; + + case AIC_7861: + case AIC_7870: + case AIC_7871: + case AIC_7872: + case AIC_7874: + case AIC_7881: + case AIC_7882: + case AIC_7884: + have_seeprom = read_seeprom(p, p->chan_num * (sizeof(*sc)/2), + scarray, sizeof(*sc)/2, C46); + break; + + case AIC_7860: /* Motherboard Ultra controllers might have RAID port. */ + case AIC_7880: + have_seeprom = read_seeprom(p, 0, scarray, sizeof(*sc)/2, C46); + if (!have_seeprom) + { + have_seeprom = read_seeprom(p, 0, scarray, sizeof(scarray)/2, C56_66); + } + break; + + case AIC_7873: /* The 3985 adapters use the 93c56 serial EEPROM. */ + case AIC_7883: + have_seeprom = read_seeprom(p, p->chan_num * (sizeof(*sc)/2), + scarray, sizeof(scarray)/2, C56_66); + break; + + default: + break; + } + + if (!have_seeprom) + { + if (aic7xxx_verbose) + { + printk("\naic7xxx: No SEEPROM available; using defaults.\n"); + } + p->flags |= USE_DEFAULTS; + } + else + { + if (aic7xxx_verbose) + { + printk("done\n"); + } + p->flags |= HAVE_SEEPROM; + + /* + * Update the settings in sxfrctl1 to match the termination settings. + */ + *sxfrctl1 = 0; + + /* + * First process the settings that are different between the VLB + * and PCI adapter seeproms. + */ + if (p->chip_class == AIC_777x) + { + /* VLB adapter seeproms */ + if (sc->bios_control & CF284XEXTEND) + p->flags |= EXTENDED_TRANSLATION; + + if (sc->adapter_control & CF284XSTERM) + *sxfrctl1 |= STPWEN; + /* + * The 284x SEEPROM doesn't have a max targets field. We + * set it to 16 to make sure we take care of the 284x-wide + * adapters. For narrow adapters, going through the extra + * 8 target entries will not cause any harm since they will + * will not be used. + * + * XXX - We should probably break out the bus detection + * from the register function so we can use it here + * to tell us how many targets there really are. */ - outb((sblkctl & ~SELBUS_MASK) | SELNARROW, SBLKCTL + base); + max_targets = 16; } + else + { + /* PCI adapter seeproms */ + if (sc->bios_control & CFEXTEND) + p->flags |= EXTENDED_TRANSLATION; - outb(SCSIRSTO, SCSISEQ + base); - udelay(1000); - outb(0, SCSISEQ + base); + if (sc->adapter_control & CFSTERM) + *sxfrctl1 |= STPWEN; - /* Ensure we don't get a RSTI interrupt from this. */ - outb(CLRSCSIRSTI, CLRSINT1 + base); - outb(CLRSCSIINT, CLRINT + base); + /* Limit to 16 targets just in case. */ + max_targets = MIN(sc->max_targets & CFMAXTARG, 16); + } - aic7xxx_delay(AIC7XXX_RESET_DELAY); + for (i = 0; i < max_targets; i++) + { + target_settings = (sc->device_flags[i] & CFXFER) << 4; + if (sc->device_flags[i] & CFSYNCH) + target_settings |= SOFS; + if (sc->device_flags[i] & CFWIDEB) + target_settings |= WIDEXFER; + if (sc->device_flags[i] & CFDISC) + p->discenable |= (0x01 << i); + outb(target_settings, p->base + TARG_SCRATCH + i); + } + outb(~(p->discenable & 0xFF), p->base + DISC_DSB); + outb(~((p->discenable >> 8) & 0xFF), p->base + DISC_DSB + 1); - printk("done.\n"); - } + p->scsi_id = sc->brtime_id & CFSCSIID; - /* - * Unpause the sequencer before returning and enable - * interrupts - we shouldn't get any until the first - * command is sent to us by the high-level SCSI code. - */ - UNPAUSE_SEQUENCER(p); - return (found); + scsi_conf = (p->scsi_id & 0x7); + if (sc->adapter_control & CFSPARITY) + scsi_conf |= ENSPCHK; + if (sc->adapter_control & CFRESETB) + scsi_conf |= RESET_SCSI; + + if ((p->chip_class == AIC_786x) || (p->chip_class == AIC_788x)) + { + /* + * We allow the operator to override ultra enable through + * the boot prompt. + */ + if (!(sc->adapter_control & CFULTRAEN) && (aic7xxx_enable_ultra == 0)) + { + /* Treat us as a non-ultra card */ + p->flags &= ~ULTRA_ENABLED; + } + } + + /* Set the host ID */ + outb(scsi_conf, p->base + SCSICONF); + /* In case we are a wide card */ + outb(p->scsi_id, p->base + SCSICONF + 1); + + if (p->chip_class != AIC_777x) + { + /* + * Update the settings in sxfrctl1 to match the termination + * settings. + */ + *sxfrctl1 = 0; + configure_termination(p, sxfrctl1, sc->adapter_control, + (unsigned char) sc->max_targets & CFMAXTARG); + } + } + return (have_seeprom); } /*+F************************************************************************* @@ -4672,17 +5678,24 @@ aic7xxx_register(Scsi_Host_Template *template, * * Description: * Try to detect and register an Adaptec 7770 or 7870 SCSI controller. + * + * XXX - This should really be called aic7xxx_probe(). A sequence of + * probe(), attach()/detach(), and init() makes more sense than + * one do-it-all function. This may be useful when (and if) the + * mid-level SCSI code is overhauled. *-F*************************************************************************/ int aic7xxx_detect(Scsi_Host_Template *template) { - int found = 0, slot, base; - unsigned char irq = 0; + int found = 0; + aha_status_type adapter_bios; + aha_chip_class_type chip_class; + aha_chip_type chip_type; + int slot, base; + int chan_num = 0; + unsigned char hcntrl, sxfrctl1, sblkctl, hostconf, irq = 0; int i; - struct aic7xxx_host_config config; - - template->proc_dir = &proc_scsi_aic7xxx; - config.chan_num = 0; + struct aic7xxx_host *p; /* * Since we may allow sharing of IRQs, it is imperative @@ -4696,6 +5709,10 @@ aic7xxx_detect(Scsi_Host_Template *template) aic7xxx_boards[i] = NULL; } + template->proc_dir = &proc_scsi_aic7xxx; + template->name = aic7xxx_info(NULL); + template->sg_tablesize = AIC7XXX_MAX_SG; + /* * Initialize the spurious count to 0. */ @@ -4717,33 +5734,174 @@ aic7xxx_detect(Scsi_Host_Template *template) continue; } - config.type = aic7xxx_probe(slot, HID0 + base, &(config.bios)); - if (config.type != AIC_NONE) + chip_type = aic7xxx_probe(slot, base + HID0, &(adapter_bios)); + if (chip_type != AIC_NONE) { + + switch (chip_type) + { + case AIC_7770: + case AIC_7771: + printk("aic7xxx: <%s> at EISA %d\n", + board_names[chip_type], slot); + break; + case AIC_284x: + printk("aic7xxx: <%s> at VLB %d\n", + board_names[chip_type], slot); + break; + default: + break; + } + /* * We found a card, allow 1 spurious interrupt. */ aic7xxx_spurious_count = 1; /* - * We "find" a AIC-7770 if we locate the card - * signature and we can set it up and register - * it with the kernel without incident. + * Pause the card preserving the IRQ type. Allow the operator + * to override the IRQ trigger. */ - config.chip_type = AIC_777x; - config.base = base; - config.mbase = 0; - config.irq = irq; - config.parity = AIC_ENABLED; - config.low_term = AIC_UNKNOWN; - config.high_term = AIC_UNKNOWN; - config.flags = 0; - if (aic7xxx_extended) - config.flags |= EXTENDED_TRANSLATION; - config.bus_speed = DFTHRSH_100; - config.busrtime = BOFF; - found += aic7xxx_register(template, &config); + if (aic7xxx_irq_trigger == 1) + hcntrl = IRQMS; /* Level */ + else if (aic7xxx_irq_trigger == 0) + hcntrl = 0; /* Edge */ + else + hcntrl = inb(base + HCNTRL) & IRQMS; /* Default */ + outb(hcntrl | PAUSE, base + HCNTRL); + + irq = inb(INTDEF + base) & 0x0F; + switch (irq) + { + case 9: + case 10: + case 11: + case 12: + case 14: + case 15: + break; + + default: + printk(KERN_WARNING "aic7xxx: Host adapter uses unsupported IRQ " + "level, ignoring.\n"); + irq = 0; + break; + } + + if (irq != 0) + { + p = aic7xxx_alloc(template, base, 0, chip_type, 0, NULL); + if (p == NULL) + { + printk(KERN_WARNING "aic7xxx: Unable to allocate device space.\n"); + continue; + } + p->irq = irq & 0x0F; + p->chip_class = AIC_777x; +#ifdef AIC7XXX_PAGE_ENABLE + p->flags |= PAGE_ENABLED; +#endif + p->instance = found; + if (aic7xxx_extended) + { + p->flags |= EXTENDED_TRANSLATION; + } + aic7xxx_chip_reset(p); + + switch (p->chip_type) + { + case AIC_7770: + case AIC_7771: + { + unsigned char biosctrl = inb(p->base + HA_274_BIOSCTRL); + + /* + * Get the primary channel information. Right now we don't + * do anything with this, but someday we will be able to inform + * the mid-level SCSI code which channel is primary. + */ + if (biosctrl & CHANNEL_B_PRIMARY) + { + p->flags |= FLAGS_CHANNEL_B_PRIMARY; + } + + if ((biosctrl & BIOSMODE) == BIOSDISABLED) + { + p->flags |= USE_DEFAULTS; + } + break; + } + + case AIC_284x: + if (!load_seeprom(p, &sxfrctl1)) + { + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: SEEPROM not available.\n"); + } + break; + + default: /* Won't get here. */ + break; + } + printk(KERN_INFO "aic7xxx: BIOS %sabled, IO Port 0x%x, IRQ %d (%s), ", + (p->flags & USE_DEFAULTS) ? "dis" : "en", p->base, p->irq, + (p->pause & IRQMS) ? "level sensitive" : "edge triggered"); + /* + * Check for Rev C or E boards. Rev E boards can supposedly have + * more than 4 SCBs, while the Rev C boards are limited to 4 SCBs. + * It's still not clear extactly what is different about the Rev E + * boards, but we think it allows 8 bit entries in the QOUTFIFO to + * support "paging" SCBs (more than 4 commands can be active at once). + * + * The Rev E boards have a read/write autoflush bit in the + * SBLKCTL register, while in the Rev C boards it is read only. + */ + sblkctl = inb(p->base + SBLKCTL) ^ AUTOFLUSHDIS; + outb(sblkctl, p->base + SBLKCTL); + if (inb(p->base + SBLKCTL) == sblkctl) + { + /* + * We detected a Rev E board, we allow paging on this board. + */ + printk("Revision >= E\n"); + outb(sblkctl & ~AUTOFLUSHDIS, base + SBLKCTL); + } + else + { + /* Do not allow paging. */ + p->flags &= ~PAGE_ENABLED; + printk("Revision <= C\n"); + } + + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", + (p->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); + + /* + * Set the FIFO threshold and the bus off time. + */ + hostconf = inb(p->base + HOSTCONF); + outb(hostconf & DFTHRSH, p->base + BUSSPD); + outb((hostconf << 2) & BOFF, p->base + BUSTIME); + /* + * Try to initialize the card and register it with the kernel. + */ + if (aic7xxx_register(template, p)) + { + /* + * We successfully found a board and registered it. + */ + found = found + 1; + } + else + { + /* + * Something went wrong; release and free all resources. + */ + aic7xxx_free(p); + } + } /* * Disallow spurious interrupts. */ @@ -4759,15 +5917,15 @@ aic7xxx_detect(Scsi_Host_Template *template) { struct { - unsigned short vendor_id; - unsigned short device_id; - aha_type card_type; - aha_chip_type chip_type; + unsigned short vendor_id; + unsigned short device_id; + aha_chip_type chip_type; + aha_chip_class_type chip_class; } const aic7xxx_pci_devices[] = { {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7850, AIC_7850, AIC_785x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7855, AIC_7855, AIC_785x}, - {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7860, AIC_7860, AIC_785x}, - {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7861, AIC_7861, AIC_785x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7860, AIC_7860, AIC_786x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7861, AIC_7861, AIC_786x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7870, AIC_7870, AIC_787x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7871, AIC_7871, AIC_787x}, {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7872, AIC_7872, AIC_787x}, @@ -4780,14 +5938,14 @@ aic7xxx_detect(Scsi_Host_Template *template) {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7884, AIC_7884, AIC_788x} }; - int error; + int error, flags; int done = 0; unsigned int iobase, mbase; unsigned short index = 0; unsigned char pci_bus, pci_device_fn; - unsigned int csize_lattime; - unsigned int class_revid; - unsigned int devconfig; + unsigned char ultra_enb = 0; + unsigned int devconfig, class_revid; + scb_data_type *shared_scb_data = NULL; char rev_id[] = {'B', 'C', 'D'}; for (i = 0; i < NUMBER(aic7xxx_pci_devices); i++) @@ -4804,36 +5962,33 @@ aic7xxx_detect(Scsi_Host_Template *template) } else /* Found an Adaptec PCI device. */ { - config.type = aic7xxx_pci_devices[i].card_type; - config.chip_type = aic7xxx_pci_devices[i].chip_type; - config.chan_num = 0; - config.bios = AIC_ENABLED; /* Assume bios is enabled. */ - config.flags = 0; - config.busrtime = 40; - switch (config.type) + chip_class = aic7xxx_pci_devices[i].chip_class; + chip_type = aic7xxx_pci_devices[i].chip_type; + chan_num = 0; + flags = 0; + switch (aic7xxx_pci_devices[i].chip_type) { case AIC_7850: case AIC_7855: - case AIC_7860: - case AIC_7861: - config.bios = AIC_DISABLED; - config.flags |= USE_DEFAULTS; - config.bus_speed = DFTHRSH_100; + flags |= USE_DEFAULTS; break; case AIC_7872: /* 3940 */ case AIC_7882: /* 3940-Ultra */ - config.chan_num = number_of_3940s & 0x1; /* Has 2 controllers */ + flags |= MULTI_CHANNEL; + chan_num = number_of_3940s & 0x1; /* Has 2 controllers */ number_of_3940s++; break; case AIC_7873: /* 3985 */ case AIC_7883: /* 3985-Ultra */ - config.chan_num = number_of_3985s; /* Has 3 controllers */ + chan_num = number_of_3985s; /* Has 3 controllers */ + flags |= MULTI_CHANNEL; number_of_3985s++; if (number_of_3985s == 3) { number_of_3985s = 0; + shared_scb_data = NULL; } break; @@ -4850,39 +6005,165 @@ aic7xxx_detect(Scsi_Host_Template *template) PCI_INTERRUPT_LINE, &irq); error += pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_1, &mbase); + error += pcibios_read_config_dword(pci_bus, pci_device_fn, + DEVCONFIG, &devconfig); + error += pcibios_read_config_dword(pci_bus, pci_device_fn, + CLASS_PROGIF_REVID, &class_revid); + + printk("aic7xxx: <%s> at PCI %d\n", + board_names[chip_type], PCI_SLOT(pci_device_fn)); /* - * The first bit of PCI_BASE_ADDRESS_0 is always set, so + * The first bit (LSB) of PCI_BASE_ADDRESS_0 is always set, so * we mask it off. */ iobase &= PCI_BASE_ADDRESS_IO_MASK; + p = aic7xxx_alloc(template, iobase, mbase, chip_type, flags, + shared_scb_data); + + if (p == NULL) + { + printk(KERN_WARNING "aic7xxx: Unable to allocate device space.\n"); + continue; + } + + /* Remember to set the channel number, irq, and chip class. */ + p->chan_num = chan_num; + p->irq = irq; + p->chip_class = chip_class; +#ifdef AIC7XXX_PAGE_ENABLE + p->flags |= PAGE_ENABLED; +#endif + p->instance = found; + /* - * Read the PCI burst size and latency timer. + * Remember how the card was setup in case there is no seeprom. */ - error += pcibios_read_config_dword(pci_bus, pci_device_fn, - CSIZE_LATTIME, &csize_lattime); - printk(KERN_INFO "aic7xxx: BurstLen = %d DWDs, Latency Timer = %d " - "PCLKS\n", (int) (csize_lattime & CACHESIZE), - (csize_lattime >> 8) & 0x000000ff); + p->scsi_id = inb(p->base + SCSIID) & OID; + if ((p->chip_class == AIC_786x) || (p->chip_class == AIC_788x)) + { + p->flags |= ULTRA_ENABLED; + ultra_enb = inb(p->base + SXFRCTL1) & FAST20; + } + sxfrctl1 = inb(p->base + SXFRCTL1) & STPWEN; - error += pcibios_read_config_dword(pci_bus, pci_device_fn, - CLASS_PROGIF_REVID, &class_revid); - if ((class_revid & DEVREVID) < 3) + aic7xxx_chip_reset(p); + +#ifdef AIC7XXX_USE_EXT_SCBRAM + if (devconfig & RAMPSM) { - printk(KERN_INFO "aic7xxx: %s Rev %c.\n", board_names[config.type], - rev_id[class_revid & DEVREVID]); + printk(KERN_INFO "aic7xxx: External RAM detected; enabling RAM " + "access.\n"); + /* + * XXX - Assume 9 bit SRAM and enable parity checking. + */ + devconfig |= EXTSCBPEN; + + /* + * XXX - Assume fast SRAM and only enable 2 cycle access if we + * are sharing the SRAM across multiple adapters (398x). + */ + if ((devconfig & MPORTMODE) == 0) + { + devconfig |= EXTSCBTIME; + } + devconfig &= ~SCBRAMSEL; + pcibios_write_config_dword(pci_bus, pci_device_fn, + DEVCONFIG, devconfig); } +#endif - error += pcibios_read_config_dword(pci_bus, pci_device_fn, - DEVCONFIG, &devconfig); - if (error) + if ((p->flags & USE_DEFAULTS) == 0) { - panic("aic7xxx: (aic7xxx_detect) Error %d reading PCI registers.\n", - error); + load_seeprom(p, &sxfrctl1); + } + + /* + * Take the LED out of diagnostic mode + */ + sblkctl = inb(p->base + SBLKCTL); + outb((sblkctl & ~(DIAGLEDEN | DIAGLEDON)), p->base + SBLKCTL); + + /* + * We don't know where this is set in the SEEPROM or by the + * BIOS, so we default to 100%. + */ + outb(DFTHRSH_100, p->base + DSPCISTATUS); + + if (p->flags & USE_DEFAULTS) + { + int j; + /* + * Default setup; should only be used if the adapter does + * not have a SEEPROM. + */ + /* + * Check the target scratch area to see if someone set us + * up already. We are previously set up if the scratch + * area contains something other than all zeroes and ones. + */ + for (j = TARG_SCRATCH; j < 0x60; j++) + { + if (inb(p->base + j) != 0x00) /* Check for all zeroes. */ + break; + } + if (j == TARG_SCRATCH) + { + for (j = TARG_SCRATCH; j < 0x60; j++) + { + if (inb(p->base + 1) != 0xFF) /* Check for all ones. */ + break; + } + } + if ((j != 0x60) && (p->scsi_id != 0)) + { + p->flags &= ~USE_DEFAULTS; + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: Using leftover BIOS values.\n"); + } + } + else + { + if (aic7xxx_verbose) + { + printk(KERN_INFO "aic7xxx: No BIOS found; using default " + "settings.\n"); + } + /* + * Assume only one connector and always turn on + * termination. + */ + sxfrctl1 = STPWEN; + p->scsi_id = 7; + } + outb((p->scsi_id & HSCSIID) | ENSPCHK | RESET_SCSI, + p->base + SCSICONF); + /* In case we are a wide card. */ + outb(p->scsi_id, p->base + SCSICONF + 1); + if ((ultra_enb == 0) && ((p->flags & USE_DEFAULTS) == 0)) + { + /* + * If there wasn't a BIOS or the board wasn't in this mode + * to begin with, turn off Ultra. + */ + p->flags &= ~ULTRA_ENABLED; + } } - printk(KERN_INFO "aic7xxx: devconfig = 0x%x.\n", devconfig); + /* + * Print some additional information about the adapter. + */ + printk(KERN_INFO "aic7xxx: BIOS %sabled, IO Port 0x%x, " + "IO Mem 0x%x, IRQ %d", + (p->flags & USE_DEFAULTS) ? "dis" : "en", + p->base, p->mbase, p->irq); + if ((class_revid & DEVREVID) < 3) + { + printk(", Revision %c", rev_id[class_revid & DEVREVID]); + } + printk("\n"); /* * I don't think we need to bother with allowing @@ -4891,58 +6172,57 @@ aic7xxx_detect(Scsi_Host_Template *template) */ aic7xxx_spurious_count = 1; - config.base = iobase; - config.mbase = mbase; - config.irq = irq; - config.parity = AIC_ENABLED; - config.low_term = AIC_UNKNOWN; - config.high_term = AIC_UNKNOWN; if (aic7xxx_extended) - config.flags |= EXTENDED_TRANSLATION; -#ifdef AIC7XXX_SHARE_SCBs - if (devconfig & RAMPSM) -#else - if ((devconfig & RAMPSM) && (config.type != AIC_7873) && - (config.type != AIC_7883)) -#endif + p->flags |= EXTENDED_TRANSLATION; + + if (aic7xxx_verbose) + printk(KERN_INFO "aic7xxx: Extended translation %sabled.\n", + (p->flags & EXTENDED_TRANSLATION) ? "en" : "dis"); + + /* + * Put our termination setting into sxfrctl1 now that the + * generic initialization is complete. + */ + sxfrctl1 |= inb(p->base + SXFRCTL1); + outb(sxfrctl1, p->base + SXFRCTL1); + + if (aic7xxx_register(template, p) == 0) { + aic7xxx_free(p); + } + else + { + found = found + 1; + +#ifdef AIC7XXX_USE_EXT_SCBRAM /* - * External SRAM present. The probe will walk the SCBs to see - * how much SRAM we have and set the number of SCBs accordingly. - * We have to turn off SCBRAMSEL to access the external SCB - * SRAM. - * - * It seems that early versions of the aic7870 didn't use these - * bits, hence the hack for the 3940 above. I would guess that - * recent 3940s using later aic7870 or aic7880 chips do actually - * set RAMPSM. + * Set the shared SCB data once we've successfully probed a + * 398x adapter. * - * The documentation isn't clear, but it sounds like the value - * written to devconfig must not have RAMPSM set. The second - * sixteen bits of the register are R/O anyway, so it shouldn't - * affect RAMPSM either way. + * Note that we can only do this if the use of external + * SCB RAM is enabled. */ - printk(KERN_INFO "aic7xxx: External RAM detected; enabling RAM " - "access.\n"); - devconfig &= ~(RAMPSM | SCBRAMSEL); - pcibios_write_config_dword(pci_bus, pci_device_fn, - DEVCONFIG, devconfig); + if ((p->chip_type == AIC_7873) || (p->chip_type == AIC_7883)) + { + if (shared_scb_data == NULL) + { + shared_scb_data = p->scb_data; + } + } +#endif } - found += aic7xxx_register(template, &config); + index++; /* * Disable spurious interrupts. */ aic7xxx_spurious_count = 0; - - index++; } /* Found an Adaptec PCI device. */ } } } #endif CONFIG_PCI - template->name = aic7xxx_info(NULL); return (found); } @@ -4958,45 +6238,45 @@ static void aic7xxx_buildscb(struct aic7xxx_host *p, Scsi_Cmnd *cmd, struct aic7xxx_scb *scb) { - unsigned int addr; /* must be 32 bits */ unsigned short mask; + struct aic7xxx_hwscb *hscb; mask = (0x01 << TARGET_INDEX(cmd)); + hscb = scb->hscb; + /* * Setup the control byte if we need negotiation and have not * already requested it. */ -#ifdef AIC7XXX_TAGGED_QUEUEING - if (cmd->device->tagged_queue) + if (p->discenable & mask) { - cmd->tag = scb->tag; - cmd->device->current_tag = scb->tag; - scb->control |= TAG_ENB; - p->device_status[TARGET_INDEX(cmd)].commands_sent++; - if (p->device_status[TARGET_INDEX(cmd)].commands_sent == 200) - { - scb->control |= 0x02; - p->device_status[TARGET_INDEX(cmd)].commands_sent = 0; - } -#if 0 - if (p->orderedtag & mask) + hscb->control |= DISCENB; +#ifdef AIC7XXX_TAGGED_QUEUEING + if (cmd->device->tagged_queue) { - scb->control |= 0x02; - p->orderedtag = p->orderedtag & ~mask; + cmd->tag = hscb->tag; + p->device_status[TARGET_INDEX(cmd)].commands_sent++; + if (p->device_status[TARGET_INDEX(cmd)].commands_sent < 75) + { + hscb->control |= MSG_SIMPLE_Q_TAG; + } + else + { + hscb->control |= MSG_ORDERED_Q_TAG; + p->device_status[TARGET_INDEX(cmd)].commands_sent = 0; + } } -#endif - } -#endif - if (p->discenable & mask) - { - scb->control |= DISCENB; +#endif /* Tagged queueing */ } + if ((p->needwdtr & mask) && !(p->wdtr_pending & mask)) { p->wdtr_pending |= mask; - scb->control |= NEEDWDTR; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_WDTR; #if 0 - printk("aic7xxx: Sending WDTR request to target %d.\n", cmd->target); + printk("scsi%d: Sending WDTR request to target %d.\n", + p->host_no, cmd->target); #endif } else @@ -5004,19 +6284,20 @@ aic7xxx_buildscb(struct aic7xxx_host *p, Scsi_Cmnd *cmd, if ((p->needsdtr & mask) && !(p->sdtr_pending & mask)) { p->sdtr_pending |= mask; - scb->control |= NEEDSDTR; + hscb->control |= MK_MESSAGE; + scb->flags |= SCB_MSGOUT_SDTR; #if 0 - printk("aic7xxx: Sending SDTR request to target %d.\n", cmd->target); + printk("scsi%d: Sending SDTR request to target %d.\n", + p->host_no, cmd->target); #endif } } - #if 0 printk("aic7xxx: (build_scb) Target %d, cmd(0x%x) size(%u) wdtr(0x%x) " "mask(0x%x).\n", cmd->target, cmd->cmnd[0], cmd->cmd_len, p->needwdtr, mask); #endif - scb->target_channel_lun = ((cmd->target << 4) & 0xF0) | + hscb->target_channel_lun = ((cmd->target << 4) & 0xF0) | ((cmd->channel & 0x01) << 3) | (cmd->lun & 0x07); /* @@ -5030,9 +6311,8 @@ aic7xxx_buildscb(struct aic7xxx_host *p, Scsi_Cmnd *cmd, * XXX - this relies on the host data being stored in a * little-endian format. */ - addr = VIRT_TO_BUS(cmd->cmnd); - scb->SCSI_cmd_length = cmd->cmd_len; - memcpy(scb->SCSI_cmd_pointer, &addr, sizeof(scb->SCSI_cmd_pointer)); + hscb->SCSI_cmd_length = cmd->cmd_len; + hscb->SCSI_cmd_pointer = VIRT_TO_BUS(cmd->cmnd); if (cmd->use_sg) { @@ -5052,15 +6332,16 @@ aic7xxx_buildscb(struct aic7xxx_host *p, Scsi_Cmnd *cmd, scb->sg_list[i].address = VIRT_TO_BUS(sg[i].address); scb->sg_list[i].length = (unsigned int) sg[i].length; } - scb->SG_segment_count = cmd->use_sg; - addr = VIRT_TO_BUS(scb->sg_list); - memcpy(scb->SG_list_pointer, &addr, sizeof(scb->SG_list_pointer)); - memcpy(scb->data_pointer, &(scb->sg_list[0].address), - sizeof(scb->data_pointer)); - scb->data_count = scb->sg_list[0].length; + hscb->SG_list_pointer = VIRT_TO_BUS(scb->sg_list); + hscb->SG_segment_count = cmd->use_sg; + scb->sg_count = hscb->SG_segment_count; + + /* Copy the first SG into the data pointer area. */ + hscb->data_pointer = scb->sg_list[0].address; + hscb->data_count = scb->sg_list[0].length | (SCB_LIST_NULL << 24); #if 0 printk("aic7xxx: (build_scb) SG segs(%d), length(%u), sg[0].length(%d).\n", - cmd->use_sg, aic7xxx_length(cmd, 0), scb->data_count); + cmd->use_sg, aic7xxx_length(cmd, 0), hscb->data_count); #endif } else @@ -5069,28 +6350,23 @@ aic7xxx_buildscb(struct aic7xxx_host *p, Scsi_Cmnd *cmd, printk("aic7xxx: (build_scb) Creating scatterlist, addr(0x%lx) length(%d).\n", (unsigned long) cmd->request_buffer, cmd->request_bufflen); #endif - if (cmd->request_bufflen == 0) + if (cmd->request_bufflen) { - /* - * In case the higher level SCSI code ever tries to send a zero - * length command, ensure the SCB indicates no data. The driver - * will interpret a zero length command as a Bus Device Reset. - */ - scb->SG_segment_count = 0; - memset(scb->SG_list_pointer, 0, sizeof(scb->SG_list_pointer)); - memset(scb->data_pointer, 0, sizeof(scb->data_pointer)); - scb->data_count = 0; + hscb->SG_segment_count = 1; + scb->sg_count = 1; + scb->sg_list[0].address = VIRT_TO_BUS(cmd->request_buffer); + scb->sg_list[0].length = cmd->request_bufflen; + hscb->SG_list_pointer = VIRT_TO_BUS(&scb->sg_list[0]); + hscb->data_count = scb->sg_list[0].length | (SCB_LIST_NULL << 24); + hscb->data_pointer = VIRT_TO_BUS(cmd->request_buffer); } else { - scb->SG_segment_count = 1; - scb->sg_list[0].address = VIRT_TO_BUS(cmd->request_buffer); - scb->sg_list[0].length = cmd->request_bufflen; - addr = VIRT_TO_BUS(&scb->sg_list[0]); - memcpy(scb->SG_list_pointer, &addr, sizeof(scb->SG_list_pointer)); - scb->data_count = scb->sg_list[0].length; - addr = VIRT_TO_BUS(cmd->request_buffer); - memcpy(scb->data_pointer, &addr, sizeof(scb->data_pointer)); + hscb->SG_segment_count = 0; + scb->sg_count = 0; + hscb->SG_list_pointer = 0; + hscb->data_pointer = 0; + hscb->data_count = SCB_LIST_NULL << 24; } } } @@ -5108,7 +6384,6 @@ aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *)) long processor_flags; struct aic7xxx_host *p; struct aic7xxx_scb *scb; - u_char curscb, intstat; p = (struct aic7xxx_host *) cmd->host->hostdata; if (p->host != cmd->host) @@ -5140,34 +6415,21 @@ aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *)) cmd->lun & 0x07); #endif - /* - * This is a critical section, since we don't want the interrupt - * routine mucking with the host data or the card. For this reason - * it is nice to know that this function can only be called in one - * of two ways from scsi.c First, as part of a routine queue command, - * in which case, the irq for our card is disabled before this - * function is called. This doesn't help us if there is more than - * one card using more than one IRQ in our system, therefore, we - * should disable all interrupts on these grounds alone. Second, - * this can be called as part of the scsi_done routine, in which case - * we are in the aic7xxx_isr routine already and interrupts are - * disabled, therefore we should saveflags first, then disable the - * interrupts, do our work, then restore the CPU flags. If it weren't - * for the possibility of more than one card using more than one IRQ - * in our system, we wouldn't have to touch the interrupt flags at all. - */ - save_flags(processor_flags); - cli(); - + if (p->device_status[TARGET_INDEX(cmd)].active_cmds + > cmd->device->queue_depth) + { + printk(KERN_WARNING "(scsi%d:%d:%d) Commands queued exceeds queue depth\n", + p->host_no, cmd->target, cmd->channel); + } scb = aic7xxx_allocate_scb(p); if (scb == NULL) { - panic("aic7xxx: (aic7xxx_free) Couldn't find a free SCB.\n"); + panic("aic7xxx: (aic7xxx_queue) Couldn't find a free SCB.\n"); } else { scb->cmd = cmd; - aic7xxx_position(cmd) = scb->tag; + aic7xxx_position(cmd) = scb->hscb->tag; #if 0 debug_scb(scb); #endif; @@ -5179,14 +6441,14 @@ aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *)) aic7xxx_buildscb(p, cmd, scb); #if 0 - if (scb != (p->scb_array[scb->position])) + if (scb != (p->scb_data->scb_array[scb->hscb->tag])) { printk("aic7xxx: (queue) Address of SCB by position does not match SCB " "address.\n"); } printk("aic7xxx: (queue) SCB pos(%d) cmdptr(0x%x) state(%d) freescb(0x%x)\n", - scb->position, (unsigned int) scb->cmd, - scb->state, (unsigned int) p->free_scb); + scb->hscb->tag, (unsigned int) scb->cmd, + scb->flags, (unsigned int) p->free_scb); #endif /* @@ -5201,70 +6463,28 @@ aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *)) cmd->host_scribble = NULL; memset(&cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); - if (scb->position != SCB_LIST_NULL) - { - /* We've got a valid slot, yeah! */ - if (p->flags & IN_ISR) - { - scbq_insert_tail(&p->assigned_scbs, scb); - scb->state |= SCB_ASSIGNEDQ; - } - else - { - /* - * Pause the sequencer so we can play with its registers - - * wait for it to acknowledge the pause. - * - * XXX - should the interrupts be left on while doing this? - */ - PAUSE_SEQUENCER(p); - intstat = inb(INTSTAT + p->base); - - /* - * Save the SCB pointer and put our own pointer in - this - * selects one of the four banks of SCB registers. Load - * the SCB, then write its pointer into the queue in FIFO - * and restore the saved SCB pointer. - */ - curscb = inb(SCBPTR + p->base); - outb(scb->position, SCBPTR + p->base); - aic7xxx_putscb(p, scb); - outb(curscb, SCBPTR + p->base); - outb(scb->position, QINFIFO + p->base); - scb->state |= SCB_ACTIVE; + scb->flags |= SCB_ACTIVE | SCB_WAITINGQ; - /* - * Guard against unpausing the sequencer if there is an interrupt - * waiting to happen. - */ - if (!(intstat & (BRKADRINT | SEQINT | SCSIINT))) - { - UNPAUSE_SEQUENCER(p); - } - } - } - else + save_flags(processor_flags); + cli(); + scbq_insert_tail(&p->waiting_scbs, scb); + if ((p->flags & (IN_ISR | IN_TIMEOUT)) == 0) { - scb->state |= SCB_WAITINGQ; - scbq_insert_tail(&p->waiting_scbs, scb); - if (!(p->flags & IN_ISR)) - { - aic7xxx_run_waiting_queues(p); - } + aic7xxx_run_waiting_queues(p); } + restore_flags(processor_flags); #if 0 printk("aic7xxx: (queue) After - cmd(0x%lx) scb->cmd(0x%lx) pos(%d).\n", - (long) cmd, (long) scb->cmd, scb->position); + (long) cmd, (long) scb->cmd, scb->hscb->tag); #endif; - restore_flags(processor_flags); } return (0); } /*+F************************************************************************* * Function: - * aic7xxx_abort_reset + * aic7xxx_bus_device_reset * * Description: * Abort or reset the current SCSI command(s). If the scb has not @@ -5276,204 +6496,257 @@ aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *)) static int aic7xxx_bus_device_reset(struct aic7xxx_host *p, Scsi_Cmnd *cmd) { - struct aic7xxx_scb *scb; + struct aic7xxx_scb *scb; + struct aic7xxx_hwscb *hscb; unsigned char bus_state; - int base, result = -1; + int result = -1; char channel; - scb = (p->scb_array[aic7xxx_position(cmd)]); - base = p->base; + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); + hscb = scb->hscb; - channel = scb->target_channel_lun & SELBUSB ? 'B': 'A'; - if ((cmd == scb->cmd) && (scb->state & SCB_IN_PROGRESS)) + /* + * Ensure that the card doesn't do anything behind our back. + * Also make sure that we didn't just miss an interrupt that + * could affect this abort/reset. + */ + pause_sequencer(p); + while (inb(p->base + INTSTAT) & INT_PEND); { + aic7xxx_isr(p->irq, (void *) NULL, (void *) NULL); + pause_sequencer(p); + } + if ((cmd != scb->cmd) || ((scb->flags & SCB_ACTIVE) == 0)) + { + result = SCSI_RESET_NOT_RUNNING; + unpause_sequencer(p, /* unpause_always */ TRUE); + return(result); + } + - if (scb->state & SCB_IN_PROGRESS) + printk(KERN_WARNING "(scsi%d:%d:%d) Abort_reset, scb flags 0x%x, ", + p->host_no, TC_OF_SCB(scb), scb->flags); + bus_state = inb(p->base + LASTPHASE); + + switch (bus_state) + { + case P_DATAOUT: + printk("Data-Out phase, "); + break; + case P_DATAIN: + printk("Data-In phase, "); + break; + case P_COMMAND: + printk("Command phase, "); + break; + case P_MESGOUT: + printk("Message-Out phase, "); + break; + case P_STATUS: + printk("Status phase, "); + break; + case P_MESGIN: + printk("Message-In phase, "); + break; + default: + /* + * We're not in a valid phase, so assume we're idle. + */ + printk("while idle, LASTPHASE = 0x%x, ", bus_state); + break; + } + printk("SCSISIGI 0x%x, SEQADDR 0x%x, SSTAT0 0x%x, SSTAT1 0x%x\n", + inb(p->base + SCSISIGI), + inb(p->base + SEQADDR0) | (inb(p->base + SEQADDR1) << 8), + inb(p->base + SSTAT0), inb(p->base + SSTAT1)); + + channel = hscb->target_channel_lun & SELBUSB ? 'B': 'A'; + /* + * Determine our course of action. + */ + if (scb->flags & SCB_ABORT) + { + /* + * Been down this road before; do a full bus reset. + */ + scb->flags |= SCB_RECOVERY_SCB; + unpause_sequencer(p, /* unpause_always */ TRUE); + result = -1; + } +#if 0 + else if (hscb->control & TAG_ENB) { /* - * Ensure that the card doesn't do anything - * behind our back. + * We could be starving this command; try sending and ordered tag + * command to the target we come from. */ - PAUSE_SEQUENCER(p); + scb->flags |= SCB_SENTORDEREDTAG | SCB_RECOVERY_SCB; + p->orderedtag = p->orderedtag | 0xFF; + result = SCSI_RESET_PENDING; + unpause_sequencer(p, /* unpause_always */ TRUE); + printk(KERN_WARNING "scsi%d: Abort_reset, odered tag queued.\n", + p->host_no); + } +#endif + else + { + unsigned char active_scb_index, saved_scbptr; + struct aic7xxx_scb *active_scb; - printk(KERN_WARNING "aic7xxx: (abort_reset) scb state 0x%x, ", scb->state); - bus_state = inb(LASTPHASE + p->base); + /* + * Send an Abort Message: + * The target that is holding up the bus may not be the same as + * the one that triggered this timeout (different commands have + * different timeout lengths). Our strategy here is to queue an + * abort message to the timed out target if it is disconnected. + * Otherwise, if we have an active target we stuff the message buffer + * with an abort message and assert ATN in the hopes that the target + * will let go of the bus and go to the mesgout phase. If this + * fails, we'll get another timeout a few seconds later which will + * attempt a bus reset. + */ + saved_scbptr = inb(p->base + SCBPTR); + active_scb_index = inb(p->base + SCB_TAG); + active_scb = p->scb_data->scb_array[active_scb_index]; - switch (bus_state) + if (bus_state != P_BUSFREE) + { + if (active_scb_index >= p->scb_data->numscbs) { - case P_DATAOUT: - printk("Data-Out phase, "); - break; - case P_DATAIN: - printk("Data-In phase, "); - break; - case P_COMMAND: - printk("Command phase, "); - break; - case P_MESGOUT: - printk("Message-Out phase, "); - break; - case P_STATUS: - printk("Status phase, "); - break; - case P_MESGIN: - printk("Message-In phase, "); - break; - default: - printk("while idle, LASTPHASE = 0x%x, ", bus_state); - /* - * We're not in a valid phase, so assume we're idle. - */ - bus_state = 0; - break; + /* + * Perform a bus reset. + * + * XXX - We want to queue an abort for the timedout SCB + * instead. + */ + result = -1; + printk(KERN_WARNING "scsi%d: Invalid SCB ID %d is active, " + "SCB flags = 0x%x.\n", p->host_no, scb->hscb->tag, scb->flags); } - printk("SCSISIGI = 0x%x\n", inb(p->base + SCSISIGI)); - - /* - * First, determine if we want to do a bus reset or simply a bus device - * reset. If this is the first time that a transaction has timed out - * and the SCB is not paged out, just schedule a bus device reset. - * Otherwise, we reset the bus and abort all pending I/Os on that bus. - */ - if (!(scb->state & (SCB_ABORTED | SCB_PAGED_OUT))) + else { -#if 0 - if (scb->control & TAG_ENB) - { + /* Send the abort message to the active SCB. */ + outb(1, p->base + MSG_LEN); + if (active_scb->hscb->control & TAG_ENB) + { + outb(MSG_ABORT_TAG, p->base + MSG_OUT); + } + else + { + outb(MSG_ABORT, p->base + MSG_OUT); + } + outb(bus_state | ATNO, p->base + SCSISIGO); + printk(KERN_WARNING "scsi%d: abort message in message buffer\n", + p->host_no); + active_scb->flags |= SCB_ABORT | SCB_RECOVERY_SCB; + if (active_scb != scb) + { /* - * We could be starving this command; try sending and ordered tag - * command to the target we come from. + * XXX - We would like to increment the timeout on scb, but + * access to that routine is denied because it is hidden + * in scsi.c. If we were able to do this, it would give + * scb a new lease on life. */ - scb->state = scb->state | SCB_ABORTED | SCB_SENTORDEREDTAG; - p->orderedtag = p->orderedtag | 0xFF; result = SCSI_RESET_PENDING; - UNPAUSE_SEQUENCER(p); - printk(KERN_WARNING "aic7xxx: (abort_reset) Ordered tag queued.\n"); - } -#endif - unsigned char active_scb, control; - struct aic7xxx_scb *active_scbp; + aic7xxx_error(active_scb->cmd) = DID_RESET; + } + else + { + aic7xxx_error(scb->cmd) = DID_RESET; + result = SCSI_RESET_PENDING; + } + unpause_sequencer(p, /* unpause_always */ TRUE); + } + } + else + { + unsigned char hscb_index, linked_next; + int disconnected; - /* - * Send a Bus Device Reset Message: - * The target we select to send the message to may be entirely - * different than the target pointed to by the scb that timed - * out. If the command is in the QINFIFO or the waiting for - * selection list, its not tying up the bus and isn't responsible - * for the delay so we pick off the active command which should - * be the SCB selected by SCBPTR. If its disconnected or active, - * we device reset the target scbp points to. Although it may - * be that this target is not responsible for the delay, it may - * may also be that we're timing out on a command that just takes - * too much time, so we try the bus device reset there first. - */ - active_scb = inb(SCBPTR + base); - active_scbp = (p->scb_array[inb(SCB_TAG + base)]); - control = inb(SCB_CONTROL + base); + disconnected = FALSE; + hscb_index = aic7xxx_find_scb(p, scb); + if (hscb_index == SCB_LIST_NULL) + { + disconnected = TRUE; + linked_next = (scb->hscb->data_count >> 24) & 0xFF; + } + else + { + outb(hscb_index, p->base + SCBPTR); + if (inb(p->base + SCB_CONTROL) & DISCONNECTED) + { + disconnected = TRUE; + } + linked_next = inb(p->base + SCB_LINKED_NEXT); + } + if (disconnected) + { + /* + * Simply set the ABORT_SCB control bit and preserve the + * linked next pointer. + */ + scb->hscb->control |= ABORT_SCB | MK_MESSAGE; + scb->hscb->data_count &= ~0xFF000000; + scb->hscb->data_count |= linked_next << 24; + if ((p->flags & PAGE_ENABLED) == 0) + { + scb->hscb->control &= ~DISCONNECTED; + } + scb->flags |= SCB_QUEUED_ABORT | SCB_ABORT | SCB_RECOVERY_SCB; + if (hscb_index != SCB_LIST_NULL) + { + unsigned char scb_control; - /* - * Test to see if scbp is disconnected - */ - outb(scb->position, SCBPTR + base); - if (inb(SCB_CONTROL + base) & DISCONNECTED) - { -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (abort_scb) scb %d is disconnected; " - "bus device reset message queued.\n", scb->position); -#endif - if (p->flags & PAGE_ENABLED) - { - /* Pull this SCB out of the disconnected list. */ - u_char prev = inb(SCB_PREV + base); - u_char next = inb(SCB_NEXT + base); - if (prev == SCB_LIST_NULL) - { - /* Head of list */ - outb(next, DISCONNECTED_SCBH + base); - } - else - { - outb(prev, SCBPTR + base); - outb(next, SCB_NEXT + base); - if (next != SCB_LIST_NULL) - { - outb(next, SCBPTR + base); - outb(prev, SCB_PREV + base); - } - outb(scb->position, SCBPTR + base); - } - } - scb->state |= (SCB_DEVICE_RESET | SCB_ABORTED); - scb->control = scb->control & DISCENB; - scb->SCSI_cmd_length = 0; - scb->SG_segment_count = 0; - memset(scb->SG_list_pointer, 0, sizeof(scb->SG_list_pointer)); - memset(scb->data_pointer, 0, sizeof(scb->data_pointer)); - scb->data_count = 0; - aic7xxx_putscb(p, scb); - aic7xxx_add_waiting_scb(base, scb); - outb(active_scb, SCBPTR + base); - result = SCSI_RESET_PENDING; - UNPAUSE_SEQUENCER(p); - } - else - { - /* - * Is the active SCB really active? - */ - if ((active_scbp->state & SCB_ACTIVE) && bus_state) - { - /* - * Load the message buffer and assert attention. - */ - active_scbp->state |= (SCB_DEVICE_RESET | SCB_ABORTED); - outb(1, MSG_LEN + base); - outb(MSG_BUS_DEVICE_RESET, MSG0 + base); - outb(bus_state | ATNO, SCSISIGO + base); -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (abort_scb) asserted ATN - " - "bus device reset in message buffer.\n"); -#endif - if (active_scbp != scb) - { - /* - * XXX - We would like to increment the timeout on scb, but - * access to that routine is denied because it is hidden - * in scsi.c. If we were able to do this, it would give - * scb a new lease on life. - */ - ; - } - aic7xxx_error(scb->cmd) = DID_RESET; - /* - * Restore the active SCB and unpause the sequencer. - */ - outb(active_scb, SCBPTR + base); - if (active_scbp != scb) - { - /* - * The mid-level SCSI code requested us to reset a command - * different from the one that we actually reset. Return - * a "not running" indication and hope that the SCSI code - * will Do the Right Thing (tm). - */ - result = SCSI_RESET_NOT_RUNNING; - } - else - { - result = SCSI_RESET_PENDING; - } - UNPAUSE_SEQUENCER(p); - } - } + scb_control = inb(p->base + SCB_CONTROL); + outb(scb_control | MK_MESSAGE| ABORT_SCB, p->base + SCB_CONTROL); + } + /* + * Actually requeue this SCB in case we can select the + * device before it reconnects. If the transaction we + * want to abort is not tagged, unbusy it first so that + * we don't get held back from sending the command. + */ + if ((scb->hscb->control & TAG_ENB) == 0) + { + unsigned char target; + int lun; + + target = scb->cmd->target; + lun = scb->cmd->lun; + aic7xxx_search_qinfifo(p, target, channel, lun, SCB_LIST_NULL, + 0, /* requeue */ TRUE); + } + printk(KERN_WARNING "(scsi%d:%d:%d) Queueing an Abort SCB.\n", + p->host_no, TC_OF_SCB(scb)); + scbq_insert_head(&p->waiting_scbs, scb); + scb->flags |= SCB_WAITINGQ; + outb(saved_scbptr, p->base + SCBPTR); + if ((p->flags & IN_ISR) == 0) + { + /* + * Processing the waiting queue may unpause us. + */ + aic7xxx_run_waiting_queues(p); + /* + * If we are using AAP, aic7xxx_run_waiting_queues() will not + * unpause us, so ensure we are unpaused. + */ + unpause_sequencer(p, /*unpause_always*/ FALSE); + } + else + { + unpause_sequencer(p, /*unpause_always*/ TRUE); + } + result = SCSI_RESET_PENDING; + } + else + { + scb->flags |= SCB_RECOVERY_SCB; + unpause_sequencer(p, /* unpause_always */ TRUE); + result = -1; } } } - /* Make sure the sequencer is unpaused upon return. */ - if (result == -1) - { - UNPAUSE_SEQUENCER(p); - } return (result); } @@ -5491,16 +6764,48 @@ aic7xxx_abort(Scsi_Cmnd *cmd) struct aic7xxx_scb *scb = NULL; struct aic7xxx_host *p; int base, result; + unsigned long processor_flags; p = (struct aic7xxx_host *) cmd->host->hostdata; - scb = (p->scb_array[aic7xxx_position(cmd)]); + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); base = p->base; + save_flags(processor_flags); + cli(); + #ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (abort) Aborting scb %d, TCL %d/%d/%d\n", - scb->position, TCL_OF_SCB(scb)); + if (scb != NULL) + { + printk("(scsi%d:%d:%d) Aborting scb %d, flags 0x%x\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag, scb->flags); + } + else + { + printk("aic7xxx: Abort called with no SCB for cmd.\n"); + } #endif + if (p->flags & IN_TIMEOUT) + { + /* + * We've already started a recovery operation. + */ + if ((scb->flags & SCB_RECOVERY_SCB) == 0) + { + restore_flags(processor_flags); + return (SCSI_ABORT_PENDING); + } + else + { + /* + * This is the second time we've tried to abort the recovery + * SCB. We want the mid-level SCSI code to call the reset + * function to reset the SCSI bus. + */ + restore_flags(processor_flags); + return (SCSI_ABORT_NOT_RUNNING); + } + } if (cmd->serial_number != cmd->serial_number_at_timeout) { result = SCSI_ABORT_NOT_RUNNING; @@ -5509,14 +6814,34 @@ aic7xxx_abort(Scsi_Cmnd *cmd) { result = SCSI_ABORT_NOT_RUNNING; } - else if ((scb->cmd != cmd) || (!(scb->state & SCB_IN_PROGRESS))) + else if ((scb->cmd != cmd) || (!(scb->flags & SCB_ACTIVE))) { result = SCSI_ABORT_NOT_RUNNING; } else { - result = SCSI_ABORT_SNOOZE; + /* + * XXX - Check use of IN_TIMEOUT to see if we're Doing the + * Right Thing with it. + */ + p->flags |= IN_TIMEOUT; + result = aic7xxx_bus_device_reset(p, scb->cmd); + switch (result) + { + case SCSI_RESET_NOT_RUNNING: + p->flags &= ~IN_TIMEOUT; + result = SCSI_ABORT_NOT_RUNNING; + break; + case SCSI_RESET_PENDING: + result = SCSI_ABORT_PENDING; + break; + default: + p->flags &= ~IN_TIMEOUT; + result = SCSI_ABORT_SNOOZE; + break; + } } + restore_flags(processor_flags); return (result); } @@ -5536,18 +6861,27 @@ aic7xxx_reset(Scsi_Cmnd *cmd, unsigned int flags) { struct aic7xxx_scb *scb = NULL; struct aic7xxx_host *p; - int base, found, tindex, min_target, max_target, result = -1; + int base, found, tindex, min_target, max_target; + int result = -1; char channel = 'A'; unsigned long processor_flags; p = (struct aic7xxx_host *) cmd->host->hostdata; - scb = (p->scb_array[aic7xxx_position(cmd)]); + scb = (p->scb_data->scb_array[aic7xxx_position(cmd)]); base = p->base; channel = cmd->channel ? 'B': 'A'; tindex = (cmd->channel << 4) | cmd->target; -#ifdef AIC7XXX_DEBUG_ABORT - printk("aic7xxx: (reset) target/channel %d/%d\n", cmd->target, cmd->channel); +#ifdef 0 /* AIC7XXX_DEBUG_ABORT */ + if (scb != NULL) + { + printk("(scsi%d:%d:%d) Reset called, scb %d, flags 0x%x\n", + p->host_no, TC_OF_SCB(scb), scb->hscb->tag, scb->flags); + } + else + { + printk("aic7xxx: Reset called with no SCB for cmd.\n"); + } #endif /* @@ -5562,34 +6896,45 @@ aic7xxx_reset(Scsi_Cmnd *cmd, unsigned int flags) if (scb->cmd != cmd) scb = NULL; - if (!(flags & (SCSI_RESET_SUGGEST_HOST_RESET | SCSI_RESET_SUGGEST_BUS_RESET)) - && (scb != NULL)) + if (p->flags & IN_TIMEOUT) { /* - * Attempt a bus device reset if commands have completed successfully - * since the last bus device reset, or it has been less than 100ms - * since the last reset. + * We've already started a recovery operation. */ - if ((p->flags & DEVICE_SUCCESS) || - ((jiffies - p->device_status[tindex].last_reset) < HZ/10)) + if ((scb->flags & SCB_RECOVERY_SCB) == 0) { - if (cmd->serial_number != cmd->serial_number_at_timeout) - { - result = SCSI_RESET_NOT_RUNNING; - } - else + restore_flags(processor_flags); + return (SCSI_RESET_PENDING); + } + } + else + { + if (!(flags & (SCSI_RESET_SUGGEST_HOST_RESET | SCSI_RESET_SUGGEST_BUS_RESET)) + && (scb != NULL)) + { + /* + * Attempt a bus device reset if commands have completed successfully + * since the last bus device reset, or it has been less than 100ms + * since the last reset. + */ + if ((p->flags & DEVICE_SUCCESS) || + ((jiffies - p->device_status[tindex].last_reset) < HZ/10)) { - if (scb == NULL) + if (cmd->serial_number != cmd->serial_number_at_timeout) + { + result = SCSI_RESET_NOT_RUNNING; + } + else if (scb == NULL) { result = SCSI_RESET_NOT_RUNNING; } else if (flags & SCSI_RESET_ASYNCHRONOUS) { - if (scb->state & SCB_ABORTED) + if (scb->flags & SCB_ABORTED) { result = SCSI_RESET_PENDING; } - else if (!(scb->state & SCB_IN_PROGRESS)) + else if (!(scb->flags & SCB_ACTIVE)) { result = SCSI_RESET_NOT_RUNNING; } @@ -5600,20 +6945,23 @@ aic7xxx_reset(Scsi_Cmnd *cmd, unsigned int flags) if ((flags & SCSI_RESET_SYNCHRONOUS) && (p->device_status[tindex].flags & BUS_DEVICE_RESET_PENDING)) { - scb->state |= SCB_ABORTED; + scb->flags |= SCB_ABORTED; result = SCSI_RESET_PENDING; } else { + p->flags |= IN_TIMEOUT; result = aic7xxx_bus_device_reset(p, cmd); if (result == 0) + { + p->flags &= ~IN_TIMEOUT; result = SCSI_RESET_PENDING; + } } - } + } } } } - if (result == -1) { /* @@ -5626,11 +6974,11 @@ aic7xxx_reset(Scsi_Cmnd *cmd, unsigned int flags) { result = SCSI_RESET_NOT_RUNNING; } - else if (!(scb->state & SCB_IN_PROGRESS)) + else if (!(scb->flags & SCB_ACTIVE)) { result = SCSI_RESET_NOT_RUNNING; } - else if ((scb->state & SCB_ABORTED) && + else if ((scb->flags & SCB_ABORTED) && (!(p->device_status[tindex].flags & BUS_DEVICE_RESET_PENDING))) { result = SCSI_RESET_PENDING; @@ -5642,8 +6990,9 @@ aic7xxx_reset(Scsi_Cmnd *cmd, unsigned int flags) /* * The reset channel function assumes that the sequencer is paused. */ - PAUSE_SEQUENCER(p); + pause_sequencer(p); found = aic7xxx_reset_channel(p, channel, TRUE); + p->flags = p->flags & ~IN_TIMEOUT; /* * If this is a synchronous reset and there is no SCB for this @@ -5689,8 +7038,10 @@ aic7xxx_reset(Scsi_Cmnd *cmd, unsigned int flags) } result = SCSI_RESET_SUCCESS | SCSI_RESET_HOST_RESET; + p->flags &= ~IN_TIMEOUT; } } + aic7xxx_run_waiting_queues(p); restore_flags(processor_flags); return (result); } |