diff options
Diffstat (limited to 'i386/i386at/gpl/linux/scsi/aic7xxx.c')
-rw-r--r-- | i386/i386at/gpl/linux/scsi/aic7xxx.c | 4645 |
1 files changed, 4645 insertions, 0 deletions
diff --git a/i386/i386at/gpl/linux/scsi/aic7xxx.c b/i386/i386at/gpl/linux/scsi/aic7xxx.c new file mode 100644 index 00000000..3b238983 --- /dev/null +++ b/i386/i386at/gpl/linux/scsi/aic7xxx.c @@ -0,0 +1,4645 @@ +/*+M************************************************************************* + * Adaptec AIC7xxx device driver for Linux. + * + * Copyright (c) 1994 John Aycock + * The University of Calgary Department of Computer Science. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Sources include the Adaptec 1740 driver (aha1740.c), the Ultrastor 24F + * driver (ultrastor.c), various Linux kernel source, the Adaptec EISA + * config file (!adp7771.cfg), the Adaptec AHA-2740A Series User's Guide, + * the Linux Kernel Hacker's Guide, Writing a SCSI Device Driver for Linux, + * the Adaptec 1542 driver (aha1542.c), the Adaptec EISA overlay file + * (adp7770.ovl), the Adaptec AHA-2740 Series Technical Reference Manual, + * 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, + * and other rework of the code. + * + * Parts of this driver are based on the FreeBSD driver by Justin + * T. Gibbs. + * + * A Boot time option was also added for not resetting the scsi bus. + * + * Form: aic7xxx=extended,no_reset + * + * -- Daniel M. Eischen, deischen@iworks.InterWorks.org, 04/03/95 + * + * $Id: aic7xxx.c,v 1.1.1.1 1997/02/25 21:27:46 thomas Exp $ + *-M*************************************************************************/ + +#ifdef MODULE +#include <linux/module.h> +#endif + +#include <stdarg.h> +#include <asm/io.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/bios32.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/blk.h> +#include "sd.h" +#include "scsi.h" +#include "hosts.h" +#include "aic7xxx.h" +#include "aic7xxx_reg.h" +#include <linux/stat.h> + +#include <linux/config.h> /* for CONFIG_PCI */ + +struct proc_dir_entry proc_scsi_aic7xxx = { + PROC_SCSI_AIC7XXX, 7, "aic7xxx", + S_IFDIR | S_IRUGO | S_IXUGO, 2 +}; + +#define AIC7XXX_C_VERSION "$Revision: 1.1.1.1 $" + +#define NUMBER(arr) (sizeof(arr) / sizeof(arr[0])) +#define MIN(a,b) ((a < b) ? a : b) +#define ALL_TARGETS -1 +#ifndef TRUE +# define TRUE 1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif + +/* + * Defines for PCI bus support, testing twin bus support, DMAing of + * SCBs, tagged queueing, commands (SCBs) per lun, and SCSI bus reset + * delay time. + * + * o PCI bus support - this has been implemented and working since + * the December 1, 1994 release of this driver. If you don't have + * a PCI bus, then you can configure your kernel without PCI + * 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 Tagged queueing - this driver is capable of tagged queueing + * but I am unsure as to how well the higher level driver implements + * tagged queueing. Therefore, the maximum commands per lun is + * set to 2. If you want to implement tagged queueing, ensure + * this define is not commented out. + * + * o Sharing IRQs - allowed for sharing of IRQs. This will allow + * for multiple aic7xxx host adapters sharing the same IRQ, but + * not for sharing IRQs with other devices. The higher level + * PCI code and interrupt handling needs to be modified to + * support this. + * + * o Commands per lun - If tagged queueing is enabled, then you + * may want to try increasing AIC7XXX_CMDS_PER_LUN to more + * than 2. By default, we limit the SCBs per lun to 2 with + * or without tagged queueing enabled. If tagged queueing is + * disabled, the sequencer will keep the 2nd SCB in the input + * queue until the first one completes - so it is OK to to have + * more than 1 SCB queued. If tagged queueing is enabled, then + * the sequencer will attempt to send the 2nd SCB to the device + * while the first SCB is executing and the device is disconnected. + * For adapters limited to 4 SCBs, you may want to actually + * decrease the commands per lun to 1, if you often have more + * than 2 devices active at the same time. This will allocate + * 1 SCB for each device and ensure that there will always be + * a free SCB for up to 4 devices active at the same time. + * + * 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 share the same bank of 255 SCBs + * instead of each controller having their own bank (like the + * controllers on the 3940). For this reason, it is important + * that all devices be resident on just one channel of the 3985. + * In the near future, we'll modify the driver to reserve 1/3 + * of the SCBs for each controller. + * + * Daniel M. Eischen, deischen@iworks.InterWorks.org, 01/11/96 + */ + +/* Uncomment this for testing twin bus support. */ +#define AIC7XXX_TWIN_SUPPORT + +/* Uncomment this for tagged queueing. */ +/* #define AIC7XXX_TAGGED_QUEUEING */ + +/* Uncomment this for allowing sharing of IRQs. */ +#define AIC7XXX_SHARE_IRQS + +/* + * You can try raising me if tagged queueing is enabled, or lowering + * me if you only have 4 SCBs. + */ +#define AIC7XXX_CMDS_PER_LUN 2 + +/* Set this to the delay in seconds after SCSI bus reset. */ +#define AIC7XXX_RESET_DELAY 15 + +/* + * Uncomment the following define for collection of SCSI transfer statistics + * for the /proc filesystem. + * + * NOTE: This does affect performance since it has to maintain statistics. + */ +/* #define AIC7XXX_PROC_STATS */ + +/* + * For debugging the abort/reset code. + */ +/* #define AIC7XXX_DEBUG_ABORT */ + +/* + * For general debug messages + */ +#define AIC7XXX_DEBUG + +/* + * Controller type and options + */ +typedef enum { + AIC_NONE, + AIC_7770, /* EISA aic7770 on motherboard */ + AIC_7771, /* EISA aic7771 on 274x */ + AIC_284x, /* VLB aic7770 on 284x */ + AIC_7850, /* PCI aic7850 */ + AIC_7870, /* PCI aic7870 on motherboard */ + AIC_7871, /* PCI aic7871 on 294x */ + AIC_7872, /* PCI aic7872 on 3940 */ + AIC_7873, /* PCI aic7873 on 3985 */ + AIC_7874, /* PCI aic7874 on 294x Differential */ + AIC_7880, /* PCI aic7880 on motherboard */ + AIC_7881, /* PCI aic7881 on 294x Ultra */ + AIC_7882, /* PCI aic7882 on 3940 Ultra */ + AIC_7883, /* PCI aic7883 on 3985 Ultra */ + AIC_7884 /* PCI aic7884 on 294x Ultra Differential */ +} aha_type; + +typedef enum { + AIC_777x, /* AIC-7770 based */ + AIC_785x, /* AIC-7850 based */ + AIC_787x, /* AIC-7870 based */ + AIC_788x /* AIC-7880 based */ +} aha_chip_type; + +typedef enum { + AIC_SINGLE, /* Single Channel */ + AIC_TWIN, /* Twin Channel */ + AIC_WIDE /* Wide Channel */ +} aha_bus_type; + +typedef enum { + AIC_UNKNOWN, + AIC_ENABLED, + AIC_DISABLED +} aha_status_type; + +typedef enum { + LIST_HEAD, + LIST_SECOND +} insert_type; + +typedef enum { + ABORT_RESET_INACTIVE, + ABORT_RESET_PENDING, + ABORT_RESET_SUCCESS +} aha_abort_reset_type; + +/* + * Define an array of board names that can be indexed by aha_type. + * 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-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 */ +}; + +/* + * There should be a specific return value for this in scsi.h, but + * it seems that most drivers ignore it. + */ +#define DID_UNDERFLOW DID_ERROR + +/* + * What we want to do is have the higher level scsi driver requeue + * the command to us. There is no specific driver status for this + * condition, but the higher level scsi driver will requeue the + * command on a DID_BUS_BUSY error. + */ +#define DID_RETRY_COMMAND DID_BUS_BUSY + +/* + * EISA/VL-bus stuff + */ +#define MINSLOT 1 +#define MAXSLOT 15 +#define SLOTBASE(x) ((x) << 12) +#define MAXIRQ 15 + +/* + * Standard EISA Host ID regs (Offset from slot base) + */ +#define HID0 0x80 /* 0,1: msb of ID2, 2-7: ID1 */ +#define HID1 0x81 /* 0-4: ID3, 5-7: LSB ID2 */ +#define HID2 0x82 /* product */ +#define HID3 0x83 /* firmware revision */ + +/* + * AIC-7770 I/O range to reserve for a card + */ +#define MINREG 0xC00 +#define MAXREG 0xCBF + +#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 +#define DEVREVID 0x000000FFul +#define PROGINFC 0x0000FF00ul +#define SUBCLASS 0x00FF0000ul +#define BASECLASS 0xFF000000ul + +#define CSIZE_LATTIME 0x0C +#define CACHESIZE 0x0000003Ful /* only 5 bits */ +#define LATTIME 0x0000FF00ul + +#define DEVCONFIG 0x40 +#define MPORTMODE 0x00000400ul /* aic7870 only */ +#define RAMPSM 0x00000200ul /* aic7870 only */ +#define VOLSENSE 0x00000100ul +#define SCBRAMSEL 0x00000080ul +#define MRDCEN 0x00000040ul +#define EXTSCBTIME 0x00000020ul /* aic7870 only */ +#define EXTSCBPEN 0x00000010ul /* aic7870 only */ +#define BERREN 0x00000008ul +#define DACEN 0x00000004ul +#define STPWLEVEL 0x00000002ul +#define DIFACTNEGEN 0x00000001ul /* aic7870 only */ + +/* + * + * Define the format of the SEEPROM registers (16 bits). + * + */ +struct seeprom_config { + +/* + * SCSI ID Configuration Flags + */ +#define CFXFER 0x0007 /* synchronous transfer rate */ +#define CFSYNCH 0x0008 /* enable synchronous transfer */ +#define CFDISC 0x0010 /* enable disconnection */ +#define CFWIDEB 0x0020 /* wide bus device (wide card) */ +/* UNUSED 0x00C0 */ +#define CFSTART 0x0100 /* send start unit SCSI command */ +#define CFINCBIOS 0x0200 /* include in BIOS scan */ +#define CFRNFOUND 0x0400 /* report even if not found */ +/* UNUSED 0xF800 */ + unsigned short device_flags[16]; /* words 0-15 */ + +/* + * BIOS Control Bits + */ +#define CFSUPREM 0x0001 /* support all removeable drives */ +#define CFSUPREMB 0x0002 /* support removeable drives for boot only */ +#define CFBIOSEN 0x0004 /* BIOS enabled */ +/* UNUSED 0x0008 */ +#define CFSM2DRV 0x0010 /* support more than two drives */ +#define CF284XEXTEND 0x0020 /* extended translation (284x cards) */ +/* UNUSED 0x0040 */ +#define CFEXTEND 0x0080 /* extended translation enabled */ +/* UNUSED 0xFF00 */ + unsigned short bios_control; /* word 16 */ + +/* + * Host Adapter Control Bits + */ +/* UNUSED 0x0001 */ +#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 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 */ +/* UNUSED 0xFF80 */ + unsigned short adapter_control; /* word 17 */ + +/* + * Bus Release, Host Adapter ID + */ +#define CFSCSIID 0x000F /* host adapter SCSI ID */ +/* UNUSED 0x00F0 */ +#define CFBRTIME 0xFF00 /* bus release time */ + unsigned short brtime_id; /* word 18 */ + +/* + * Maximum targets + */ +#define CFMAXTARG 0x00FF /* maximum targets */ +/* UNUSED 0xFF00 */ + unsigned short max_targets; /* word 19 */ + + unsigned short res_1[11]; /* words 20-30 */ + unsigned short checksum; /* word 31 */ +}; + +/* + * 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) \ + 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); + +/* + * If an error occurs during a data transfer phase, run the comand + * to completion - it's easier that way - making a note of the error + * condition in this location. This then will modify a DID_OK status + * into an appropriate error for the higher-level SCSI code. + */ +#define aic7xxx_error(cmd) ((cmd)->SCp.Status) + +/* + * Keep track of the targets returned status. + */ +#define aic7xxx_status(cmd) ((cmd)->SCp.sent_command) + +/* + * The position of the SCSI commands scb within the scb array. + */ +#define aic7xxx_position(cmd) ((cmd)->SCp.have_data_in) + +/* + * Since the sequencer code DMAs the scatter-gather structures + * directly from memory, we use this macro to assert that the + * kernel structure hasn't changed. + */ +#define SG_STRUCT_CHECK(sg) \ + ((char *) &(sg).address - (char *) &(sg) != 0 || \ + (char *) &(sg).length - (char *) &(sg) != 8 || \ + sizeof((sg).address) != 4 || \ + sizeof((sg).length) != 4 || \ + sizeof(sg) != 12) + +/* + * "Static" structures. Note that these are NOT initialized + * to zero inside the kernel - we have to initialize them all + * explicitly. + * + * We support multiple adapter cards per interrupt, but keep a + * linked list of Scsi_Host structures for each IRQ. On an interrupt, + * use the IRQ as an index into aic7xxx_boards[] to locate the card + * information. + */ +static struct Scsi_Host *aic7xxx_boards[MAXIRQ + 1]; + +/* + * When we detect and register the card, it is possible to + * have the card raise a spurious interrupt. Because we need + * to support multiple cards, we cannot tell which card caused + * the spurious interrupt. And, we might not even have added + * the card info to the linked list at the time the spurious + * interrupt gets raised. This variable is suppose to keep track + * of when we are registering a card and how many spurious + * interrupts we have encountered. + * + * 0 - do not allow spurious interrupts. + * 1 - allow 1 spurious interrupt + * 2 - have 1 spurious interrupt, do not allow any more. + * + * I've made it an integer instead of a boolean in case we + * want to allow more than one spurious interrupt for debugging + * purposes. Otherwise, it could just go from true to false to + * true (or something like that). + * + * When the driver detects the cards, we'll set the count to 1 + * for each card detection and registration. After the registration + * of a card completes, we'll set the count back to 0. So far, it + * seems to be enough to allow a spurious interrupt only during + * card registration; if a spurious interrupt is going to occur, + * this is where it happens. + * + * We should be able to find a way to avoid getting the spurious + * interrupt. But until we do, we have to keep this ugly code. + */ +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. + */ + +struct aic7xxx_scb { +/* ------------ 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)); +/* 8*/ unsigned char residual_SG_segment_count; +/* 9*/ unsigned char residual_data_count[3]; +/*12*/ unsigned char data_pointer[4] __attribute__ ((packed)); +/*16*/ unsigned long data_count; +/*20*/ unsigned char SCSI_cmd_pointer[4] __attribute__ ((packed)); +/*24*/ unsigned char SCSI_cmd_length; +#define SCB_PIO_TRANSFER_SIZE 25 /* + * amount we need to upload/download + * via rep in/outsb to perform + * a request sense. The second + * RESERVED byte is initialized to + * 0 in getscb(). + */ +/*25*/ u_char next_waiting; /* Used to thread SCBs awaiting selection. */ + /*-----------------end of hardware supported fields----------------*/ + struct aic7xxx_scb *next; /* next ptr when in free list */ + Scsi_Cmnd *cmd; /* Scsi_Cmnd for this scb */ +#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 + int state; /* current state of scb */ + unsigned int position; /* Position in scb array */ + struct scatterlist sg; + struct scatterlist sense_sg; + unsigned char sense_cmd[6]; /* Allocate 6 characters for sense command */ +}; + +static struct { + unsigned char errno; + const char *errmesg; +} hard_error[] = { + { ILLHADDR, "Illegal Host Access" }, + { ILLSADDR, "Illegal Sequencer Address referrenced" }, + { ILLOPCODE, "Illegal Opcode in sequencer program" }, + { PARERR, "Sequencer Ram Parity Error" } +}; + +static unsigned char +generic_sense[] = { REQUEST_SENSE, 0, 0, 0, 255, 0 }; + +/* + * 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 + +/* + * Define a structure used for each host adapter, only one per IRQ. + */ +struct aic7xxx_host { + int base; /* card base address */ + int maxscb; /* hardware SCBs */ + int numscb; /* current number of scbs */ + int extended; /* extended xlate? */ + aha_type type; /* card type */ + aha_chip_type chip_type; /* chip base type */ + int ultra_enabled; /* Ultra SCSI speed enabled */ + int chan_num; /* for 3940/3985, channel number */ + aha_bus_type bus_type; /* normal/twin/wide bus */ + unsigned char a_scanned; /* 0 not scanned, 1 scanned */ + unsigned char b_scanned; /* 0 not scanned, 1 scanned */ + unsigned int isr_count; /* Interrupt count */ + volatile unsigned char unpause; /* unpause value for HCNTRL */ + volatile unsigned char pause; /* pause value for HCNTRL */ + volatile unsigned short needsdtr_copy; /* default config */ + volatile unsigned short needsdtr; + volatile unsigned short sdtr_pending; + volatile unsigned short needwdtr_copy; /* default config */ + volatile unsigned short needwdtr; + volatile unsigned short wdtr_pending; + volatile unsigned short discenable; /* Targets allowed to disconnect */ + struct seeprom_config seeprom; + int have_seeprom; + struct Scsi_Host *next; /* allow for multiple IRQs */ + struct aic7xxx_scb scb_array[AIC7XXX_MAXSCB]; /* active commands */ + struct aic7xxx_scb *free_scb; /* list of free SCBs */ +#ifdef AIC7XXX_PROC_STATS + /* + * Statistics Kept: + * + * Total Xfers (count for each command that has a data xfer), + * broken down further by reads && writes. + * + * Binned sizes, writes && reads: + * < 512, 512, 1-2K, 2-4K, 4-8K, 8-16K, 16-32K, 32-64K, 64K-128K, > 128K + * + * Total amounts read/written above 512 bytes (amts under ignored) + */ + struct aic7xxx_xferstats { + long xfers; /* total xfer count */ + long w_total; /* total writes */ + long w_total512; /* 512 byte blocks written */ + long w_bins[10]; /* binned write */ + long r_total; /* total reads */ + long r_total512; /* 512 byte blocks read */ + long r_bins[10]; /* binned reads */ + } stats[2][16][8]; /* channel, target, lun */ +#endif /* AIC7XXX_PROC_STATS */ +}; + +struct aic7xxx_host_config { + int irq; /* IRQ number */ + int base; /* I/O base */ + int maxscb; /* hardware SCBs */ + 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 */ + int extended; /* extended xlate? */ + int busrtime; /* bus release time */ + int walk_scbs; /* external SCB RAM detected; walk the scb array */ + aha_type type; /* card type */ + aha_chip_type chip_type; /* chip base type */ + int ultra_enabled; /* Ultra SCSI speed enabled */ + int chan_num; /* for 3940/3985, channel number */ + aha_bus_type bus_type; /* normal/twin/wide bus */ + 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 tranfer periods in ns to the proper value to + * stick in the scsiscfr reg to use that transfer rate. + */ +static struct { + short period; + /* Rates in Ultra mode have bit 8 of sxfr set */ +#define ULTRA_SXFR 0x100 + short rate; + const char *english; +} aic7xxx_syncrates[] = { + { 50, 0x100, "20.0" }, + { 62, 0x110, "16.0" }, + { 75, 0x120, "13.4" }, + { 100, 0x140, "10.0" }, + { 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" } +}; + +static int num_aic7xxx_syncrates = + sizeof(aic7xxx_syncrates) / sizeof(aic7xxx_syncrates[0]); + +#ifdef CONFIG_PCI +static int number_of_39xxs = 0; +#endif CONFIG_PCI + +#ifdef AIC7XXX_DEBUG +static void +debug(const char *fmt, ...) +{ + va_list ap; + char buf[256]; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + printk(buf); + va_end(ap); +} + +static void +debug_config(struct aic7xxx_host_config *p) +{ + int host_conf, 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" }; + + host_conf = inb(HOSTCONF + p->base); + scsi_conf = inb(SCSICONF + p->base); + + /* + * The 7870 gets the bus release time and data FIFO threshold + * from the serial EEPROM (stored in the config structure) and + * scsi_conf register respectively. The 7770 gets the bus + * release time and data FIFO threshold from the scsi_conf and + * host_conf registers respectively. + */ + if (p->chip_type == AIC_777x) + { + dfthresh = (host_conf >> 6); + } + else + { + dfthresh = (scsi_conf >> 6); + } + + 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_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):\n", board_names[p->type], BUSW[p->bus_type]); + 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); + 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); +} +#endif + +#else +# define debug(fmt, args...) +# define debug_config(x) +# define debug_scb(x) +#endif AIC7XXX_DEBUG + +/* + * 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? + */ +static unsigned int aic7xxx_extended = 0; /* extended translation on? */ +static unsigned int aic7xxx_no_reset = 0; /* no resetting of SCSI bus */ + +/*+F************************************************************************* + * Function: + * aic7xxx_setup + * + * Description: + * Handle Linux boot parameters. This routine allows for assigning a value + * to a parameter with a ':' between the parameter and the value. + * ie. aic7xxx=unpause:0x0A,extended + *-F*************************************************************************/ +void +aic7xxx_setup(char *s, int *dummy) +{ + int i, n; + char *p; + + static struct { + const char *name; + unsigned int *flag; + } options[] = { + { "extended", &aic7xxx_extended }, + { "no_reset", &aic7xxx_no_reset }, + { NULL, NULL } + }; + + for (p = strtok(s, ","); p; p = strtok(NULL, ",")) + { + for (i = 0; options[i].name; i++) + { + n = strlen(options[i].name); + if (!strncmp(options[i].name, p, n)) + { + if (p[n] == ':') + { + *(options[i].flag) = simple_strtoul(p + n + 1, NULL, 0); + } + else + { + *(options[i].flag) = !0; + } + } + } + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_loadseq + * + * Description: + * Load the sequencer code into the controller memory. + *-F*************************************************************************/ +static void +aic7xxx_loadseq(int base) +{ + 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" + }; + + /* + * 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); + + outsb(SEQRAM + base, seqprog, sizeof(seqprog)); + + /* + * 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)); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_delay + * + * Description: + * Delay for specified amount of time. + *-F*************************************************************************/ +static void +aic7xxx_delay(int seconds) +{ + unsigned long i; + + i = jiffies + (seconds * HZ); /* compute time to stop */ + + while (jiffies < i) + { + ; /* Do nothing! */ + } +} + +/*+F************************************************************************* + * Function: + * rcs_version + * + * Description: + * Return a string containing just the RCS version number from either + * an Id or Revison RCS clause. + *-F*************************************************************************/ +const char * +rcs_version(const char *version_info) +{ + static char buf[10]; + char *bp, *ep; + + bp = NULL; + strcpy(buf, "????"); + if (!strncmp(version_info, "$Id: ", 5)) + { + if ((bp = strchr(version_info, ' ')) != NULL) + { + bp++; + if ((bp = strchr(bp, ' ')) != NULL) + { + bp++; + } + } + } + else + { + if (!strncmp(version_info, "$Revision: ", 11)) + { + if ((bp = strchr(version_info, ' ')) != NULL) + { + bp++; + } + } + } + + if (bp != NULL) + { + if ((ep = strchr(bp, ' ')) != NULL) + { + register int len = ep - bp; + + strncpy(buf, bp, len); + buf[len] = '\0'; + } + } + + return buf; +} + +/*+F************************************************************************* + * Function: + * aic7xxx_info + * + * Description: + * Return a string describing the driver. + *-F*************************************************************************/ +const char * +aic7xxx_info(struct Scsi_Host *notused) +{ + static char buffer[128]; + + strcpy(buffer, "Adaptec AHA274x/284x/294x (EISA/VLB/PCI-Fast SCSI) "); + strcat(buffer, rcs_version(AIC7XXX_C_VERSION)); + strcat(buffer, "/"); + strcat(buffer, rcs_version(AIC7XXX_H_VERSION)); + strcat(buffer, "/"); + strcat(buffer, rcs_version(AIC7XXX_SEQ_VER)); + + return buffer; +} + +/*+F************************************************************************* + * Function: + * 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. + *-F*************************************************************************/ +static unsigned +aic7xxx_length(Scsi_Cmnd *cmd, int sg_last) +{ + int i, segments; + unsigned length; + struct scatterlist *sg; + + segments = cmd->use_sg - sg_last; + sg = (struct scatterlist *) cmd->buffer; + + if (cmd->use_sg) + { + for (i = length = 0; (i < cmd->use_sg) && (i < segments); i++) + { + length += sg[i].length; + } + } + else + { + length = cmd->request_bufflen; + } + + return (length); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_scsirate + * + * Description: + * Look up the valid period to SCSIRATE conversion in our table + *-F*************************************************************************/ +static void +aic7xxx_scsirate(struct aic7xxx_host *p, unsigned char *scsirate, + short period, unsigned char offset, + int target, char channel) +{ + int i; + + for (i = 0; i < num_aic7xxx_syncrates; i++) + { + if ((aic7xxx_syncrates[i].period - period) >= 0) + { + /* + * Watch out for Ultra speeds when ultra is not enabled and + * vice-versa. + */ + if (p->ultra_enabled) + { + if (!(aic7xxx_syncrates[i].rate & ULTRA_SXFR)) + { + printk ("aic7xxx: Target %d, channel %c, requests %sMHz transfers, " + "but adapter in Ultra mode can only sync at 10MHz or " + "above.\n", target, channel, aic7xxx_syncrates[i].english); + break; /* Use asynchronous transfers. */ + } + } + else + { + /* + * Check for an Ultra device trying to negotiate an Ultra rate + * on an adapter with Ultra mode disabled. + */ + if (aic7xxx_syncrates[i].rate & ULTRA_SXFR) + { + /* + * This should only happen if the driver is the first to negotiate + * and chooses a high rate. We'll just move down the table until + * we hit a non Ultra speed. + */ + continue; + } + } + *scsirate = (aic7xxx_syncrates[i].rate) | (offset & 0x0F); + printk("aic7xxx: Target %d, channel %c, now synchronous at %sMHz, " + "offset(0x%x).\n", + target, channel, aic7xxx_syncrates[i].english, offset); + return; + } + } + + /* + * Default to asynchronous transfer + */ + *scsirate = 0; + printk("aic7xxx: Target %d, channel %c, using asynchronous transfers.\n", + 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) +{ + unsigned char curscb; + int base = p->base; + + curscb = inb(SCBPTR + base); + outb(scb->position, SCBPTR + base); + outb(SCBAUTO, SCBCNT + base); + + /* + * 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. + */ + if (p->type == AIC_284x) + { + outsb(SCBARRAY + base, scb, SCB_PIO_TRANSFER_SIZE); + } + else + { + outsl(SCBARRAY + base, scb, (SCB_PIO_TRANSFER_SIZE + 3) / 4); + } + + outb(0, SCBCNT + base); + outb(curscb, SCBPTR + 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); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_match_scb + * + * Description: + * Checks to see if an scb matches the target/channel as specified. + * If target is ALL_TARGETS (-1), then we're looking for any device + * on the specified channel; this happens when a channel is going + * 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) +{ + int targ = (scb->target_channel_lun >> 4) & 0x0F; + char chan = (scb->target_channel_lun & SELBUSB) ? 'B' : 'A'; + +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (match_scb) comparing target/channel %d/%c to scb %d/%c\n", + target, channel, targ, chan); +#endif + if (target == ALL_TARGETS) + { + return (chan == channel); + } + else + { + return ((chan == channel) && (targ == target)); + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_busy_target + * + * Description: + * Set the specified target active. + *-F*************************************************************************/ +static void +aic7xxx_busy_target(unsigned char target, char channel, int base) +{ + unsigned char active; + unsigned long active_port = ACTIVE_A + base; + + if ((target > 0x07) || (channel == 'B')) + { + /* + * targets on the Second channel or above id 7 store info in byte two + * of ACTIVE + */ + active_port++; + } + active = inb(active_port); + active |= (0x01 << (target & 0x07)); + outb(active, active_port); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_unbusy_target + * + * Description: + * Set the specified target inactive. + *-F*************************************************************************/ +static void +aic7xxx_unbusy_target(unsigned char target, char channel, int base) +{ + unsigned char active; + unsigned long active_port = ACTIVE_A + base; + +#ifdef 0 + printk ("aic7xxx: (unbusy_target) target/channel %d/%c\n", + target, channel); +#endif + if ((target > 0x07) || (channel == 'B')) + { + /* + * targets on the Second channel or above id 7 store info in byte two + * of ACTIVE + */ + active_port++; + } + active = inb(active_port); + active &= ~(0x01 << (target & 0x07)); + outb(active, active_port); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_done + * + * Description: + * Calls the higher level scsi done function and frees the scb. + *-F*************************************************************************/ +static void +aic7xxx_done(struct aic7xxx_host *p, struct aic7xxx_scb *scb) +{ + long flags; + Scsi_Cmnd *cmd = scb->cmd; + +#ifdef 0 + printk ("aic7xxx: (done) target/channel %d/%d\n", + cmd->target, cmd->channel); +#endif + /* + * This is a critical section, since we don't want the + * queue routine mucking with the host data. + */ + save_flags(flags); + cli(); + + /* + * Process the command after marking the scb as free + * and adding it to the free list. + */ + scb->state = SCB_FREE; + scb->next = p->free_scb; + p->free_scb = scb; + scb->cmd = NULL; + + restore_flags(flags); + + cmd->scsi_done(cmd); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_add_waiting_scb + * + * Description: + * Add this SCB to the "waiting for selection" list. + *-F*************************************************************************/ +static void +aic7xxx_add_waiting_scb(u_long base, + struct aic7xxx_scb *scb, + insert_type where) +{ + unsigned char head; + unsigned char curscb; + + curscb = inb(SCBPTR + base); + head = inb(WAITING_SCBH + base); + if (head == SCB_LIST_NULL) + { + /* + * List was empty + */ + head = scb->position; + } + else + { + if (where == LIST_HEAD) + { + outb(scb->position, SCBPTR + base); + outb(head, SCB_NEXT_WAITING + base); + head = scb->position; + } + else + { + /* where == LIST_SECOND */ + unsigned char third_scb; + + outb(head, SCBPTR + base); + third_scb = inb(SCB_NEXT_WAITING + base); + outb(scb->position, SCB_NEXT_WAITING + base); + outb(scb->position, SCBPTR + base); + outb(third_scb, SCB_NEXT_WAITING + base); + } + } + outb(head, WAITING_SCBH + base); + outb(curscb, SCBPTR + base); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_abort_waiting_scb + * + * Description: + * Manipulate the waiting for selection list and return the + * scb that follows the one that we remove. + *-F*************************************************************************/ +static unsigned char +aic7xxx_abort_waiting_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, + unsigned char prev, unsigned char timedout_scb) +{ + 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_WAITING + base); + + /* + * Clear the necessary fields + */ + outb(0, SCBARRAY + base); + outb(SCB_LIST_NULL, SCB_NEXT_WAITING + base); + aic7xxx_unbusy_target(target, channel, base); + + /* + * Update the waiting list + */ + if (prev == SCB_LIST_NULL) + { + /* + * First in the list + */ + outb(next, WAITING_SCBH + base); + } + else + { + /* + * Select the scb that pointed to us and update its next pointer. + */ + outb(prev, SCBPTR + base); + outb(next, SCB_NEXT_WAITING + base); + } + /* + * Update the tail pointer + */ + if (inb(WAITING_SCBT + base) == scb->position) + { + outb(prev, WAITING_SCBT + base); + } + + /* + * 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->cmd->result = (DID_RESET << 16); + aic7xxx_done(p, scb); + +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_waiting_scb) target/channel %d/%c, prev %d, " + "to_scb %d, next %d\n", target, channel, prev, timedout_scb, next); +#endif + return (next); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_reset_device + * + * Description: + * The device at the given target/channel has been reset. Abort + * all active and queued scbs for that target/channel. + *-F*************************************************************************/ +static int +aic7xxx_reset_device(struct aic7xxx_host *p, int target, char channel, + unsigned char timedout_scb) +{ + int base = p->base; + struct aic7xxx_scb *scb; + unsigned char active_scb; + int i = 0; + int found = 0; + + /* + * Restore this when we're done + */ + active_scb = inb(SCBPTR + base); + +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (reset_device) target/channel %d/%c, to_scb %d, " + "active_scb %d\n", target, channel, timedout_scb, active_scb); +#endif + /* + * Search the QINFIFO. + */ + { + int saved_queue[AIC7XXX_MAXSCB]; + int queued = inb(QINCNT + base); + + for (i = 0; i < (queued - found); i++) + { + saved_queue[i] = inb(QINFIFO + base); + scb = &(p->scb_array[saved_queue[i]]); + if (aic7xxx_match_scb(scb, target, channel)) + { + /* + * We found an scb that needs to be aborted. + */ + scb->state |= SCB_ABORTED; + scb->cmd->result = (DID_RESET << 16); + aic7xxx_done(p, scb); + outb(scb->position, SCBPTR + base); + outb(0, SCBARRAY + base); + i--; + found++; + } + } + /* + * Now put the saved scbs back. + */ + for (queued = 0; queued < i; queued++) + { + outb(saved_queue[queued], QINFIFO + base); + } + } + + /* + * Search waiting for selection list. + */ + { + unsigned char next, prev; + + next = inb(WAITING_SCBH + base); /* Start at head of list. */ + prev = SCB_LIST_NULL; + + while (next != SCB_LIST_NULL) + { + scb = &(p->scb_array[next]); + /* + * Select the SCB. + */ + if (aic7xxx_match_scb(scb, target, channel)) + { + next = aic7xxx_abort_waiting_scb(p, scb, prev, timedout_scb); + found++; + } + else + { + outb(scb->position, SCBPTR + base); + prev = next; + next = inb(SCB_NEXT_WAITING + base); + } + } + } + + /* + * Go through the entire SCB array now and look for + * commands for this target that are active. These + * are other (most likely tagged) commands that + * were disconnected when the reset occured. + */ + for (i = 0; i < p->numscb; i++) + { + scb = &(p->scb_array[i]); + if ((scb->state & SCB_ACTIVE) && aic7xxx_match_scb(scb, target, channel)) + { + /* + * Ensure the target is "free" + */ + aic7xxx_unbusy_target(target, channel, base); + outb(scb->position, SCBPTR + base); + outb(0, SCBARRAY + base); + scb->state |= SCB_ABORTED; + scb->cmd->result = (DID_RESET << 16); + aic7xxx_done(p, scb); + found++; + } + } + + outb(active_scb, SCBPTR + base); + return (found); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_reset_current_bus + * + * Description: + * Reset the current SCSI bus. + *-F*************************************************************************/ +static void +aic7xxx_reset_current_bus(int base) +{ +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (reset_current_bus)\n"); +#endif + outb(SCSIRSTO, SCSISEQ + base); + udelay(1000); + outb(0, SCSISEQ + base); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_reset_channel + * + * Description: + * Reset the channel. + *-F*************************************************************************/ +static int +aic7xxx_reset_channel(struct aic7xxx_host *p, char channel, + unsigned char timedout_scb) +{ + int base = p->base; + unsigned char sblkctl; + char cur_channel; + unsigned long offset, offset_max; + int found; + +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (reset_channel) channel %c, to_scb %d\n", + channel, timedout_scb); +#endif + /* + * Clean up all the state information for the + * pending transactions on this bus. + */ + found = aic7xxx_reset_device(p, ALL_TARGETS, channel, timedout_scb); + + 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; + } + else + { + if (p->bus_type == AIC_WIDE) + { + p->needsdtr = p->needsdtr_copy; + 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; + } + else + { + 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; + } + } + while (offset < offset_max) + { + /* + * Revert to async/narrow transfers + * until we renegotiate. + */ + u_char targ_scratch; + targ_scratch = inb(offset); + targ_scratch &= SXFR; + outb(targ_scratch, offset); + offset++; + } + + /* + * Reset the bus and unpause/restart the controller + */ + + /* + * Case 1: Command for another bus is active + */ + sblkctl = inb(SBLKCTL + base); + cur_channel = (sblkctl & SELBUSB) ? 'B' : 'A'; + if (cur_channel != channel) + { +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (reset_channel) Stealthily resetting channel %c\n", + channel); +#endif + /* + * Stealthily reset the other bus without upsetting the current bus + */ + outb(sblkctl ^ SELBUSB, SBLKCTL + base); + aic7xxx_reset_current_bus(base); + outb(sblkctl, SBLKCTL + base); + + UNPAUSE_SEQUENCER(p); + } + /* + * Case 2: A command from this bus is active or we're idle + */ + else + { +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (reset_channel) Resetting current channel %c\n", + channel); +#endif + aic7xxx_reset_current_bus(base); + RESTART_SEQUENCER(p); + } + + return found; +} + +/*+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, struct pt_regs * regs) +{ + int base, intstat; + struct aic7xxx_host *p; + struct aic7xxx_scb *scb; + unsigned char ha_flags; + short transfer; + unsigned char scsi_id, bus_width; + unsigned char offset, rate, scratch, scratch_offset; + unsigned char max_offset, rej_byte; + unsigned short target_mask; + char channel; + void *addr; + int actual; + int scb_index; + Scsi_Cmnd *cmd; + + 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(INTSTAT + p->base) & INT_PEND)) + { + if (p->next == NULL) + { + p = NULL; + } + else + { + p = (struct aic7xxx_host *) p->next->hostdata; + } + } + + if (p == NULL) + { + if (aic7xxx_spurious_count == 1) + { + aic7xxx_spurious_count = 2; + printk("aic7xxx: (aic7xxx_isr) Encountered spurious interrupt.\n"); + return; + } + else + { + /* + * The best we can do is to set p back to head of list and process + * the erroneous interrupt - most likely a BRKADRINT. + */ + p = (struct aic7xxx_host *) aic7xxx_boards[irq]->hostdata; + } + } + + /* + * Keep track of interrupts for /proc/scsi + */ + p->isr_count++; + + if (!p->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("aic7xxx: (aic7xxx_isr) Encountered spurious interrupt.\n"); + return; + } + + base = p->base; + /* + * Handle all the interrupt sources - especially for SCSI + * interrupts, we won't get a second chance at them. + */ + intstat = inb(INTSTAT + base); + + if (intstat & BRKADRINT) + { + int i; + unsigned char errno = inb(ERROR + base); + + printk("aic7xxx: (aic7xxx_isr) BRKADRINT error(0x%x):\n", errno); + for (i = 0; i < NUMBER(hard_error); i++) + { + if (errno & hard_error[i].errno) + { + printk(" %s\n", hard_error[i].errmesg); + } + } + + panic("aic7xxx: (aic7xxx_isr) BRKADRINT, error(0x%x) seqaddr(0x%x).\n", + inb(ERROR + base), (inb(SEQADDR1 + base) << 8) | inb(SEQADDR0 + base)); + } + + if (intstat & SEQINT) + { + /* + * 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); + + 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); + + switch (intstat & SEQINT_MASK) + { + case BAD_PHASE: + panic("aic7xxx: (aic7xxx_isr) Unknown scsi bus phase.\n"); + break; + + case SEND_REJECT: + rej_byte = inb(REJBYTE + base); + if ((rej_byte & 0xF0) == 0x20) + { + scb_index = inb(SCBPTR + base); + scb = &(p->scb_array[scb_index]); + printk("aic7xxx: Warning - Tagged message received without identify." + "Disabling tagged commands for target %d channel %c.\n", + scsi_id, channel); + scb->cmd->device->tagged_supported = 0; + scb->cmd->device->tagged_queue = 0; + } + else + { + debug("aic7xxx: Warning - Rejecting unknown message (0x%x) received " + "from target %d channel %c.\n", rej_byte, scsi_id, channel); + } + break; + + case NO_IDENT: + panic("aic7xxx: Target %d, channel %c, did not send an IDENTIFY " + "message. SAVED_TCL(0x%x).\n", + scsi_id, channel, inb(SAVED_TCL + base)); + break; + + case NO_MATCH: + printk("aic7xxx: No active SCB for reconnecting target %d, " + "channel %c - Issuing ABORT. SAVED_TCL(0x%x).\n", + scsi_id, channel, inb(SAVED_TCL + base)); + aic7xxx_unbusy_target(scsi_id, channel, base); + outb(0, SCBARRAY + base); + outb(CLRSELTIMEO, CLRSINT1 + base); + RESTART_SEQUENCER(p); + 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); + /* + * The maximum offset for a wide device is 0x08; for a + * 8-bit bus device the maximum offset is 0x0F. + */ + if (scratch & WIDEXFER) + { + max_offset = 0x08; + } + 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) + { /* + * The requested rate was so low that asynchronous transfers + * are faster (not to mention the controller won't support + * them), so we issue a reject to ensure we go to asynchronous + * transfers. + */ + 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("aic7xxx: Received MSG_WDTR, Target %d, channel %c " + "needwdtr(0x%x).\n", 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("aic7xxx: Target %d, channel %c, using 16 bit " + "transfers.\n", scsi_id, channel); + scratch |= 0x80; + break; + + case BUS_32_BIT: + outb(SEND_REJ, RETURN_1 + base); + printk("aic7xxx: Target %d, channel %c, requesting 32 bit " + "transfers, rejecting...\n", scsi_id, channel); + break; + } + } + else + { + /* + * Send our own WDTR in reply. + */ + printk("aic7xxx: Will send WDTR!!\n"); + 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("aic7xxx: Target %d, channel %c, using 16 bit " + "transfers.\n", scsi_id, channel); + scratch |= 0x80; + break; + } + outb(bus_width | SEND_WDTR, RETURN_1 + base); + } + p->needwdtr &= ~target_mask; + p->wdtr_pending &= ~target_mask; + outb(scratch, TARG_SCRATCH + base + scratch_offset); + outb(scratch, SCSIRATE + base); + break; + } + + case REJECT_MSG: + { + /* + * 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. + */ + + 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("aic7xxx: Target %d, channel %c, refusing WIDE negotiation. " + "Using 8 bit transfers.\n", 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("aic7xxx: Target %d, channel %c, refusing synchronous " + "negotiation. Using asynchronous transfers.\n", + scsi_id, channel); + } + /* + * Otherwise, we ignore it. + */ + } + outb(scratch, TARG_SCRATCH + base + scratch_offset); + outb(scratch, SCSIRATE + base); + break; + } + + case BAD_STATUS: + scb_index = inb(SCBPTR + 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)) + { + printk("aic7xxx: Referenced SCB not valid during SEQINT(0x%x) " + "scb(%d) state(0x%x) cmd(0x%x).\n", + intstat, scb_index, scb->state, (unsigned int) scb->cmd); + } + else + { + cmd = scb->cmd; + aic7xxx_getscb(p, scb); + aic7xxx_status(cmd) = scb->target_status; + + cmd->result |= scb->target_status; + + switch (status_byte(scb->target_status)) + { + case GOOD: + printk("aic7xxx: Interrupted for status of GOOD???\n"); + break; + + case CHECK_CONDITION: + if ((aic7xxx_error(cmd) == 0) && !(cmd->flags & WAS_SENSE)) + { + unsigned char tcl; + unsigned char control; + void *req_buf; + + tcl = scb->target_channel_lun; + + /* + * Send a sense command to the requesting target. + */ + 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->sense_sg.address = (char *) &cmd->sense_buffer; + scb->sense_sg.length = sizeof(cmd->sense_buffer); + req_buf = &scb->sense_sg; + cmd->cmd_len = COMMAND_SIZE(cmd->cmnd[0]); + control = scb->control; + + memset(scb, 0, SCB_PIO_TRANSFER_SIZE); + scb->control = control & DISCENB; + scb->target_channel_lun = tcl; + addr = 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->sense_sg.length; + memcpy(scb->data_pointer, &(scb->sense_sg.address), 4); + + aic7xxx_putscb(p, scb); + outb(SCB_LIST_NULL, SCB_NEXT_WAITING + base); + /* + * Ensure that the target is "BUSY" so we don't get overlapping + * commands if we happen to be doing tagged I/O. + */ + aic7xxx_busy_target(scsi_id, channel, base); + + aic7xxx_add_waiting_scb(base, scb, LIST_HEAD); + outb(SEND_SENSE, RETURN_1 + base); + } /* first time sense, no errors */ + + cmd->flags &= ~ASKED_FOR_SENSE; + if (aic7xxx_error(cmd) == 0) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + break; + + case BUSY: + printk("aic7xxx: Target busy.\n"); + if (!aic7xxx_error(cmd)) + { + aic7xxx_error(cmd) = DID_BUS_BUSY; + } + break; + + case QUEUE_FULL: + printk("aic7xxx: Queue full.\n"); + if (!aic7xxx_error(cmd)) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + break; + + default: + printk("aic7xxx: Unexpected target status(0x%x).\n", + scb->target_status); + if (!aic7xxx_error(cmd)) + { + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + break; + } /* end switch */ + } /* end else of */ + break; + + case RESIDUAL: + scb_index = inb(SCBPTR + base); + scb = &(p->scb_array[scb_index]); + if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk("aic7xxx: Referenced SCB not valid during SEQINT(0x%x) " + "scb(%d) state(0x%x) cmd(0x%x).\n", + intstat, scb_index, scb->state, (unsigned int) 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("aic7xxx: Target %d underflow - " + "Wanted (at least) (%u) got(%u) count(%d).\n", + cmd->target, cmd->underflow, actual, + inb(SCB_RESID_SGCNT + base)); + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + aic7xxx_status(cmd) = scb->target_status; + } + } + } + break; + + case ABORT_TAG: + scb_index = inb(SCBPTR + base); + scb = &(p->scb_array[scb_index]); + if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk("aic7xxx: Referenced SCB not valid during SEQINT(0x%x) " + "scb(%d) state(0x%x) cmd(0x%x)\n", + intstat, scb_index, scb->state, (unsigned int) scb->cmd); + } + else + { + cmd = scb->cmd; + /* + * We didn't receive a valid tag back from the target + * on a reconnect. + */ + printk("aic7xxx: Invalid tag received on target %d, channel %c, " + "lun %d - Sending ABORT_TAG.\n", + scsi_id, channel, cmd->lun & 0x07); + + cmd->result = (DID_RETRY_COMMAND << 16); + aic7xxx_done(p, scb); + } + break; + + case AWAITING_MSG: + scb_index = inb(SCBPTR + base); + scb = &(p->scb_array[scb_index]); + if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk("aic7xxx: Referenced SCB not valid during SEQINT(0x%x) " + "scb(%d) state(0x%x) cmd(0x%x).\n", + intstat, scb_index, scb->state, (unsigned int) scb->cmd); + } + else + { + /* + * 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("aic7xxx: AWAITING_SCB for an SCB that does " + "not have a waiting message.\n"); + } + } + break; + + case IMMEDDONE: + scb_index = inb(SCBPTR + base); + scb = &(p->scb_array[scb_index]); +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (isr) received IMMEDDONE for target %d, scb %d, state %d\n", + scsi_id, scb_index, scb->state); +#endif + if (scb->state & SCB_DEVICE_RESET) + { + int found; + + /* + * Go back to async/narrow transfers and renogiate. + */ + 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, SCB_LIST_NULL); + } + else + { + panic("aic7xxx: Immediate complete for unknown operation.\n"); + } + break; + +#if AIC7XXX_NOT_YET + /* XXX Fill these in later */ + case MESG_BUFFER_BUSY: + break; + case MSGIN_PHASEMIS: + break; +#endif + + case PARITY_ERROR: + { + scb_index = inb(SCBPTR + base); + scb = &(p->scb_array[scb_index]); + if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk("aic7xxx: Referenced SCB not valid during SEQINT(0x%x) " + "scb(%d) state(0x%x) cmd(0x%x).\n", + intstat, scb_index, scb->state, (unsigned int) scb->cmd); + } + else + { + char *phase; + unsigned char mesg_out = MSG_NOP; + unsigned char lastphase = inb(LASTPHASE + base); + + cmd = scb->cmd; + switch (lastphase) + { + 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; + } + + /* + * A parity error has occurred during a data + * transfer phase. Flag it and continue. + */ + printk("aic7xxx: Parity error during phase %s on target %d, " + "channel %d, lun %d.\n", phase, + cmd->target, cmd->channel & 0x01, cmd->lun & 0x07); + + /* + * 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); + aic7xxx_error(cmd) = DID_PARITY; + } + else + { + /* + * Should we allow the target to make this decision for us? + */ + aic7xxx_error(cmd) = DID_RETRY_COMMAND; + } + } + break; + } + default: /* unknown */ + debug("aic7xxx: SEQINT, INTSTAT(0x%x) SCSISIGI(0x%x).\n", + intstat, inb(SCSISIGI + base)); + break; + } + + outb(CLRSEQINT, CLRINT + base); + UNPAUSE_SEQUENCER(p); + } + + if (intstat & SCSIINT) + { + int status = inb(SSTAT1 + base); + scsi_id = (inb(SCSIID + base) >> 4) & 0x0F; + channel = 'A'; + if (inb(SBLKCTL + base) & SELBUSB) + { + channel = 'B'; + } + + scb_index = inb(SCBPTR + base); + scb = &(p->scb_array[scb_index]); + if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk("aic7xxx: No command for SCB (SCSIINT).\n"); + /* + * Turn off the interrupt and set status + * to zero, so that it falls through the + * reset of the SCSIINT code. + */ + outb(status, CLRSINT1 + base); + UNPAUSE_SEQUENCER(p); + outb(CLRSCSIINT, CLRINT + base); + scb = NULL; + } + else + { + cmd = scb->cmd; + + /* + * Only the SCSI Status 1 register has information + * about exceptional conditions that we'd have a + * SCSIINT about; anything in SSTAT0 will be handled + * by the sequencer. Note that there can be multiple + * bits set. + */ + if (status & SELTO) + { + unsigned char waiting; + + /* + * Hardware selection timer has expired. Turn + * off SCSI selection sequence. + */ + outb(ENRSELI, SCSISEQ + base); + 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, SCBARRAY + base); + + /* + * Shut off the offending interrupt sources, reset + * the sequencer address to zero and unpause it, + * then call the high-level SCSI completion routine. + * + * WARNING! This is a magic sequence! After many + * hours of guesswork, turning off the SCSI interrupts + * in CLRSINT? does NOT clear the SCSIINT bit in + * INTSTAT. By writing to the (undocumented, unused + * according to the AIC-7770 manual) third bit of + * CLRINT, you can clear INTSTAT. But, if you do it + * while the sequencer is paused, you get a BRKADRINT + * with an Illegal Host Address status, so the + * sequencer has to be restarted first. + */ + outb(CLRSELTIMEO, CLRSINT1 + base); + + outb(CLRSCSIINT, CLRINT + base); + + /* + * Shift the waiting for selection queue forward + */ + waiting = inb(WAITING_SCBH + base); + outb(waiting, SCBPTR + base); + waiting = inb(SCB_NEXT_WAITING + base); + outb(waiting, WAITING_SCBH + base); + + RESTART_SEQUENCER(p); + aic7xxx_done(p, scb); +#if 0 + printk("aic7xxx: SELTO SCB(%d) state(0x%x) cmd(0x%x).\n", + scb->position, scb->state, (unsigned int) scb->cmd); +#endif + } + else + { + if (!(status & BUSFREE)) + { + /* + * We don't know what's going on. Turn off the + * interrupt source and try to continue. + */ + printk("aic7xxx: SSTAT1(0x%x).\n", status); + outb(status, CLRSINT1 + base); + UNPAUSE_SEQUENCER(p); + outb(CLRSCSIINT, CLRINT + base); + } + } + } /* else */ + } + + if (intstat & CMDCMPLT) + { + int complete; + + /* + * 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); + + scb = &(p->scb_array[complete]); + if (!(scb->state & SCB_ACTIVE) || (scb->cmd == NULL)) + { + printk("aic7xxx: Warning - No command for SCB %d (CMDCMPLT).\n" + " QOUTCNT(%d) SCB state(0x%x) cmd(0x%x) pos(%d).\n", + complete, inb(QOUTFIFO + base), + scb->state, (unsigned int) scb->cmd, scb->position); + outb(CLRCMDINT, CLRINT + base); + continue; + } + cmd = scb->cmd; + + if ((cmd->flags & WAS_SENSE) && !(cmd->flags & ASKED_FOR_SENSE)) + { + /* + * Got sense information. + */ + cmd->flags &= ASKED_FOR_SENSE; + } +#if 0 + printk("aic7xxx: (complete) State(%d) cmd(0x%x) free(0x%x).\n", + scb->state, (unsigned int) scb->cmd, (unsigned int) p->free_scb); +#endif + + /* + * Clear interrupt status before checking + * the output queue again. This eliminates + * a race condition whereby a command could + * complete between the queue poll and the + * interrupt clearing, so notification of the + * command being complete never made it back + * up to the kernel. + */ + outb(CLRCMDINT, CLRINT + base); + aic7xxx_done(p, scb); +#if 0 + if (scb != &p->scb_array[scb->position]) + { + printk("aic7xxx: (complete) Address mismatch, pos(%d).\n", scb->position); + } + printk("aic7xxx: (complete) State(%d) cmd(0x%x) free(0x%x).\n", + scb->state, (unsigned int) scb->cmd, (unsigned int) p->free_scb); +#endif + +#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)) + { + 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 */ + + } while (inb(QOUTCNT + base)); + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_probe + * + * Description: + * Probing for EISA boards: it looks like the first two bytes + * are a manufacturer code - three characters, five bits each: + * + * BYTE 0 BYTE 1 BYTE 2 BYTE 3 + * ?1111122 22233333 PPPPPPPP RRRRRRRR + * + * The characters are baselined off ASCII '@', so add that value + * to each to get the real ASCII code for it. The next two bytes + * appear to be a product and revision number, probably vendor- + * specific. This is what is being searched for at each port, + * and what should probably correspond to the ID= field in the + * ECU's .cfg file for the card - if your card is not detected, + * make sure your signature is listed in the array. + * + * The fourth byte's lowest bit seems to be an enabled/disabled + * flag (rest of the bits are reserved?). + *-F*************************************************************************/ +static aha_type +aic7xxx_probe(int slot, int base) +{ + int i; + unsigned char buf[4]; + + static struct { + int n; + unsigned char signature[sizeof(buf)]; + aha_type type; + } AIC7xxx[] = { + { 4, { 0x04, 0x90, 0x77, 0x71 }, AIC_7771 }, /* host adapter 274x */ + { 4, { 0x04, 0x90, 0x77, 0x70 }, AIC_7770 }, /* motherboard 7770 */ + { 4, { 0x04, 0x90, 0x77, 0x56 }, AIC_284x }, /* 284x, BIOS enabled */ + { 4, { 0x04, 0x90, 0x77, 0x57 }, AIC_284x } /* 284x, BIOS disabled */ + }; + + /* + * The VL-bus cards need to be primed by + * writing before a signature check. + */ + for (i = 0; i < sizeof(buf); i++) + { + outb(0x80 + i, base); + buf[i] = inb(base + i); + } + + for (i = 0; i < NUMBER(AIC7xxx); i++) + { + /* + * Signature match on enabled card? + */ + if (!memcmp(buf, AIC7xxx[i].signature, AIC7xxx[i].n)) + { + if (inb(base + 4) & 1) + { + return (AIC7xxx[i].type); + } + + printk("aic7xxx: Disabled at slot %d, ignored.\n", slot); + } + } + + return (AIC_NONE); +} + +/*+F************************************************************************* + * Function: + * read_2840_seeprom + * + * Description: + * Reads the 2840 serial EEPROM and returns 1 if successful and 0 if + * not successful. + * + * See read_seeprom (for the 2940) for the instruction set of the 93C46 + * chip. + * + * The 2840 interface to the 93C46 serial EEPROM is through the + * STATUS_2840 and SEECTL_2840 registers. The CS_2840, CK_2840, and + * DO_2840 bits of the SEECTL_2840 register are connected to the chip + * select, clock, and data out lines respectively of the serial EEPROM. + * The DI_2840 bit of the STATUS_2840 is connected to the data in line + * of the serial EEPROM. The EEPROM_TF bit of STATUS_2840 register is + * useful in that it gives us an 800 nsec timer. After a read from the + * SEECTL_2840 register the timing flag is cleard and goes high 800 nsec + * later. + * + *-F*************************************************************************/ +static int +read_2840_seeprom(int base, struct seeprom_config *sc) +{ + int i = 0, k = 0; + unsigned char temp; + unsigned short checksum = 0; + unsigned short *seeprom = (unsigned short *) sc; + struct seeprom_cmd { + unsigned char len; + unsigned char bits[3]; + }; + struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; + +#define CLOCK_PULSE(p) \ + while ((inb(STATUS_2840 + base) & EEPROM_TF) == 0) \ + { \ + ; /* Do nothing */ \ + } \ + (void) inb(SEECTL_2840 + base); + + /* + * Read the first 32 registers of the seeprom. For the 2840, + * 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. + */ + for (k = 0; k < (sizeof(*sc) / 2); k++) + { + /* + * Send chip select for one clock cycle. + */ + outb(CK_2840 | CS_2840, SEECTL_2840 + base); + CLOCK_PULSE(base); + + /* + * Now we're ready to send the read command followed by the + * address of the 16-bit register we want to read. + */ + for (i = 0; i < seeprom_read.len; i++) + { + temp = CS_2840 | seeprom_read.bits[i]; + outb(temp, SEECTL_2840 + base); + CLOCK_PULSE(base); + temp = temp ^ CK_2840; + outb(temp, SEECTL_2840 + base); + CLOCK_PULSE(base); + } + /* + * Send the 6 bit address (MSB first, LSB last). + */ + for (i = 5; i >= 0; i--) + { + temp = k; + temp = (temp >> i) & 1; /* Mask out all but lower bit. */ + temp = CS_2840 | temp; + outb(temp, SEECTL_2840 + base); + CLOCK_PULSE(base); + temp = temp ^ CK_2840; + outb(temp, SEECTL_2840 + base); + CLOCK_PULSE(base); + } + + /* + * Now read the 16 bit register. An initial 0 precedes the + * register contents which begins with bit 15 (MSB) and ends + * with bit 0 (LSB). The initial 0 will be shifted off the + * top of our word as we let the loop run from 0 to 16. + */ + for (i = 0; i <= 16; i++) + { + temp = CS_2840; + outb(temp, SEECTL_2840 + base); + CLOCK_PULSE(base); + temp = temp ^ CK_2840; + seeprom[k] = (seeprom[k] << 1) | (inb(STATUS_2840 + base) & DI_2840); + outb(temp, SEECTL_2840 + base); + CLOCK_PULSE(base); + } + /* + * 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. + */ + if (k < (sizeof(*sc) / 2) - 1) + { + checksum = checksum + seeprom[k]; + } + + /* + * 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); + } + +#if 0 + printk("Computed checksum 0x%x, checksum read 0x%x\n", checksum, sc->checksum); + printk("Serial EEPROM:"); + for (k = 0; k < (sizeof(*sc) / 2); k++) + { + if (((k % 8) == 0) && (k != 0)) + { + printk("\n "); + } + printk(" 0x%x", seeprom[k]); + } + printk("\n"); +#endif + + if (checksum != sc->checksum) + { + printk("aic7xxx: SEEPROM checksum error, ignoring SEEPROM settings.\n"); + return (0); + } + + return (1); +#undef CLOCK_PULSE +} + +/*+F************************************************************************* + * Function: + * read_seeprom + * + * Description: + * Reads the serial EEPROM and returns 1 if successful and 0 if + * not successful. + * + * The instruction set of the 93C46 chip is as follows: + * + * Start OP + * Function Bit Code Address Data Description + * ------------------------------------------------------------------- + * READ 1 10 A5 - A0 Reads data stored in memory, + * starting at specified address + * EWEN 1 00 11XXXX Write enable must preceed + * all programming modes + * ERASE 1 11 A5 - A0 Erase register A5A4A3A2A1A0 + * WRITE 1 01 A5 - A0 D15 - D0 Writes register + * ERAL 1 00 10XXXX Erase all registers + * WRAL 1 00 01XXXX D15 - D0 Writes to all registers + * EWDS 1 00 00XXXX Disables all programming + * instructions + * *Note: A value of X for address is a don't care condition. + * + * The 93C46 has a four wire interface: clock, chip select, data in, and + * data out. In order to perform one of the above functions, you need + * to enable the chip select for a clock period (typically a minimum of + * 1 usec, with the clock high and low a minimum of 750 and 250 nsec + * respectively. While the chip select remains high, you can clock in + * the instructions (above) starting with the start bit, followed by the + * OP code, Address, and Data (if needed). For the READ instruction, the + * requested 16-bit register contents is read from the data out line but + * is preceded by an initial zero (leading 0, followed by 16-bits, MSB + * 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 + * 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, + * and data in lines respectively. The SEERDY bit of SEECTL is useful + * in that it gives us an 800 nsec timer. After a write to the SEECTL + * register, the SEERDY goes high 800 nsec later. The one exception + * 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) +{ + 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]; + }; + struct seeprom_cmd seeprom_read = {3, {1, 1, 0}}; + +#define CLOCK_PULSE(p) \ + while ((inb(SEECTL + base) & 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. + */ + 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) + { + 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. + */ + for (k = 0; k < (sizeof(*sc) / 2); k++) + { + /* + * Send chip select for one clock cycle. + */ + outb(SEEMS | SEECK | SEECS, SEECTL + base); + CLOCK_PULSE(base); + + /* + * Now we're ready to send the read command followed by the + * address of the 16-bit register we want to read. + */ + for (i = 0; i < seeprom_read.len; i++) + { + temp = SEEMS | SEECS | (seeprom_read.bits[i] << 1); + outb(temp, SEECTL + base); + CLOCK_PULSE(base); + temp = temp ^ SEECK; + outb(temp, SEECTL + base); + CLOCK_PULSE(base); + } + /* + * Send the 6 bit address (MSB first, LSB last). + */ + for (i = 5; 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); + temp = temp ^ SEECK; + outb(temp, SEECTL + base); + CLOCK_PULSE(base); + } + + /* + * Now read the 16 bit register. An initial 0 precedes the + * register contents which begins with bit 15 (MSB) and ends + * with bit 0 (LSB). The initial 0 will be shifted off the + * top of our word as we let the loop run from 0 to 16. + */ + for (i = 0; i <= 16; i++) + { + temp = SEEMS | SEECS; + outb(temp, SEECTL + base); + CLOCK_PULSE(base); + temp = temp ^ SEECK; + seeprom[k] = (seeprom[k] << 1) | (inb(SEECTL + base) & SEEDI); + outb(temp, SEECTL + base); + CLOCK_PULSE(base); + } + + /* + * 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. + */ + if (k < (sizeof(*sc) / 2) - 1) + { + checksum = checksum + seeprom[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); + } + + /* + * Release access to the memory port and the serial EEPROM. + */ + outb(0, SEECTL + base); + +#if 0 + printk("Computed checksum 0x%x, checksum read 0x%x\n", checksum, sc->checksum); + printk("Serial EEPROM:"); + for (k = 0; k < (sizeof(*sc) / 2); k++) + { + if (((k % 8) == 0) && (k != 0)) + { + printk("\n "); + } + printk(" 0x%x", seeprom[k]); + } + printk("\n"); +#endif + + if (checksum != sc->checksum) + { + printk("aic7xxx: SEEPROM checksum error, ignoring SEEPROM settings.\n"); + return (0); + } + + return (1); +#undef CLOCK_PULSE +} + +/*+F************************************************************************* + * Function: + * detect_maxscb + * + * Description: + * Return the maximum number of SCB's allowed for a given controller. + *-F*************************************************************************/ +static int +detect_maxscb(aha_type type, int base, int walk_scbs) +{ + unsigned char sblkctl_reg, scb_byte; + int maxscb = 0, i; + + switch (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. + * Until we know how to access more than 4 SCBs for the Rev E chips, + * we limit them, along with the Rev C chips, to 4 SCBs. + * + * 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. + */ + printk("aic7xxx: %s Rev E and subsequent.\n", board_names[type]); + outb(sblkctl_reg ^ AUTOFLUSHDIS, SBLKCTL + base); + maxscb = 4; + } + else + { + printk("aic7xxx: %s Rev C and previous.\n", board_names[type]); + maxscb = 4; + } + break; + + case AIC_7850: + maxscb = 3; + break; + + case AIC_7870: + case AIC_7871: + case AIC_7874: + case AIC_7880: + case AIC_7881: + case AIC_7884: + maxscb = 16; + break; + + case AIC_7872: + case AIC_7873: + case AIC_7882: + case AIC_7883: + /* + * Is suppose to have 255 SCBs, but we'll walk the SCBs + * looking for more if external RAM is detected. + */ + maxscb = 16; + break; + + case AIC_NONE: + /* + * This should never happen... But just in case. + */ + break; + } + + if (walk_scbs) + { + /* + * This adapter has external SCB memory. + * Walk the SCBs to determine how many there are. + */ + i = 0; + while (i < AIC7XXX_MAXSCB) + { + outb(i, SCBPTR + base); + scb_byte = ~(inb(SCBARRAY + base)); /* complement the byte */ + outb(scb_byte, SCBARRAY + base); /* write it back out */ + if (inb(SCBARRAY + base) != scb_byte) + { + break; + } + i++; + } + maxscb = i; + + printk("aic7xxx: Using %d SCB's after checking for SCB memory.\n", maxscb); + } + else + { + printk("aic7xxx: Using %d SCB's; No SCB memory check.\n", maxscb); + } + + return (maxscb); +} + +/*+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_config *config) +{ + int i; + unsigned char sblkctl; + int max_targets; + int found = 1, base; + int bios_disabled = FALSE; + unsigned char target_settings; + unsigned char scsi_conf, host_conf; + 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(MINREG + base, MAXREG - MINREG, "aic7xxx"); + + switch (config->type) + { + case AIC_7770: + case AIC_7771: + /* + * 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. + * + * Use the BIOS settings to determine the interrupt + * trigger type (level or edge) and use this value + * for pausing and unpausing the sequencer. + */ + config->unpause = (inb(HCNTRL + base) & IRQMS) | INTEN; + config->pause = config->unpause | PAUSE; + config->extended = aic7xxx_extended; + + outb(config->pause | CHIPRST, HCNTRL + base); + aic7xxx_delay(1); + if (inb(HCNTRL + base) & CHIPRST) + { + printk("aic7xxx: Chip reset not cleared; clearing manually.\n"); + } + outb(config->pause, HCNTRL + base); + + /* + * 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. + */ + config->irq = inb(INTDEF + base) & 0x0F; + if ((inb(HA_274_BIOSCTRL + base) & BIOSMODE) == BIOSDISABLED) + { + bios_disabled = TRUE; + } + host_conf = inb(HOSTCONF + base); + config->busrtime = host_conf & 0x3C; + /* XXX Is this valid for motherboard based controllers? */ + /* Setup the FIFO threshold and the bus off time */ + outb(host_conf & DFTHRSH, BUSSPD + base); + outb((host_conf << 2) & BOFF, BUSTIME + base); + + /* + * A reminder until this can be detected automatically. + */ + printk("aic7xxx: Extended translation %sabled.\n", + config->extended ? "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->extended = aic7xxx_extended; + config->irq = inb(INTDEF + base) & 0x0F; + if ((inb(HA_274_BIOSCTRL + base) & BIOSMODE) == BIOSDISABLED) + { + bios_disabled = TRUE; + } + host_conf = inb(HOSTCONF + base); + + printk("aic7xxx: Reading SEEPROM..."); + have_seeprom = read_2840_seeprom(base, &sc); + if (!have_seeprom) + { + printk("aic7xxx: Unable to read SEEPROM.\n"); + config->busrtime = host_conf & 0x3C; + } + else + { + printk("done.\n"); + config->extended = ((sc.bios_control & CF284XEXTEND) >> 5); + config->scsi_id = (sc.brtime_id & CFSCSIID); + 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. For now, we'll just assume + * that it's in the same place as for the 2940 card. + */ + config->high_term = (sc.adapter_control & CFWSTERM) ? + AIC_ENABLED : AIC_DISABLED; + config->busrtime = ((sc.brtime_id & CFBRTIME) >> 8); + } + /* XXX Is this valid for motherboard based controllers? */ + /* Setup the FIFO threshold and the bus off time */ + outb(host_conf & DFTHRSH, BUSSPD + base); + outb((host_conf << 2) & BOFF, BUSTIME + base); + + printk("aic7xxx: Extended translation %sabled.\n", + config->extended ? "en" : "dis"); + break; + + case AIC_7850: + 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: + outb(CHIPRST, HCNTRL + base); + config->unpause = UNPAUSE_294X; + config->pause = config->unpause | PAUSE; + aic7xxx_delay(1); + outb(config->pause, HCNTRL + base); + + config->extended = aic7xxx_extended; + config->scsi_id = 7; + + printk("aic7xxx: Reading SEEPROM..."); + have_seeprom = read_seeprom(base, config->chan_num * (sizeof(sc) / 2), &sc); + if (!have_seeprom) + { + printk("aic7xxx: Unable to read SEEPROM.\n"); + } + else + { + printk("done.\n"); + config->extended = ((sc.bios_control & CFEXTEND) >> 7); + 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_7882) || + (config->type == AIC_7883) || (config->type == AIC_7884)) && + (sc.adapter_control & CFULTRAEN)) + { + printk ("aic7xxx: Enabling support for Ultra SCSI speed.\n"); + config->ultra_enabled = TRUE; + } + } + + /* + * XXX - force data fifo threshold to 100%. Why does this + * need to be done? + * + * We don't know where this is set in the SEEPROM or by the BIOS, + * so we default it to 100%. + */ + outb(config->scsi_id | DFTHRSH_100, SCSICONF + base); + outb(DFTHRSH_100, DSPCISTATUS + base); + + /* + * In case we are a wide card, place scsi ID in second conf byte. + */ + outb(config->scsi_id, (SCSICONF + base + 1)); + + printk("aic7xxx: Extended translation %sabled.\n", + config->extended ? "en" : "dis"); + break; + + default: + panic("aic7xxx: (aic7xxx_register) Internal error.\n"); + } + + config->maxscb = detect_maxscb(config->type, base, config->walk_scbs); + + if (config->chip_type == AIC_777x) + { + if (config->pause & IRQMS) + { + printk("aic7xxx: Using level sensitive interrupts.\n"); + } + else + { + printk("aic7xxx: Using edge triggered interrupts.\n"); + } + } + + /* + * 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); + switch (sblkctl & SELBUS_MASK) + { + case SELNARROW: /* narrow/normal bus */ + config->scsi_id = inb(SCSICONF + base) & 0x07; + config->bus_type = AIC_SINGLE; + outb(SINGLE_BUS, FLAGS + base); + 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(WIDE_BUS, FLAGS + base); + 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("aic7xxx: Enabled channel B of %s-Twin.\n", + board_names[config->type]); + outb(TWIN_BUS, FLAGS + base); +#else + config->bus_type = AIC_SINGLE; + printk("aic7xxx: Channel B of %s-Twin will be ignored.\n", + board_names[config->type]); + outb(0, FLAGS + base); +#endif + break; + + default: + printk("aic7xxx: Unsupported type 0x%x, please " + "mail deang@ims.com\n", inb(SBLKCTL + base)); + outb(0, FLAGS + base); + return (0); + } + + /* + * For the 294x cards, clearing DIAGLEDEN and DIAGLEDON, will + * take the card out of diagnostic mode and make the host adatper + * LED follow bus activity (will not always be on). + */ + outb(sblkctl & ~(DIAGLEDEN | DIAGLEDON), SBLKCTL + base); + + /* + * 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("aic7xxx: Host adapter uses unsupported IRQ level, ignoring.\n"); + return (0); + } + + /* + * Check the IRQ to see if it is shared by another aic7xxx + * controller. If it is and sharing of IRQs is not defined, + * then return 0 hosts found. If sharing of IRQs is allowed + * or the IRQ is not shared by another host adapter, then + * proceed. + */ +#ifndef AIC7XXX_SHARE_IRQS + if (aic7xxx_boards[config->irq] != NULL) + { + printk("aic7xxx: Sharing of IRQ's is not configured.\n"); + return (0); + } +#endif + + /* + * 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); + + /* + * Before registry, make sure that the offsets of the + * struct scatterlist are what the sequencer will expect, + * otherwise disable scatter-gather altogether until someone + * can fix it. This is important since the sequencer will + * DMA elements of the SG array in while executing commands. + */ + if (template->sg_tablesize != SG_NONE) + { + struct scatterlist sg; + + if (SG_STRUCT_CHECK(sg)) + { + printk("aic7xxx: Warning - Kernel scatter-gather structures changed, " + "disabling it.\n"); + template->sg_tablesize = SG_NONE; + } + } + + /* + * 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->maxscb; + host->cmd_per_lun = AIC7XXX_CMDS_PER_LUN; + host->this_id = config->scsi_id; + host->irq = config->irq; + if (config->bus_type == AIC_WIDE) + { + host->max_id = 16; + } + if (config->bus_type == AIC_TWIN) + { + host->max_channel = 1; + } + + p = (struct aic7xxx_host *) host->hostdata; + + /* + * Initialize the scb array by setting the state to free. + */ + for (i = 0; i < AIC7XXX_MAXSCB; i++) + { + p->scb_array[i].state = SCB_FREE; + p->scb_array[i].next = NULL; + p->scb_array[i].cmd = NULL; + } + + p->isr_count = 0; + p->a_scanned = FALSE; + p->b_scanned = FALSE; + p->base = base; + p->maxscb = config->maxscb; + p->numscb = 0; + p->extended = config->extended; + p->type = config->type; + p->chip_type = config->chip_type; + p->ultra_enabled = config->ultra_enabled; + p->chan_num = config->chan_num; + p->bus_type = config->bus_type; + p->have_seeprom = have_seeprom; + p->seeprom = sc; + p->free_scb = NULL; + p->next = NULL; + + p->unpause = config->unpause; + p->pause = config->pause; + + if (aic7xxx_boards[config->irq] == NULL) + { + /* + * Warning! This must be done before requesting the irq. It is + * possible for some boards to raise an interrupt as soon as + * they are enabled. So when we request the irq from the Linux + * kernel, an interrupt is triggered immediately. Therefore, we + * must ensure the board data is correctly set before the request. + */ + aic7xxx_boards[config->irq] = host; + + /* + * Register IRQ with the kernel. + */ + if (request_irq(config->irq, aic7xxx_isr, SA_INTERRUPT, "aic7xxx")) + { + printk("aic7xxx: Couldn't register IRQ %d, ignoring.\n", config->irq); + aic7xxx_boards[config->irq] = NULL; + return (0); + } + } + else + { + /* + * We have found a host adapter sharing an IRQ of a previously + * 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("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); + } + + printk("done.\n"); + + /* + * Set the SCSI Id, SXFRCTL0, SXFRCTL1, and SIMODE1, for both channels + */ + if (p->bus_type == AIC_TWIN) + { + /* + * Select Channel B. + */ + 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); + outb(ENSELTIMO , SIMODE1 + base); + if (p->ultra_enabled) + { + outb(DFON | SPIOEN | ULTRAEN, SXFRCTL0 + base); + } + else + { + outb(DFON | SPIOEN, SXFRCTL0 + base); + } + + /* + * Select Channel A + */ + outb((sblkctl & ~SELBUS_MASK) | SELNARROW, SBLKCTL + base); + } + outb(config->scsi_id, SCSIID + base); + scsi_conf = inb(SCSICONF + base) & (ENSPCHK | STIMESEL); + outb(scsi_conf | ENSTIMER | ACTNEGEN | STPWEN, SXFRCTL1 + base); + outb(ENSELTIMO , SIMODE1 + base); + if (p->ultra_enabled) + { + outb(DFON | SPIOEN | ULTRAEN, SXFRCTL0 + base); + } + else + { + outb(DFON | SPIOEN, SXFRCTL0 + base); + } + + /* + * Look at the information that board initialization or the board + * BIOS has left us. In the lower four bits of each target's + * scratch space any value other than 0 indicates that we should + * initiate synchronous transfers. If it's zero, the user or the + * BIOS has decided to disable synchronous negotiation to that + * target so we don't activate the needsdtr flag. + */ + p->needsdtr_copy = 0x0; + p->sdtr_pending = 0x0; + p->needwdtr_copy = 0x0; + p->wdtr_pending = 0x0; + if (p->bus_type == AIC_SINGLE) + { + max_targets = 8; + } + else + { + max_targets = 16; + } + + /* + * Grab the disconnection disable table and invert it for our needs + */ + if (have_seeprom) + { + p->discenable = 0x0; + } + else + { + if (bios_disabled) + { + printk("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)); + } + } + + for (i = 0; i < max_targets; i++) + { + if (have_seeprom) + { + 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); + } + } + else + { + if (bios_disabled) + { + target_settings = 0; /* 10 MHz */ + p->needsdtr_copy |= (0x01 << i); + p->needwdtr_copy |= (0x01 << i); + } + else + { + 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; + } + } + } + outb(target_settings, (TARG_SCRATCH + base + i)); + } + + /* + * If we are not wide, forget WDTR. This makes the driver + * work on some cards that don't leave these fields cleared + * when BIOS is not installed. + */ + if (p->bus_type != AIC_WIDE) + { + p->needwdtr = 0; + } + p->needsdtr = p->needsdtr_copy; + p->needwdtr = p->needwdtr_copy; +#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 + + /* + * Clear the control byte for every SCB so that the sequencer + * doesn't get confused and think that one of them is valid + */ + for (i = 0; i < config->maxscb; i++) + { + outb(i, SCBPTR + base); + outb(0, SCBARRAY + base); + } + + /* + * For reconnecting targets, the sequencer code needs to + * know how many SCBs it has to search through. + */ + outb(config->maxscb, SCBCOUNT + base); + + /* + * 2s compliment of SCBCOUNT + */ + i = p->maxscb; + outb(-i & 0xff, COMP_SCBCOUNT + base); + + /* + * Clear the active flags - no targets are busy. + */ + outb(0, ACTIVE_A + base); + outb(0, ACTIVE_B + base); + + /* + * We don't have any waiting selections + */ + outb(SCB_LIST_NULL, WAITING_SCBH + base); + outb(SCB_LIST_NULL, WAITING_SCBT + base); + + /* + * 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. + */ + + if (!aic7xxx_no_reset) + { + printk("aic7xxx: Resetting the SCSI bus..."); + if (p->bus_type == AIC_TWIN) + { + /* + * Select Channel B. + */ + outb((sblkctl & ~SELBUS_MASK) | SELBUSB, SBLKCTL + base); + + outb(SCSIRSTO, SCSISEQ + base); + udelay(1000); + outb(0, SCSISEQ + base); + + /* + * Select Channel A. + */ + outb((sblkctl & ~SELBUS_MASK) | SELNARROW, SBLKCTL + base); + } + + outb(SCSIRSTO, SCSISEQ + base); + udelay(1000); + outb(0, SCSISEQ + base); + + aic7xxx_delay(AIC7XXX_RESET_DELAY); + + printk("done.\n"); + } + + /* + * 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); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_detect + * + * Description: + * Try to detect and register an Adaptec 7770 or 7870 SCSI controller. + *-F*************************************************************************/ +int +aic7xxx_detect(Scsi_Host_Template *template) +{ + int found = 0, slot, base; + unsigned char irq = 0; + int i; + struct aic7xxx_host_config config; + + template->proc_dir = &proc_scsi_aic7xxx; + config.chan_num = 0; + + /* + * Since we may allow sharing of IRQs, it is imperative + * that we "null-out" the aic7xxx_boards array. It is + * not guaranteed to be initialized to 0 (NULL). We use + * a NULL entry to indicate that no prior hosts have + * been found/registered for that IRQ. + */ + for (i = 0; i <= MAXIRQ; i++) + { + aic7xxx_boards[i] = NULL; + } + + /* + * Initialize the spurious count to 0. + */ + aic7xxx_spurious_count = 0; + + /* + * EISA/VL-bus card signature probe. + */ + for (slot = MINSLOT; slot <= MAXSLOT; slot++) + { + base = SLOTBASE(slot) + MINREG; + + if (check_region(MINREG + base, MAXREG - MINREG)) + { + /* + * Some other driver has staked a + * claim to this i/o region already. + */ + continue; + } + + config.type = aic7xxx_probe(slot, HID0 + base); + if (config.type != AIC_NONE) + { + /* + * 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. + */ + config.chip_type = AIC_777x; + config.base = base; + config.irq = irq; + config.parity = AIC_UNKNOWN; + config.low_term = AIC_UNKNOWN; + config.high_term = AIC_UNKNOWN; + config.busrtime = 0; + config.walk_scbs = FALSE; + config.ultra_enabled = FALSE; + found += aic7xxx_register(template, &config); + + /* + * Disallow spurious interrupts. + */ + aic7xxx_spurious_count = 0; + } + } + +#ifdef CONFIG_PCI + /* + * PCI-bus probe. + */ + if (pcibios_present()) + { + struct + { + unsigned short vendor_id; + unsigned short device_id; + aha_type card_type; + aha_chip_type chip_type; + } 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_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}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7873, AIC_7873, AIC_787x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7874, AIC_7874, AIC_787x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7880, AIC_7880, AIC_788x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7881, AIC_7881, AIC_788x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7882, AIC_7882, AIC_788x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7883, AIC_7883, AIC_788x}, + {PCI_VENDOR_ID_ADAPTEC, PCI_DEVICE_ID_ADAPTEC_7884, AIC_7884, AIC_788x} + }; + + int error; + int done = 0; + unsigned int io_port; + unsigned short index = 0; + unsigned char pci_bus, pci_device_fn; + unsigned int csize_lattime; + unsigned int class_revid; + unsigned int devconfig; + char rev_id[] = {'B', 'C', 'D'}; + + for (i = 0; i < NUMBER(aic7xxx_pci_devices); i++) + { + done = FALSE; + while (!done) + { + if (pcibios_find_device(aic7xxx_pci_devices[i].vendor_id, + aic7xxx_pci_devices[i].device_id, + index, &pci_bus, &pci_device_fn)) + { + done = TRUE; + } + 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.walk_scbs = FALSE; + switch (config.type) + { + case AIC_7872: /* 3940 */ + case AIC_7882: /* 3940-Ultra */ + config.walk_scbs = TRUE; + config.chan_num = number_of_39xxs & 0x1; /* Has 2 controllers */ + number_of_39xxs++; + if (number_of_39xxs == 2) + { + number_of_39xxs = 0; /* To be consistent with 3985. */ + } + break; + + case AIC_7873: /* 3985 */ + case AIC_7883: /* 3985-Ultra */ + config.chan_num = number_of_39xxs & 0x3; /* Has 3 controllers */ + number_of_39xxs++; + if (number_of_39xxs == 3) + { + number_of_39xxs = 0; + } + break; + + default: + break; + } + + /* + * Read esundry information from PCI BIOS. + */ + error = pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &io_port); + error += pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &irq); + + /* + * Ensure that we are using good values for the PCI burst size + * and latency timer. + */ + error += pcibios_read_config_dword(pci_bus, pci_device_fn, + CSIZE_LATTIME, &csize_lattime); + if ((csize_lattime & CACHESIZE) == 0) + { + /* Default to 8DWDs - what's the PCI define for this? */ + csize_lattime |= 8; + } + if((csize_lattime & LATTIME) == 0) + { + /* Default to 64 PCLKS (is this a good value?) */ + /* This may also be availble in the SEEPROM?? */ + csize_lattime |= (64 << 8); + } + pcibios_write_config_dword(pci_bus, pci_device_fn, + CSIZE_LATTIME, csize_lattime); + printk("aic7xxx: BurstLen = %d DWDs, Latency Timer = %d PCLKS\n", + (int) (csize_lattime & CACHESIZE), + (csize_lattime >> 8) & 0x000000ff); + + error += pcibios_read_config_dword(pci_bus, pci_device_fn, + CLASS_PROGIF_REVID, &class_revid); + if ((class_revid & DEVREVID) < 3) + { + printk("aic7xxx: %s Rev %c.\n", board_names[config.type], + rev_id[class_revid & DEVREVID]); + } + + error += pcibios_read_config_dword(pci_bus, pci_device_fn, + DEVCONFIG, &devconfig); + if (error) + { + panic("aic7xxx: (aic7xxx_detect) Error %d reading PCI registers.\n", + error); + } + + printk("aic7xxx: devconfig = 0x%x.\n", devconfig); + + /* + * The first bit of PCI_BASE_ADDRESS_0 is always set, so + * we mask it off. + */ + base = io_port & 0xfffffffe; + + /* + * I don't think we need to bother with allowing + * spurious interrupts for the 787x/7850, but what + * the hey. + */ + aic7xxx_spurious_count = 1; + + config.base = base; + config.irq = irq; + config.parity = AIC_UNKNOWN; + config.low_term = AIC_UNKNOWN; + config.high_term = AIC_UNKNOWN; + config.busrtime = 0; + config.ultra_enabled = FALSE; + if (devconfig & RAMPSM) + { + /* + * External SRAM present. Have the probe 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. + * + * 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. + */ + printk ("aic7xxx: External RAM detected. Enabling RAM access.\n"); + devconfig &= ~(RAMPSM | SCBRAMSEL); + pcibios_write_config_dword(pci_bus, pci_device_fn, + DEVCONFIG, devconfig); + config.walk_scbs = TRUE; + } + found += aic7xxx_register(template, &config); + + /* + * Disable spurious interrupts. + */ + aic7xxx_spurious_count = 0; + + index++; + } /* Found an Adaptec PCI device. */ + } + } + } +#endif CONFIG_PCI + + template->name = aic7xxx_info(NULL); + return (found); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_buildscb + * + * Description: + * Build a SCB. + *-F*************************************************************************/ +static void +aic7xxx_buildscb(struct aic7xxx_host *p, + Scsi_Cmnd *cmd, + struct aic7xxx_scb *scb) +{ + void *addr; + unsigned short mask; + struct scatterlist *sg; + + /* + * Setup the control byte if we need negotiation and have not + * already requested it. + */ +#ifdef AIC7XXX_TAGGED_QUEUEING + if (cmd->device->tagged_supported) + { + if (cmd->device->tagged_queue == 0) + { + printk("aic7xxx: Enabling tagged queuing for target %d, " + "channel %d.\n", cmd->target, cmd->channel); + cmd->device->tagged_queue = 1; + cmd->device->current_tag = 1; /* enable tagging */ + } + cmd->tag = cmd->device->current_tag; + cmd->device->current_tag++; + scb->control |= TAG_ENB; + } +#endif + mask = (0x01 << (cmd->target | (cmd->channel << 3))); + if (p->discenable & mask) + { + scb->control |= DISCENB; + } + if ((p->needwdtr & mask) && !(p->wdtr_pending & mask)) + { + p->wdtr_pending |= mask; + scb->control |= NEEDWDTR; +#if 0 + printk("aic7xxx: Sending WDTR request to target %d.\n", cmd->target); +#endif + } + else + { + if ((p->needsdtr & mask) && !(p->sdtr_pending & mask)) + { + p->sdtr_pending |= mask; + scb->control |= NEEDSDTR; +#if 0 + printk("aic7xxx: Sending SDTR request to target %d.\n", 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) | + ((cmd->channel & 0x01) << 3) | (cmd->lun & 0x07); + + /* + * The interpretation of request_buffer and request_bufflen + * changes depending on whether or not use_sg is zero; a + * non-zero use_sg indicates the number of elements in the + * scatter-gather array. + */ + + /* + * XXX - this relies on the host data being stored in a + * little-endian format. + */ + addr = cmd->cmnd; + scb->SCSI_cmd_length = cmd->cmd_len; + memcpy(scb->SCSI_cmd_pointer, &addr, sizeof(scb->SCSI_cmd_pointer)); + + if (cmd->use_sg) + { + scb->SG_segment_count = cmd->use_sg; + memcpy(scb->SG_list_pointer, &cmd->request_buffer, + sizeof(scb->SG_list_pointer)); + memcpy(&sg, &cmd->request_buffer, sizeof(sg)); + memcpy(scb->data_pointer, &(sg[0].address), sizeof(scb->data_pointer)); + scb->data_count = sg[0].length; +#if 0 + debug("aic7xxx: (build_scb) SG segs(%d), length(%u), sg[0].length(%d).\n", + cmd->use_sg, aic7xxx_length(cmd, 0), scb->data_count); +#endif + } + else + { +#if 0 + debug("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) + { + /* + * 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; + } + else + { + scb->SG_segment_count = 1; + scb->sg.address = (char *) cmd->request_buffer; + scb->sg.length = cmd->request_bufflen; + addr = &scb->sg; + memcpy(scb->SG_list_pointer, &addr, sizeof(scb->SG_list_pointer)); + scb->data_count = scb->sg.length; + memcpy(scb->data_pointer, &cmd->request_buffer, sizeof(scb->data_pointer)); + } + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_queue + * + * Description: + * Queue a SCB to the controller. + *-F*************************************************************************/ +int +aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *)) +{ + long flags; + struct aic7xxx_host *p; + struct aic7xxx_scb *scb; + + p = (struct aic7xxx_host *) cmd->host->hostdata; + + /* + * Check to see if channel was scanned. + */ + if (!p->a_scanned && (cmd->channel == 0)) + { + printk("aic7xxx: Scanning channel A for devices.\n"); + p->a_scanned = TRUE; + } + else + { + if (!p->b_scanned && (cmd->channel == 1)) + { + printk("aic7xxx: Scanning channel B for devices.\n"); + p->b_scanned = TRUE; + } + } + +#if 0 + debug("aic7xxx: (queue) cmd(0x%x) size(%u), target %d, channel %d, lun %d.\n", + cmd->cmnd[0], cmd->cmd_len, cmd->target, cmd->channel, + 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. Since the kernel documentation is vague on + * whether or not we are in a cli/sti pair already, save + * the flags to be on the safe side. + */ + save_flags(flags); + cli(); + + /* + * Find a free slot in the SCB array to load this command + * into. Since can_queue is set to the maximum number of + * SCBs for the card, we should always find one. + * + * First try to find an scb in the free list. If there are + * none in the free list, then check the current number of + * of scbs and take an unused one from the scb array. + */ + scb = p->free_scb; + if (scb != NULL) + { /* found one in the free list */ + p->free_scb = scb->next; /* remove and update head of list */ + /* + * Warning! For some unknown reason, the scb at the head + * of the free list is not the same address that it should + * be. That's why we set the scb pointer taken by the + * position in the array. The scb at the head of the list + * should match this address, but it doesn't. + */ + scb = &(p->scb_array[scb->position]); + scb->control = 0; + scb->state = SCB_ACTIVE; + } + else + { + if (p->numscb >= p->maxscb) + { + panic("aic7xxx: (aic7xxx_queue) Couldn't find a free SCB.\n"); + } + else + { + /* + * Initialize the scb within the scb array. The + * position within the array is the position on + * the board that it will be loaded. + */ + scb = &(p->scb_array[p->numscb]); + memset(scb, 0, sizeof(*scb)); + + scb->position = p->numscb; + p->numscb++; + scb->state = SCB_ACTIVE; + } + } + + scb->cmd = cmd; + aic7xxx_position(cmd) = scb->position; +#if 0 + debug_scb(scb); +#endif; + + /* + * Construct the SCB beforehand, so the sequencer is + * paused a minimal amount of time. + */ + aic7xxx_buildscb(p, cmd, scb); + +#if 0 + if (scb != &p->scb_array[scb->position]) + { + 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); +#endif + /* + * 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); + + /* + * 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. + */ + aic7xxx_putscb(p, scb); + outb(scb->position, QINFIFO + p->base); + + /* + * Make sure the Scsi_Cmnd pointer is saved, the struct it + * points to is set up properly, and the parity error flag + * is reset, then unpause the sequencer and watch the fun + * begin. + */ + cmd->scsi_done = fn; + aic7xxx_error(cmd) = DID_OK; + aic7xxx_status(cmd) = 0; + cmd->result = 0; + memset(&cmd->sense_buffer, 0, sizeof(cmd->sense_buffer)); + + UNPAUSE_SEQUENCER(p); +#if 0 + printk("aic7xxx: (queue) After - cmd(0x%lx) scb->cmd(0x%lx) pos(%d).\n", + (long) cmd, (long) scb->cmd, scb->position); +#endif; + restore_flags(flags); + return (0); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_abort_scb + * + * Description: + * Abort an scb. If the scb has not previously been aborted, then + * we attempt to send a BUS_DEVICE_RESET message to the target. If + * the scb has previously been unsuccessfully aborted, then we will + * reset the channel and have all devices renegotiate. Returns an + * enumerated type that indicates the status of the operation. + *-F*************************************************************************/ +static aha_abort_reset_type +aic7xxx_abort_scb(struct aic7xxx_host *p, struct aic7xxx_scb *scb, + unsigned char errcode) +{ + int base = p->base; + int found = FALSE; + aha_abort_reset_type scb_status = ABORT_RESET_SUCCESS; + char channel = scb->target_channel_lun & SELBUSB ? 'B': 'A'; + + /* + * Ensure that the card doesn't do anything + * behind our back. + */ + PAUSE_SEQUENCER(p); + +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_scb) scb %d, scb_aborted 0x%x\n", + scb->position, (scb->state & SCB_ABORTED)); +#endif + /* + * 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, + * 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) + { + /* + * Been down this road before. Do a full bus reset. + */ + found = aic7xxx_reset_channel(p, channel, scb->position); + } + else + { + unsigned char active_scb, control; + struct aic7xxx_scb *active_scbp; + + /* + * 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[active_scb]); + control = inb(SCBARRAY + base); + + /* + * Test to see if scbp is disconnected + */ + outb(scb->position, SCBPTR + base); + if (inb(SCBARRAY + base) & DISCONNECTED) + { +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_scb) scb %d is disconnected.\n", scb->position); +#endif + scb->state |= (SCB_DEVICE_RESET | SCB_ABORTED); + 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_error(scb->cmd) = errcode; + scb_status = ABORT_RESET_PENDING; + aic7xxx_add_waiting_scb(base, scb, LIST_SECOND); + UNPAUSE_SEQUENCER(p); + } + else + { + /* + * Is the active SCB really active? + */ + if (active_scbp->state & SCB_ACTIVE) + { + unsigned char msg_len = inb(MSG_LEN + base); + if (msg_len != 0) + { +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_scb) scb is active, needs DMA, " + "msg_len is non-zero.\n"); +#endif + /* + * If we're in a message phase, tacking on another message + * may confuse the target totally. The bus is probably wedged, + * so reset the channel. + */ + channel = (active_scbp->target_channel_lun & SELBUSB) ? 'B': 'A'; + aic7xxx_reset_channel(p, channel, scb->position); + } + else + { +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_scb) scb is active, needs DMA, " + "msg_len is zero.\n"); +#endif + /* + * 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); + if (active_scbp->target_channel_lun != scb->target_channel_lun) + { + /* + * 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) = errcode; + scb_status = ABORT_RESET_PENDING; + UNPAUSE_SEQUENCER(p); + } + } + else + { +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_scb) no active command.\n"); +#endif + /* + * No active command to single out, so reset + * the bus for the timed out target. + */ + aic7xxx_reset_channel(p, channel, scb->position); + } + } + } + return (scb_status); +} + +/*+F************************************************************************* + * Function: + * aic7xxx_abort_reset + * + * Description: + * Abort or reset the current SCSI command(s). Returns an enumerated + * type that indicates the status of the operation. + *-F*************************************************************************/ +static aha_abort_reset_type +aic7xxx_abort_reset(Scsi_Cmnd *cmd, unsigned char errcode) +{ + struct aic7xxx_scb *scb; + struct aic7xxx_host *p; + long flags; + aha_abort_reset_type scb_status = ABORT_RESET_SUCCESS; + + p = (struct aic7xxx_host *) cmd->host->hostdata; + scb = &(p->scb_array[aic7xxx_position(cmd)]); + + save_flags(flags); + cli(); + +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_reset) scb state 0x%x\n", scb->state); +#endif + + if (scb->state & SCB_ACTIVE) + { + if (scb->state & SCB_IMMED) + { + /* + * Don't know how set the number of retries to 0. + */ + /* cmd->retries = 0; */ + aic7xxx_error(cmd) = errcode; + aic7xxx_done(p, scb); + } + else + { + /* + * Abort the operation. + */ + scb_status = aic7xxx_abort_scb(p, scb, errcode); + } + } + else + { + /* + * The scb is not active and must have completed after the timeout + * check in scsi.c and before we check the scb state above. For + * this case we return SCSI_ABORT_NOT_RUNNING (if abort was called) + * or SCSI_RESET_SUCCESS (if reset was called). + */ +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort_reset) called with no active scb, errcode 0x%x\n", + errcode); +#endif + scb_status = ABORT_RESET_INACTIVE; + /* + * According to the comments in scsi.h and Michael Neuffer, if we do not + * have an active command for abort or reset, we should not call the + * command done function. Unfortunately, this hangs the system for me + * unless we *do* call the done function. + * + * XXX - Revisit this sometime! + */ + cmd->result = errcode << 16; + cmd->scsi_done(cmd); + } + restore_flags(flags); + return (scb_status); +} + + +/*+F************************************************************************* + * Function: + * aic7xxx_abort + * + * Description: + * Abort the current SCSI command(s). + *-F*************************************************************************/ +int +aic7xxx_abort(Scsi_Cmnd *cmd) +{ +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (abort) target/channel %d/%d\n", cmd->target, cmd->channel); +#endif + + switch (aic7xxx_abort_reset(cmd, DID_ABORT)) + { + case ABORT_RESET_INACTIVE: + return (SCSI_ABORT_NOT_RUNNING); + break; + case ABORT_RESET_PENDING: + return (SCSI_ABORT_PENDING); + break; + case ABORT_RESET_SUCCESS: + default: + return (SCSI_ABORT_SUCCESS); + break; + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_reset + * + * Description: + * Resetting the bus always succeeds - is has to, otherwise the + * kernel will panic! Try a surgical technique - sending a BUS + * DEVICE RESET message - on the offending target before pulling + * the SCSI bus reset line. + *-F*************************************************************************/ +int +aic7xxx_reset(Scsi_Cmnd *cmd) +{ +#ifdef AIC7XXX_DEBUG_ABORT + printk ("aic7xxx: (reset) target/channel %d/%d\n", cmd->target, cmd->channel); +#endif + + switch (aic7xxx_abort_reset(cmd, DID_RESET)) + { + case ABORT_RESET_PENDING: + return (SCSI_RESET_PENDING); + break; + case ABORT_RESET_INACTIVE: + case ABORT_RESET_SUCCESS: + default: + return (SCSI_RESET_SUCCESS); + break; + } +} + +/*+F************************************************************************* + * Function: + * aic7xxx_biosparam + * + * Description: + * Return the disk geometry for the given SCSI device. + *-F*************************************************************************/ +int +aic7xxx_biosparam(Disk *disk, kdev_t dev, int geom[]) +{ + int heads, sectors, cylinders; + struct aic7xxx_host *p; + + p = (struct aic7xxx_host *) disk->device->host->hostdata; + + /* + * XXX - if I could portably find the card's configuration + * information, then this could be autodetected instead + * of left to a boot-time switch. + */ + heads = 64; + sectors = 32; + cylinders = disk->capacity / (heads * sectors); + + if (p->extended && cylinders > 1024) + { + heads = 255; + sectors = 63; + cylinders = disk->capacity / (255 * 63); + } + + geom[0] = heads; + geom[1] = sectors; + geom[2] = cylinders; + + return (0); +} + +#ifdef MACH +#include "aic7xxx_proc.src" +#else +#include "aic7xxx_proc.c" +#endif + +#ifdef MODULE +/* Eventually this will go into an include file, but this will be later */ +Scsi_Host_Template driver_template = AIC7XXX; + +#include "scsi_module.c" +#endif + +/* + * Overrides for Emacs so that we almost follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 2 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -2 + * c-argdecl-indent: 2 + * c-label-offset: -2 + * c-continued-statement-offset: 2 + * c-continued-brace-offset: 0 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ + |