patch-1.3.5 linux/drivers/scsi/aic7xxx.c
Next file: linux/drivers/scsi/aic7xxx.h
Previous file: linux/drivers/scsi/aic7770.c
Back to the patch index
Back to the overall index
- Lines: 5383
- Date:
Thu Jun 29 11:19:10 1995
- Orig file:
v1.3.4/linux/drivers/scsi/aic7xxx.c
- Orig date:
Tue Jun 27 14:11:37 1995
diff -u --recursive --new-file v1.3.4/linux/drivers/scsi/aic7xxx.c linux/drivers/scsi/aic7xxx.c
@@ -1,44 +1,67 @@
-/*
- * @(#)aic7xxx.c 1.34 94/11/30 jda
+/*+M*************************************************************************
+ * Adaptec 274x/284x/294x device driver for Linux.
*
- * Adaptec 274x/284x/294x device driver for Linux.
- * Copyright (c) 1994 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 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 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), ...
- *
- * On a twin-bus adapter card, channel B is ignored. Rationale:
- * it would greatly complicate the sequencer and host driver code,
- * and both busses are multiplexed on to the EISA bus anyway. So
- * I don't really see any technical advantage to supporting both.
- *
- * As well, multiple adapter card using the same IRQ level are
- * not supported. It doesn't make sense to configure the cards
- * this way from a performance standpoint. Not to mention that
- * the kernel would have to support two devices per registered IRQ.
- */
+ * Copyright (c) 1994 John Aycock
+ * The University of Calgary Department of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of Calgary
+ * Department of Computer Science and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * 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.49 1995/06/28 05:41:09 deang Exp $
+ *-M*************************************************************************/
+
+#ifdef MODULE
+#include <linux/module.h>
+#endif
#include <stdarg.h>
#include <asm/io.h>
@@ -47,1669 +70,3832 @@
#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 "../block/blk.h"
#include "sd.h"
#include "scsi.h"
#include "hosts.h"
#include "aic7xxx.h"
+#define AIC7XXX_C_VERSION "$Revision: 1.49 $"
+
+#define NUMBER(arr) (sizeof(arr) / sizeof(arr[0]))
+#define MIN(a,b) ((a < b) ? a : b)
+
/*
- * There should be a specific return value for this in scsi.h, but
- * it seems that most drivers ignore it.
+ * Defines for PCI bus support, testing twin bus support, DMAing of
+ * SCBs, and tagged queueing.
+ *
+ * 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 and do not wish to configure your kernel with PCI
+ * support, then make sure this define is set to the cprrect
+ * define for PCI support (CONFIG_PCI) and configure your kernel
+ * without PCI support (make config).
+ *
+ * o Twin bus support - this has been tested and does work.
+ *
+ * o DMAing of SCBs - thanks to Kai Makisara, this now works
+ *
+ * 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.
+ *
+ * Daniel M. Eischen, deischen@iworks.InterWorks.org, 03/11/95
*/
-#define DID_UNDERFLOW DID_ERROR
-/* EISA/VL-bus stuff */
+/* Uncomment this for testing twin bus support. */
+#define AIC7XXX_TWIN_SUPPORT
-#define MINSLOT 1
-#define MAXSLOT 15
-#define SLOTBASE(x) ((x) << 12)
+/* Uncomment this for DMAing of SCBs. */
+#define AIC7XXX_USE_DMA
-#define MAXIRQ 15
+/* Uncomment this for tagged queueing. */
+/* #define AIC7XXX_TAGGED_QUEUEING */
-/* AIC-7770 offset definitions */
+/* Uncomment this for allowing sharing of IRQs. */
+#define AIC7XXX_SHARE_IRQS
-#define O_MINREG(x) ((x) + 0xc00) /* i/o range to reserve */
-#define O_MAXREG(x) ((x) + 0xcbf)
+/* Set this to the delay in seconds after SCSI bus reset. */
+#define AIC7XXX_RESET_DELAY 15
-#define O_SCSISEQ(x) ((x) + 0xc00) /* scsi sequence control */
-#define O_SCSISIGI(x) ((x) + 0xc03) /* scsi control signal read */
-#define O_SCSISIGO(x) ((x) + 0xc03) /* scsi control signal write */
-#define O_SCSIID(x) ((x) + 0xc05) /* scsi id */
-#define O_SSTAT0(x) ((x) + 0xc0b) /* scsi status register 0 */
-#define O_CLRSINT1(x) ((x) + 0xc0c) /* clear scsi interrupt 1 */
-#define O_SSTAT1(x) ((x) + 0xc0c) /* scsi status register 1 */
-#define O_SELID(x) ((x) + 0xc19) /* [re]selection id */
-#define O_SBLKCTL(x) ((x) + 0xc1f) /* scsi block control */
-#define O_SEQCTL(x) ((x) + 0xc60) /* sequencer control */
-#define O_SEQRAM(x) ((x) + 0xc61) /* sequencer ram data */
-#define O_SEQADDR(x) ((x) + 0xc62) /* sequencer address (W) */
-#define O_BIDx(x) ((x) + 0xc80) /* board id */
-#define O_BCTL(x) ((x) + 0xc84) /* board control */
-#define O_HCNTRL(x) ((x) + 0xc87) /* host control */
-#define O_SCBPTR(x) ((x) + 0xc90) /* scb pointer */
-#define O_INTSTAT(x) ((x) + 0xc91) /* interrupt status */
-#define O_ERROR(x) ((x) + 0xc92) /* hard error */
-#define O_CLRINT(x) ((x) + 0xc92) /* clear interrupt status */
-#define O_SCBCNT(x) ((x) + 0xc9a) /* scb auto increment */
-#define O_QINFIFO(x) ((x) + 0xc9b) /* queue in fifo */
-#define O_QINCNT(x) ((x) + 0xc9c) /* queue in count */
-#define O_QOUTFIFO(x) ((x) + 0xc9d) /* queue out fifo */
-#define O_QOUTCNT(x) ((x) + 0xc9e) /* queue out count */
-#define O_SCBARRAY(x) ((x) + 0xca0) /* scb array start */
-
-/* AIC-7870-only definitions */
-
-#define O_DSPCISTATUS(x) ((x) + 0xc86) /* ??? */
-
-/* host adapter offset definitions */
-
-#define HA_REJBYTE(x) ((x) + 0xc31) /* 1st message in byte */
-#define HA_MSG_FLAGS(x) ((x) + 0xc35) /* outgoing message flag */
-#define HA_MSG_LEN(x) ((x) + 0xc36) /* outgoing message length */
-#define HA_MSG_START(x) ((x) + 0xc37) /* outgoing message body */
-#define HA_ARG_1(x) ((x) + 0xc4c) /* sdtr <-> rate parameters */
-#define HA_ARG_2(x) ((x) + 0xc4d)
-#define HA_RETURN_1(x) ((x) + 0xc4c)
-#define HA_RETURN_2(x) ((x) + 0xc4d)
-#define HA_SIGSTATE(x) ((x) + 0xc4e) /* value in SCSISIGO */
-#define HA_NEEDSDTR(x) ((x) + 0xc4f) /* synchronous negotiation? */
-#define HA_SCBCOUNT(x) ((x) + 0xc56) /* number of hardware SCBs */
-
-#define HA_SCSICONF(x) ((x) + 0xc5a) /* SCSI config register */
-#define HA_INTDEF(x) ((x) + 0xc5c) /* interrupt def'n register */
-#define HA_HOSTCONF(x) ((x) + 0xc5d) /* host config def'n register */
+/*
+ * Uncomment this to always use scatter/gather lists.
+ * *NOTE: The sequencer must be changed also!
+ */
+#define AIC7XXX_USE_SG
-/* debugging code */
+/*
+ * Controller type and options
+ */
+typedef enum {
+ AIC_NONE,
+ AIC_274x, /* EISA aic7770 */
+ AIC_284x, /* VLB aic7770 */
+ AIC_7870, /* PCI aic7870 */
+ AIC_7850, /* PCI aic7850 */
+ AIC_7872 /* PCI aic7870 on 394x */
+} aha_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;
/*
-#define AIC7XXX_DEBUG
-*/
+ * 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
/*
- * If a parity error occurs during a data transfer phase, run the
- * command 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 a DID_PARITY one for the higher-level SCSI
- * code.
+ * 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 aic7xxx_parity(cmd) ((cmd)->SCp.Status)
+#define DID_RETRY_COMMAND DID_BUS_BUSY
/*
- * 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.
+ * EISA/VL-bus stuff
*/
-#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)
+#define MINSLOT 1
+#define MAXSLOT 15
+#define SLOTBASE(x) ((x) << 12)
+#define MAXIRQ 15
/*
- * "Static" structures. Note that these are NOT initialized
- * to zero inside the kernel - we have to initialize them all
- * explicitly.
- *
- * We support a maximum of one adapter card per IRQ level (see the
- * rationale for this above). On an interrupt, use the IRQ as an
- * index into aic7xxx_boards[] to locate the card information.
+ * Standard EISA Host ID regs (Offset from slot base)
*/
-static struct Scsi_Host *aic7xxx_boards[MAXIRQ + 1];
+#define HID0(x) ((x) + 0xC80) /* 0,1: msb of ID2, 2-7: ID1 */
+#define HID1(x) ((x) + 0xC81) /* 0-4: ID3, 5-7: LSB ID2 */
+#define HID2(x) ((x) + 0xC82) /* product */
+#define HID3(x) ((x) + 0xC83) /* firmware revision */
/*
- * 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!
+ * AIC-7770 I/O range to reserve for a card
*/
-#define AIC7XXX_MAXSCB 16
+#define MINREG(x) ((x) + 0xC00ul)
+#define MAXREG(x) ((x) + 0xCBFul)
-struct aic7xxx_host {
- int base; /* card base address */
- int maxscb; /* hardware SCBs */
- int startup; /* intr type check */
- int extended; /* extended xlate? */
- volatile int unpause; /* value for HCNTRL */
- volatile Scsi_Cmnd *SCB_array[AIC7XXX_MAXSCB]; /* active commands */
-};
+/* -------------------- AIC-7770 offset definitions ----------------------- */
-struct aic7xxx_host_config {
- int irq; /* IRQ number */
- int base; /* I/O base */
- int maxscb; /* hardware SCBs */
- int unpause; /* value for HCNTRL */
- int scsi_id; /* host SCSI id */
- int extended; /* extended xlate? */
-};
+/*
+ * SCSI Sequence Control (p. 3-11).
+ * Each bit, when set starts a specific SCSI sequence on the bus
+ */
+#define SCSISEQ(x) ((x) + 0xC00ul)
+#define TEMODEO 0x80
+#define ENSELO 0x40
+#define ENSELI 0x20
+#define ENRSELI 0x10
+#define ENAUTOATNO 0x08
+#define ENAUTOATNI 0x04
+#define ENAUTOATNP 0x02
+#define SCSIRSTO 0x01
-struct aic7xxx_scb {
- unsigned char control;
- unsigned char target_channel_lun; /* 4/1/3 bits */
- unsigned char SG_segment_count;
- unsigned char SG_list_pointer[4];
- unsigned char SCSI_cmd_pointer[4];
- unsigned char SCSI_cmd_length;
- unsigned char RESERVED[2]; /* must be zero */
- unsigned char target_status;
- unsigned char residual_data_count[3];
- unsigned char residual_SG_segment_count;
- unsigned char data_pointer[4];
- unsigned char data_count[3];
-#if 0
- /*
- * No real point in transferring this to the
- * SCB registers.
- */
- unsigned char RESERVED[6];
-#endif
-};
+/*
+ * SCSI Transfer Control 1 Register (pp. 3-14,15).
+ * Controls the SCSI module data path.
+ */
+#define SXFRCTL1(x) ((x) + 0xC02ul)
+#define BITBUCKET 0x80
+#define SWRAPEN 0x40
+#define ENSPCHK 0x20
+#define STIMESEL 0x18
+#define ENSTIMER 0x04
+#define ACTNEGEN 0x02
+#define STPWEN 0x01 /* Powered Termination */
/*
- * NB. This table MUST be ordered shortest period first.
+ * SCSI Control Signal Read Register (p. 3-15).
+ * Reads the actual state of the SCSI bus pins
*/
-static struct {
- short period;
- short rate;
- char *english;
-} aic7xxx_synctab[] = {
- 100, 0, "10.0",
- 125, 1, "8.0",
- 150, 2, "6.67",
- 175, 3, "5.7",
- 200, 4, "5.0",
- 225, 5, "4.4",
- 250, 6, "4.0",
- 275, 7, "3.6"
-};
+#define SCSISIGI(x) ((x) + 0xC03ul)
+#define CDI 0x80
+#define IOI 0x40
+#define MSGI 0x20
+#define ATNI 0x10
+#define SELI 0x08
+#define BSYI 0x04
+#define REQI 0x02
+#define ACKI 0x01
-static int aic7xxx_synctab_max =
- sizeof(aic7xxx_synctab) / sizeof(aic7xxx_synctab[0]);
+/*
+ * SCSI Contol Signal Write Register (p. 3-16).
+ * Writing to this register modifies the control signals on the bus. Only
+ * those signals that are allowed in the current mode (Initiator/Target) are
+ * asserted.
+ */
+#define SCSISIGO(x) ((x) + 0xC03ul)
+#define CDO 0x80
+#define IOO 0x40
+#define MSGO 0x20
+#define ATNO 0x10
+#define SELO 0x08
+#define BSYO 0x04
+#define REQO 0x02
+#define ACKO 0x01
-enum aha_type {
- T_NONE,
- T_274X,
- T_284X,
- T_294X,
- T_MAX
-};
+/*
+ * SCSI Rate
+ */
+#define SCSIRATE(x) ((x) + 0xC04ul)
-#ifdef AIC7XXX_DEBUG
+/*
+ * SCSI ID (p. 3-18).
+ * Contains the ID of the board and the current target on the
+ * selected channel
+ */
+#define SCSIID(x) ((x) + 0xC05ul)
+#define TID 0xF0 /* Target ID mask */
+#define OID 0x0F /* Our ID mask */
- extern int vsprintf(char *, const char *, va_list);
+/*
+ * SCSI Status 0 (p. 3-21)
+ * Contains one set of SCSI Interrupt codes
+ * These are most likely of interest to the sequencer
+ */
+#define SSTAT0(x) ((x) + 0xC0Bul)
+#define TARGET 0x80 /* Board is a target */
+#define SELDO 0x40 /* Selection Done */
+#define SELDI 0x20 /* Board has been selected */
+#define SELINGO 0x10 /* Selection In Progress */
+#define SWRAP 0x08 /* 24bit counter wrap */
+#define SDONE 0x04 /* STCNT = 0x000000 */
+#define SPIORDY 0x02 /* SCSI PIO Ready */
+#define DMADONE 0x01 /* DMA transfer completed */
- static
- void debug(const char *fmt, ...)
- {
- va_list ap;
- char buf[256];
+/*
+ * Clear SCSI Interrupt 1 (p. 3-23)
+ * Writing a 1 to a bit clears the associated SCSI Interrupt in SSTAT1.
+ */
+#define CLRSINT1(x) ((x) + 0xC0Cul)
+#define CLRSELTIMEO 0x80
+#define CLRATNO 0x40
+#define CLRSCSIRSTI 0x20
+/* UNUSED 0x10 */
+#define CLRBUSFREE 0x08
+#define CLRSCSIPERR 0x04
+#define CLRPHASECHG 0x02
+#define CLRREQINIT 0x01
- va_start(ap, fmt);
- vsprintf(buf, fmt, ap);
- printk(buf);
- va_end(ap);
- }
-
- static
- void debug_config(enum aha_type type, struct aic7xxx_host_config *p)
- {
- int ioport2, ioport3;
-
- static char *BRT[T_MAX][16] = {
- { }, /* T_NONE */
- {
- "2", "???", "???", "12", /* T_274X */
- "???", "???", "???", "28",
- "???", "???", "???", "44",
- "???", "???", "???", "60"
- },
- {
- "2", "4", "8", "12", /* T_284X */
- "16", "20", "24", "28",
- "32", "36", "40", "44",
- "48", "52", "56", "60"
- },
- {
- "???", "???", "???", "???", /* T_294X */
- "???", "???", "???", "???",
- "???", "???", "???", "???",
- "???", "???", "???", "???"
- }
- };
- static int DFT[4] = {
- 0, 50, 75, 100
- };
- static int SST[4] = {
- 256, 128, 64, 32
- };
-
- ioport2 = inb(HA_HOSTCONF(p->base));
- ioport3 = inb(HA_SCSICONF(p->base));
-
- switch (type) {
- case T_274X:
- printk("AHA274X AT EISA SLOT %d:\n", p->base >> 12);
- break;
- case T_284X:
- printk("AHA284X AT SLOT %d:\n", p->base >> 12);
- break;
- case T_294X:
- printk("AHA294X (PCI-bus):\n");
- break;
- default:
- panic("aic7xxx debug_config: internal error\n");
- }
+/*
+ * SCSI Status 1 (p. 3-24)
+ * These interrupt bits are of interest to the kernel driver
+ */
+#define SSTAT1(x) ((x) + 0xC0Cul)
+#define SELTO 0x80
+#define ATNTARG 0x40
+#define SCSIRSTI 0x20
+#define PHASEMIS 0x10
+#define BUSFREE 0x08
+#define SCSIPERR 0x04
+#define PHASECHG 0x02
+#define REQINIT 0x01
- printk(" irq %d\n"
- " bus release time %s bclks\n"
- " data fifo threshold %d%%\n",
- p->irq,
- BRT[type][(ioport2 >> 2) & 0xf],
- DFT[(ioport2 >> 6) & 0x3]);
-
- printk(" SCSI CHANNEL A:\n"
- " scsi id %d\n"
- " scsi bus parity check %sabled\n"
- " scsi selection timeout %d ms\n"
- " scsi bus reset at power-on %sabled\n",
- ioport3 & 0x7,
- (ioport3 & 0x20) ? "en" : "dis",
- SST[(ioport3 >> 3) & 0x3],
- (ioport3 & 0x40) ? "en" : "dis");
-
- if (type == T_274X) {
- printk(" scsi bus termination %sabled\n",
- (ioport3 & 0x80) ? "en" : "dis");
- }
- }
+/*
+ * SCSI Interrrupt Mode 1 (pp. 3-28,29).
+ * Set bits in this register enable the corresponding
+ * interrupt source.
+ */
+#define SIMODE1(x) ((x) + 0xC11ul)
+#define ENSELTIMO 0x80
+#define ENATNTARG 0x40
+#define ENSCSIRST 0x20
+#define ENPHASEMIS 0x10
+#define ENBUSFREE 0x08
+#define ENSCSIPERR 0x04
+#define ENPHASECHG 0x02
+#define ENREQINIT 0x01
- static
- void debug_rate(int base, int rate)
- {
- int target = inb(O_SCSIID(base)) >> 4;
+/*
+ * Selection/Reselection ID (p. 3-31)
+ * Upper four bits are the device id. The ONEBIT is set when the re/selecting
+ * device did not set its own ID.
+ */
+#define SELID(x) ((x) + 0xC19ul)
+#define SELID_MASK 0xF0
+#define ONEBIT 0x08
+/* UNUSED 0x07 */
- if (rate) {
- printk("aic7xxx: target %d now synchronous at %sMb/s\n",
- target,
- aic7xxx_synctab[(rate >> 4) & 0x7].english);
- } else {
- printk("aic7xxx: target %d using asynchronous mode\n",
- target);
- }
- }
+/*
+ * Serial EEPROM Control (p. 4-92 in 7870 Databook)
+ * Controls the reading and writing of an external serial 1-bit
+ * EEPROM Device. In order to access the serial EEPROM, you must
+ * first set the SEEMS bit that generates a request to the memory
+ * port for access to the serial EEPROM device. When the memory
+ * port is not busy servicing another request, it reconfigures
+ * to allow access to the serial EEPROM. When this happens, SEERDY
+ * gets set high to verify that the memory port access has been
+ * granted. See aic7xxx_read_eprom for detailed information on
+ * the protocol necessary to read the serial EEPROM.
+ */
+#define SEECTL(x) ((x) + 0xC1Eul)
+#define EXTARBACK 0x80
+#define EXTARBREQ 0x40
+#define SEEMS 0x20
+#define SEERDY 0x10
+#define SEECS 0x08
+#define SEECK 0x04
+#define SEEDO 0x02
+#define SEEDI 0x01
-#else
+/*
+ * SCSI Block Control (p. 3-32)
+ * Controls Bus type and channel selection. In a twin channel configuration
+ * addresses 0x00-0x1E are gated to the appropriate channel based on this
+ * register. SELWIDE allows for the coexistence of 8bit and 16bit devices
+ * on a wide bus.
+ */
+#define SBLKCTL(x) ((x) + 0xC1Ful)
+/* UNUSED 0xC0 */
+#define AUTOFLUSHDIS 0x20 /* used for Rev C check */
+/* UNUSED 0x10 */
+#define SELBUSB 0x08
+/* UNUSED 0x04 */
+#define SELWIDE 0x02
+/* UNUSED 0x01 */
+#define SELSINGLE 0x00
-# define debug(fmt, args...)
-# define debug_config(x)
-# define debug_rate(x,y)
+/*
+ * Sequencer Control (p. 3-33)
+ * Error detection mode and speed configuration
+ */
+#define SEQCTL(x) ((x) + 0xC60ul)
+#define PERRORDIS 0x80
+#define PAUSEDIS 0x40
+#define FAILDIS 0x20
+#define FASTMODE 0x10
+#define BRKADRINTEN 0x08
+#define STEP 0x04
+#define SEQRESET 0x02
+#define LOADRAM 0x01
-#endif AIC7XXX_DEBUG
+/*
+ * Sequencer RAM Data (p. 3-34)
+ * Single byte window into the Scratch Ram area starting at the address
+ * specified by SEQADDR0 and SEQADDR1. To write a full word, simply write
+ * four bytes in sucessesion. The SEQADDRs will increment after the most
+ * significant byte is written
+ */
+#define SEQRAM(x) ((x) + 0xC61ul)
/*
- * 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?
+ * Sequencer Address Registers (p. 3-35)
+ * Only the first bit of SEQADDR1 holds addressing information
*/
-static int aic7xxx_extended = 0; /* extended translation on? */
+#define SEQADDR0(x) ((x) + 0xC62ul)
+#define SEQADDR1(x) ((x) + 0xC63ul)
-void aic7xxx_setup(char *s, int *dummy)
-{
- int i;
- char *p;
+#define ACCUM(x) ((x) + 0xC64ul) /* accumulator */
- static struct {
- char *name;
- int *flag;
- } options[] = {
- "extended", &aic7xxx_extended,
- NULL
- };
+/*
+ * Board Control (p. 3-43)
+ */
+#define BCTL(x) ((x) + 0xC84ul)
+/* RSVD 0xF0 */
+#define ACE 0x08 /* Support for external processors */
+/* RSVD 0x06 */
+#define ENABLE 0x01
- for (p = strtok(s, ","); p; p = strtok(NULL, ",")) {
- for (i = 0; options[i].name; i++)
- if (!strcmp(options[i].name, p))
- *(options[i].flag) = !0;
- }
-}
+#define BUSSPD(x) ((x) + 0xC86ul) /* FIFO threshold bits ? */
-static
-void aic7xxx_getscb(int base, struct aic7xxx_scb *scb)
-{
- /*
- * This is almost identical to aic7xxx_putscb().
- */
- outb(0x80, O_SCBCNT(base)); /* SCBAUTO */
+/*
+ * Host Control (p. 3-47) R/W
+ * Overal host control of the device.
+ */
+#define HCNTRL(x) ((x) + 0xC87ul)
+/* UNUSED 0x80 */
+#define POWRDN 0x40
+/* UNUSED 0x20 */
+#define SWINT 0x10
+#define IRQMS 0x08
+#define PAUSE 0x04
+#define INTEN 0x02
+#define CHIPRST 0x01
+#define REQ_PAUSE IRQMS | PAUSE | INTEN
+#define UNPAUSE_274X IRQMS | INTEN
+#define UNPAUSE_284X INTEN
+#define UNPAUSE_294X IRQMS | INTEN
- asm volatile("cld\n\t"
- "rep\n\t"
- "insb"
- : /* no output */
- :"D" (scb), "c" (sizeof(*scb)), "d" (O_SCBARRAY(base))
- :"di", "cx", "dx");
+/*
+ * SCB Pointer (p. 3-49)
+ * Gate one of the four SCBs into the SCBARRAY window.
+ */
+#define SCBPTR(x) ((x) + 0xC90ul)
- outb(0, O_SCBCNT(base));
-}
+/*
+ * Interrupt Status (p. 3-50)
+ * Status for system interrupts
+ */
+#define INTSTAT(x) ((x) + 0xC91ul)
+#define SEQINT_MASK 0xF0 /* SEQINT Status Codes */
+#define BAD_PHASE 0x00
+#define SEND_REJECT 0x10
+#define NO_IDENT 0x20
+#define NO_MATCH 0x30
+#define MSG_SDTR 0x40
+#define MSG_WDTR 0x50
+#define MSG_REJECT 0x60
+#define BAD_STATUS 0x70
+#define RESIDUAL 0x80
+#define ABORT_TAG 0x90
+#define AWAITING_MSG 0xa0
+#define BRKADRINT 0x08
+#define SCSIINT 0x04
+#define CMDCMPLT 0x02
+#define SEQINT 0x01
+#define INT_PEND (BRKADRINT | SEQINT | SCSIINT | CMDCMPLT)
/*
- * 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.
+ * Hard Error (p. 3-53)
+ * Reporting of catastrophic errors. You usually cannot recover from
+ * these without a full board reset.
*/
-static
-unsigned aic7xxx_length(Scsi_Cmnd *cmd, int sg_last)
-{
- int i, segments;
- unsigned length;
- struct scatterlist *sg;
+#define ERROR(x) ((x) + 0xC92ul)
+/* UNUSED 0xF0 */
+#define PARERR 0x08
+#define ILLOPCODE 0x04
+#define ILLSADDR 0x02
+#define ILLHADDR 0x01
- segments = cmd->use_sg - sg_last;
- sg = (struct scatterlist *)cmd->buffer;
+/*
+ * Clear Interrupt Status (p. 3-52)
+ */
+#define CLRINT(x) ((x) + 0xC92ul)
+#define CLRBRKADRINT 0x08
+#define CLRSCSIINT 0x04
+#define CLRCMDINT 0x02
+#define CLRSEQINT 0x01
- if (cmd->use_sg) {
- for (i = length = 0;
- i < cmd->use_sg && i < segments;
- i++)
- {
- length += sg[i].length;
- }
- } else
- length = cmd->request_bufflen;
+/*
+ * SCB Auto Increment (p. 3-59)
+ * Byte offset into the SCB Array and an optional bit to allow auto
+ * incrementing of the address during download and upload operations
+ */
+#define SCBCNT(x) ((x) + 0xC9Aul)
+#define SCBAUTO 0x80
+#define SCBCNT_MASK 0x1F
- return(length);
-}
+/*
+ * Queue In FIFO (p. 3-60)
+ * Input queue for queued SCBs (commands that the seqencer has yet to start)
+ */
+#define QINFIFO(x) ((x) + 0xC9Bul)
-static
-void aic7xxx_sg_check(Scsi_Cmnd *cmd)
-{
- int i;
- struct scatterlist *sg = (struct scatterlist *)cmd->buffer;
+/*
+ * Queue In Count (p. 3-60)
+ * Number of queued SCBs
+ */
+#define QINCNT(x) ((x) + 0xC9Cul)
- if (cmd->use_sg) {
- for (i = 0; i < cmd->use_sg; i++)
- if ((unsigned)sg[i].length > 0xffff)
- panic("aic7xxx_sg_check: s/g segment > 64k\n");
- }
-}
+/*
+ * Queue Out FIFO (p. 3-61)
+ * Queue of SCBs that have completed and await the host
+ */
+#define QOUTFIFO(x) ((x) + 0xC9Dul)
-static
-void aic7xxx_to_scsirate(unsigned char *rate,
- unsigned char transfer,
- unsigned char offset)
-{
- int i;
+/*
+ * Queue Out Count (p. 3-61)
+ * Number of queued SCBs in the Out FIFO
+ */
+#define QOUTCNT(x) ((x) + 0xC9Eul)
- transfer *= 4;
+#define SCBARRAY(x) ((x) + 0xCA0ul)
- for (i = 0; i < aic7xxx_synctab_max-1; i++) {
+/* ---------------- END AIC-7770 Register Definitions ----------------- */
- if (transfer == aic7xxx_synctab[i].period) {
- *rate = (aic7xxx_synctab[i].rate << 4) | (offset & 0xf);
- return;
- }
+/* --------------------- AIC-7870-only definitions -------------------- */
- if (transfer > aic7xxx_synctab[i].period &&
- transfer < aic7xxx_synctab[i+1].period)
- {
- *rate = (aic7xxx_synctab[i+1].rate << 4) |
- (offset & 0xf);
- return;
- }
- }
- *rate = 0;
-}
+#define DSPCISTATUS(x) ((x) + 0xC86ul)
+#define DFTHRESH 0xC0
+
+/* Scratch RAM offset definitions */
+
+/* ---------------------- Scratch RAM Offsets ------------------------- */
+/* These offsets are either to values that are initialized by the board's
+ * BIOS or are specified by the Linux sequencer code. If I can figure out
+ * how to read the EISA configuration info at probe time, the cards could
+ * be run without BIOS support installed
+ */
/*
- * Pause the sequencer and wait for it to actually stop - this
- * is important since the sequencer can disable pausing for critical
- * sections.
+ * 1 byte per target starting at this address for configuration values
*/
-#define PAUSE_SEQUENCER(p) \
- do { \
- outb(0xe, O_HCNTRL(p->base)); /* IRQMS|PAUSE|INTEN */ \
- \
- while ((inb(O_HCNTRL(p->base)) & 0x4) == 0) \
- ; \
- } while (0)
+#define HA_TARG_SCRATCH(x) ((x) + 0xC20ul)
/*
- * Unpause the sequencer. Unremarkable, yet done often enough to
- * warrant an easy way to do it.
+ * The sequencer will stick the first byte of any rejected message here so
+ * we can see what is getting thrown away.
*/
-#define UNPAUSE_SEQUENCER(p) \
- outb(p->unpause, O_HCNTRL(p->base)) /* IRQMS|INTEN */
+#define HA_REJBYTE(x) ((x) + 0xC31ul)
/*
- * See comments in aic7xxx_loadram() wrt this.
+ * Bit vector of targets that have disconnection disabled.
*/
-#define RESTART_SEQUENCER(p) \
- do { \
- do { \
- outb(0x2, O_SEQCTL(p->base)); \
- \
- } while (inb(O_SEQADDR(p->base)) != 0 && \
- inb(O_SEQADDR(p->base) + 1) != 0); \
- \
- UNPAUSE_SEQUENCER(p); \
- } while (0)
+#define HA_DISC_DSB ((x) + 0xc32ul)
/*
- * Since we declared this using SA_INTERRUPT, interrupts should
- * be disabled all through this function unless we say otherwise.
+ * Length of pending message
*/
-static
-void aic7xxx_isr(int irq)
-{
- int base, intstat;
- struct aic7xxx_host *p;
-
- p = (struct aic7xxx_host *)aic7xxx_boards[irq]->hostdata;
- base = p->base;
+#define HA_MSG_LEN(x) ((x) + 0xC34ul)
- /*
- * Check the startup flag - if no commands have been queued,
- * we probably have the interrupt type set wrong. Reverse
- * the stored value and the active one in the host control
- * register.
- */
- if (p->startup) {
- p->unpause ^= 0x8;
- outb(inb(O_HCNTRL(p->base)) ^ 0x8, O_HCNTRL(p->base));
- return;
- }
+/*
+ * Outgoing Message Body
+ */
+#define HA_MSG_START(x) ((x) + 0xC35ul)
- /*
- * Handle all the interrupt sources - especially for SCSI
- * interrupts, we won't get a second chance at them.
- */
- intstat = inb(O_INTSTAT(base));
+/*
+ * These are offsets into the card's scratch ram. Some of the values are
+ * specified in the AHA2742 technical reference manual and are initialized
+ * by the BIOS at boot time.
+ */
+#define HA_ARG_1(x) ((x) + 0xC4Aul) /* sdtr <-> rate parameters */
+#define HA_RETURN_1(x) ((x) + 0xC4Aul)
+#define SEND_SENSE 0x80
+#define SEND_SDTR 0x80
+#define SEND_WDTR 0x80
+#define SEND_REJ 0x40
+
+#define HA_SIGSTATE(x) ((x) + 0xC4Bul) /* value in SCSISIGO */
+#define HA_SCBCOUNT(x) ((x) + 0xC52ul) /* number of hardware SCBs */
+
+#define HA_FLAGS(x) ((x) + 0xC53ul) /* TWIN and WIDE bus flags */
+#define SINGLE_BUS 0x00
+#define TWIN_BUS 0x01
+#define WIDE_BUS 0x02
+#define ACTIVE_MSG 0x20
+#define IDENTIFY_SEEN 0x40
+#define RESELECTING 0x80
+
+#define HA_ACTIVE0(x) ((x) + 0xC54ul) /* Active bits; targets 0-7 */
+#define HA_ACTIVE1(x) ((x) + 0xC55ul) /* Active bits; targets 8-15 */
+#define SAVED_TCL(x) ((x) + 0xC56ul) /* Saved target, channel, LUN */
+#define WAITING_SCBH(x) ((x) + 0xC57ul) /* Head of disconnected targets list. */
+#define WAITING_SCBT(x) ((x) + 0xC58ul) /* Tail of disconnected targets list. */
+
+#define HA_SCSICONF(x) ((x) + 0xC5Aul) /* SCSI config register */
+#define HA_INTDEF(x) ((x) + 0xC5Cul) /* interrupt def'n register */
+#define HA_HOSTCONF(x) ((x) + 0xC5Dul) /* host config def'n register */
+
+#define MSG_ABORT 0x06
+#define MSG_BUS_DEVICE_RESET 0x0c
+#define BUS_8_BIT 0x00
+#define BUS_16_BIT 0x01
+#define BUS_32_BIT 0x02
- if (intstat & 0x8) { /* BRKADRINT */
- panic("aic7xxx_isr: brkadrint, error = 0x%x, seqaddr = 0x%x\n",
- inb(O_ERROR(base)), inw(O_SEQADDR(base)));
- }
+/*
+ *
+ * Define the format of the SEEPROM registers (16 bits).
+ *
+ */
+struct seeprom_config {
- if (intstat & 0x4) { /* SCSIINT */
-
- int scbptr = inb(O_SCBPTR(base));
- int status = inb(O_SSTAT1(base));
- Scsi_Cmnd *cmd;
-
- cmd = (Scsi_Cmnd *)p->SCB_array[scbptr];
- if (!cmd) {
- printk("aic7xxx_isr: 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, O_CLRSINT1(base));
- UNPAUSE_SEQUENCER(p);
- outb(0x4, O_CLRINT(base)); /* undocumented */
- status = 0;
- }
- p->SCB_array[scbptr] = NULL;
+/*
+ * 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 */
+/* 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 */
- /*
- * 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 & 0x80) { /* SELTO */
- /*
- * Hardware selection timer has expired. Turn
- * off SCSI selection sequence.
- */
- outb(0, O_SCSISEQ(base));
- cmd->result = DID_TIME_OUT << 16;
-
- /*
- * If there's an active message, it belongs to the
- * command that is getting punted - remove it.
- */
- outb(0, HA_MSG_FLAGS(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(0x80, O_CLRSINT1(base)); /* CLRSELTIMO */
- RESTART_SEQUENCER(p);
+/*
+ * 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 */
+/* UNUSED 0x0060 */
+#define CFEXTEND 0x0080 /* extended translation enabled */
+/* UNUSED 0xFF00 */
+ unsigned short bios_control; /* word 16 */
- outb(0x4, O_CLRINT(base)); /* undocumented */
- cmd->scsi_done(cmd);
- }
+/*
+ * Host Adapter Control Bits
+ */
+/* UNUSED 0x0003 */
+#define CFWSTERM 0x0008 /* SCSI high byte termination (wide card) */
+#define CFSTERM 0x0004 /* SCSI low byte termination (non-wide cards) */
+#define CFSPARITY 0x0010 /* SCSI parity */
+/* UNUSED 0x0020 */
+#define CFRESETB 0x0040 /* reset SCSI bus at IC initialization */
+/* UNUSED 0xFF80 */
+ unsigned short adapter_control; /* word 17 */
- if (status & 0x4) { /* SCSIPERR */
- /*
- * A parity error has occurred during a data
- * transfer phase. Flag it and continue.
- */
- printk("aic7xxx: parity error on target %d, lun %d\n",
- cmd->target,
- cmd->lun);
- aic7xxx_parity(cmd) = DID_PARITY;
-
- /*
- * Clear interrupt and resume as above.
- */
- outb(0x4, O_CLRSINT1(base)); /* CLRSCSIPERR */
- UNPAUSE_SEQUENCER(p);
+/*
+ * 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 */
- outb(0x4, O_CLRINT(base)); /* undocumented */
- }
+/*
+ * Maximum targets
+ */
+#define CFMAXTARG 0x00FF /* maximum targets */
+/* UNUSED 0xFF00 */
+ unsigned short max_targets; /* word 19 */
- if ((status & (0x8|0x4)) == 0 && status) {
- /*
- * We don't know what's going on. Turn off the
- * interrupt source and try to continue.
- */
- printk("aic7xxx_isr: sstat1 = 0x%x\n", status);
- outb(status, O_CLRSINT1(base));
- UNPAUSE_SEQUENCER(p);
- outb(0x4, O_CLRINT(base)); /* undocumented */
- }
- }
+ unsigned short res_1[11]; /* words 20-30 */
+ unsigned short checksum; /* word 31 */
- if (intstat & 0x2) { /* CMDCMPLT */
+};
- int complete, old_scbptr;
- struct aic7xxx_scb scb;
- unsigned actual;
- Scsi_Cmnd *cmd;
- /*
- * 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(O_QOUTFIFO(base));
+#define AIC7XXX_DEBUG
- cmd = (Scsi_Cmnd *)p->SCB_array[complete];
- if (!cmd) {
- printk("aic7xxx warning: "
- "no command for scb (cmdcmplt)\n");
- continue;
- }
- p->SCB_array[complete] = NULL;
-
- PAUSE_SEQUENCER(p);
-
- /*
- * After pausing the sequencer (and waiting
- * for it to stop), save its SCB pointer, then
- * write in our completed one and read the SCB
- * registers. Afterwards, restore the saved
- * pointer, unpause the sequencer and call the
- * higher-level completion function - unpause
- * first since we have no idea how long done()
- * will take.
- */
- old_scbptr = inb(O_SCBPTR(base));
- outb(complete, O_SCBPTR(base));
-
- aic7xxx_getscb(base, &scb);
- outb(old_scbptr, O_SCBPTR(base));
-
- UNPAUSE_SEQUENCER(p);
-
- cmd->result = scb.target_status |
- (aic7xxx_parity(cmd) << 16);
-
- /*
- * Did we 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 -= ((scb.residual_data_count[2] << 16) |
- (scb.residual_data_count[1] << 8) |
- (scb.residual_data_count[0]));
-
- if (actual < cmd->underflow) {
- printk("aic7xxx: target %d underflow - "
- "wanted (at least) %u, got %u\n",
- cmd->target, cmd->underflow, actual);
-
- cmd->result = scb.target_status |
- (DID_UNDERFLOW << 16);
- }
-
- cmd->scsi_done(cmd);
-
- /*
- * 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(0x2, O_CLRINT(base)); /* CLRCMDINT */
+/*
+ * 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) \
+ ; \
- } while (inb(O_QOUTCNT(base)));
- }
+/*
+ * 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))
- if (intstat & 0x1) { /* SEQINT */
+/*
+ * 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);
- unsigned char transfer, offset, rate;
+/*
+ * 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)
- /*
- * Although the sequencer is paused immediately on
- * a SEQINT, an interrupt for a SCSIINT or a CMDCMPLT
- * condition will have unpaused the sequencer before
- * this point.
- */
- PAUSE_SEQUENCER(p);
+/*
+ * Keep track of the targets returned status.
+ */
+#define aic7xxx_status(cmd) ((cmd)->SCp.sent_command)
- switch (intstat & 0xf0) {
- case 0x00:
- panic("aic7xxx_isr: unknown scsi bus phase\n");
- case 0x10:
- debug("aic7xxx_isr warning: "
- "issuing message reject, 1st byte 0x%x\n",
- inb(HA_REJBYTE(base)));
- break;
- case 0x20:
- panic("aic7xxx_isr: reconnecting target %d "
- "didn't issue IDENTIFY message\n",
- (inb(O_SELID(base)) >> 4) & 0xf);
- case 0x30:
- debug("aic7xxx_isr: sequencer couldn't find match "
- "for reconnecting target %d - issuing ABORT\n",
- (inb(O_SELID(base)) >> 4) & 0xf);
- break;
- case 0x40:
- transfer = inb(HA_ARG_1(base));
- offset = inb(HA_ARG_2(base));
- aic7xxx_to_scsirate(&rate, transfer, offset);
- outb(rate, HA_RETURN_1(base));
- debug_rate(base, rate);
- break;
- default:
- debug("aic7xxx_isr: seqint, "
- "intstat = 0x%x, scsisigi = 0x%x\n",
- intstat, inb(O_SCSISIGI(base)));
- break;
- }
+/*
+ * The position of the SCSI commands scb within the scb array.
+ */
+#define aic7xxx_position(cmd) ((cmd)->SCp.have_data_in)
- outb(0x1, O_CLRINT(base)); /* CLRSEQINT */
- UNPAUSE_SEQUENCER(p);
- }
-}
+/*
+ * 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)
/*
- * 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
+ * "Static" structures. Note that these are NOT initialized
+ * to zero inside the kernel - we have to initialize them all
+ * explicitly.
*
- * 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?).
+ * We support a maximum of one adapter card per IRQ level (see the
+ * rationale for this above). On an interrupt, use the IRQ as an
+ * index into aic7xxx_boards[] to locate the card information.
*/
-
-static
-enum aha_type aic7xxx_probe(int slot, int s_base)
-{
- int i;
- unsigned char buf[4];
-
- static struct {
- int n;
- unsigned char signature[sizeof(buf)];
- enum aha_type type;
- } S[] = {
- 4, { 0x04, 0x90, 0x77, 0x71 }, T_274X, /* host adapter 274x */
- 4, { 0x04, 0x90, 0x77, 0x70 }, T_274X, /* motherboard 274x */
- 4, { 0x04, 0x90, 0x77, 0x56 }, T_284X, /* 284x, BIOS enabled */
- };
-
- for (i = 0; i < sizeof(buf); i++) {
- /*
- * The VL-bus cards need to be primed by
- * writing before a signature check.
- */
- outb(0x80 + i, s_base);
- buf[i] = inb(s_base + i);
- }
-
- for (i = 0; i < sizeof(S)/sizeof(S[0]); i++) {
- if (!memcmp(buf, S[i].signature, S[i].n)) {
- /*
- * Signature match on enabled card?
- */
- if (inb(s_base + 4) & 1)
- return(S[i].type);
- printk("aic7xxx disabled at slot %d, ignored\n", slot);
- }
- }
- return(T_NONE);
-}
+static struct Scsi_Host *aic7xxx_boards[MAXIRQ + 1];
/*
- * Return ' ' for plain 274x, 'T' for twin-channel, 'W' for
- * wide channel, '?' for anything else.
+ * The driver keeps up to four scb structures per card in memory. Only the
+ * first 26 bytes of the structure are valid for the hardware, the rest used
+ * for driver level bookeeping. The driver is further optimized
+ * so that we only have to download the first 19 bytes since as long
+ * as we always use S/G, the last fields should be zero anyway.
*/
+#ifdef AIC7XXX_USE_SG
+#define SCB_DOWNLOAD_SIZE 19 /* amount to actually download */
+#else
+#define SCB_DOWNLOAD_SIZE 26
+#endif
-static
-char aic7xxx_type(int base)
-{
- /*
- * AIC-7770/7870s can be wired so that, on chip reset,
- * the SCSI Block Control register indicates how many
- * busses the chip is configured for. The two high bits
- * set indicate a 294x.
- */
- switch (inb(O_SBLKCTL(base))) {
- case 0:
- case 0xc0:
- return(' ');
- case 2:
- case 0xc2:
- return('W');
- case 8:
- return('T');
- default:
- printk("aic7xxx has unknown bus configuration\n");
- return('?');
- }
-}
-
-static
-void aic7xxx_loadram(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"
- };
+#define SCB_UPLOAD_SIZE 19 /* amount to actually upload */
+struct aic7xxx_scb {
+/* ------------ Begin hardware supported fields ---------------- */
+/*1 */ unsigned char control;
+#define SCB_NEEDWDTR 0x80 /* Initiate Wide Negotiation */
+#define SCB_NEEDSDTR 0x40 /* Initiate Sync Negotiation */
+#define SCB_NEEDDMA 0x08 /* SCB needs to be DMA'd from
+ * from host memory
+ */
+#define SCB_REJ_MDP 0x80 /* Reject MDP message */
+#define SCB_DISEN 0x40 /* SCB Disconnect enable */
+#define SCB_TE 0x20 /* Tag enable */
+/* RESERVED 0x10 */
+#define SCB_WAITING 0x08 /* Waiting */
+#define SCB_DIS 0x04 /* Disconnected */
+#define SCB_TAG_TYPE 0x03
+#define SIMPLE_QUEUE 0x00 /* Simple Queue */
+#define HEAD_QUEUE 0x01 /* Head of Queue */
+#define ORD_QUEUE 0x02 /* Ordered Queue */
+/* ILLEGAL 0x03 */
+/*2 */ unsigned char target_channel_lun; /* 4/1/3 bits */
+/*3 */ unsigned char SG_segment_count;
+/*7 */ unsigned char SG_list_pointer[4] __attribute__ ((packed));
+/*11*/ unsigned char SCSI_cmd_pointer[4] __attribute__ ((packed));
+/*12*/ unsigned char SCSI_cmd_length;
+/*14*/ unsigned char RESERVED[2]; /* must be zero */
+/*15*/ unsigned char target_status;
+/*18*/ unsigned char residual_data_count[3];
+/*19*/ unsigned char residual_SG_segment_count;
+/*23*/ unsigned char data_pointer[4] __attribute__ ((packed));
+/*26*/ unsigned char data_count[3];
+/*30*/ unsigned char host_scb[4] __attribute__ ((packed));
+/*31*/ u_char next_waiting; /* Used to thread SCBs awaiting selection. */
+#define SCB_LIST_NULL 0x10 /* SCB list equivelent to NULL */
+#if 0
/*
- * 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.
+ * No real point in transferring this to the
+ * SCB registers.
*/
- outb(0x83, O_SEQCTL(base)); /* PERRORDIS|SEQRESET|LOADRAM */
+ unsigned char RESERVED[1];
+#endif
- asm volatile("cld\n\t"
- "rep\n\t"
- "outsb"
- : /* no output */
- :"S" (seqprog), "c" (sizeof(seqprog)), "d" (O_SEQRAM(base))
- :"si", "cx", "dx");
+ /*-----------------end of hardware supported fields----------------*/
+ struct aic7xxx_scb *next; /* next ptr when in free list */
+ Scsi_Cmnd *cmd; /* Scsi_Cmnd for this scb */
+ int state; /* current state of 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
+ unsigned int position; /* Position in scb array */
+#ifdef AIC7XXX_USE_SG
+ struct scatterlist sg;
+ struct scatterlist sense_sg;
+#endif
+ unsigned char sense_cmd[6]; /* Allocate 6 characters for sense command */
+};
- /*
- * 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?)
- */
- outb(0, O_SEQCTL(base));
- do {
- /*
- * Actually, reset it until
- * the address shows up as
- * zero just to be safe..
- */
- outb(0x2, O_SEQCTL(base)); /* SEQRESET */
+static struct {
+ unsigned char errno;
+ char *errmesg;
+} hard_error[] = {
+ { ILLHADDR, "Illegal Host Access" },
+ { ILLSADDR, "Illegal Sequencer Address referrenced" },
+ { ILLOPCODE, "Illegal Opcode in sequencer program" },
+ { PARERR, "Sequencer Ram Parity Error" }
+};
- } while (inb(O_SEQADDR(base)) != 0 && inb(O_SEQADDR(base) + 1) != 0);
-}
+static unsigned char
+generic_sense[] = { REQUEST_SENSE, 0, 0, 0, 255, 0 };
-static
-void aha274x_config(struct aic7xxx_host_config *p, va_list ap)
-{
- int base = va_arg(ap, int);
+/*
+ * 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 16
- /*
- * Give the AIC-7770 a reset - reading the 274x's registers
- * returns zeroes unless you do. This forces a pause of the
- * Sequencer.
- */
- outb(1, O_HCNTRL(base)); /* CHIPRST */
+/*
+ * 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_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;
+ 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 */
+};
- p->base = base;
- p->irq = inb(HA_INTDEF(base)) & 0xf;
- p->scsi_id = inb(HA_SCSICONF(base)) & 0x7;
+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 */
+ aha_type type; /* card type */
+ 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) */
+};
- /*
- * This value for HCNTRL may be changed in the ISR if we
- * catch a spurious interrupt right away.
- */
- p->unpause = 0xa;
+/*
+ * 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;
+ short rate;
+ char *english;
+} aic7xxx_syncrates[] = {
+ { 100, 0, "10.0" },
+ { 125, 1, "8.0" },
+ { 150, 2, "6.67" },
+ { 175, 3, "5.7" },
+ { 200, 4, "5.0" },
+ { 225, 5, "4.4" },
+ { 250, 6, "4.0" },
+ { 275, 7, "3.6" }
+};
- /*
- * XXX - these are values that I don't know how to query
- * the hardware for. Apparently some revision E
- * '7770s can have more SCBs, and I don't know how
- * to get the "extended translation" flag from the
- * EISA data area.
- */
- p->maxscb = 4;
- p->extended = aic7xxx_extended;
+static int num_aic7xxx_syncrates =
+ sizeof(aic7xxx_syncrates) / sizeof(aic7xxx_syncrates[0]);
- /*
- * A reminder until this can be detected automatically.
- */
- printk("aha274x: extended translation %sabled\n",
- p->extended ? "en" : "dis");
-}
+#ifdef AIC7XXX_DEBUG
+extern int vsprintf(char *, const char *, va_list);
-static
-void aha284x_config(struct aic7xxx_host_config *p, va_list ap)
+static void
+debug(const char *fmt, ...)
{
- int base = va_arg(ap, int);
+ va_list ap;
+ char buf[256];
- /*
- * Give the AIC-7770 a reset - this forces a pause of the
- * Sequencer and returns everything to default values.
- */
- outb(1, O_HCNTRL(base)); /* CHIPRST */
-
- p->base = base;
- p->unpause = 0x2;
- p->irq = inb(HA_INTDEF(base)) & 0xf;
- p->scsi_id = inb(HA_SCSICONF(base)) & 0x7;
-
- /*
- * XXX - these are values that I don't know how to query
- * the hardware for. Apparently some revision E
- * '7770s can have more SCBs, and I don't know how
- * to get the "extended translation" flag from the
- * onboard memory.
- */
- p->maxscb = 4;
- p->extended = aic7xxx_extended;
-
- /*
- * A reminder until this can be detected automatically.
- */
- printk("aha284x: extended translation %sabled\n",
- p->extended ? "en" : "dis");
+ 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 char *BUSW[] = { "", "-TWIN", "-WIDE" };
+
+ host_conf = inb(HA_HOSTCONF(p->base));
+ scsi_conf = inb(HA_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->type == AIC_274x) || (p->type == AIC_284x))
+ {
+ brelease = scsi_conf & 0x3F;
+ dfthresh = host_conf >> 6;
+ }
+ else
+ {
+ brelease = p->busrtime;
+ dfthresh = scsi_conf >> 6;
+ }
+ if (brelease == 0)
+ {
+ brelease = 2;
+ }
+
+ switch (p->type)
+ {
+ case AIC_274x:
+ printk("AIC7770%s AT EISA SLOT %d:\n", BUSW[p->bus_type], p->base >> 12);
+ break;
+
+ case AIC_284x:
+ printk("AIC7770%s AT VLB SLOT %d:\n", BUSW[p->bus_type], p->base >> 12);
+ break;
+
+ case AIC_7870:
+ printk("AIC7870%s (PCI-bus):\n", BUSW[p->bus_type]);
+ break;
+
+ case AIC_7850:
+ printk("AIC7850%s (PCI-bus):\n", BUSW[p->bus_type]);
+ break;
+
+ case AIC_7872:
+ printk("AIC7872%s (PCI-bus):\n", 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->type == AIC_274x) || (p->type == AIC_284x)) && 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_274x)
+ {
+ 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");
+ }
}
+#else
+# define debug(fmt, args...)
+# define debug_config(x)
+#endif AIC7XXX_DEBUG
-static
-void aha294x_config(struct aic7xxx_host_config *p, va_list ap)
-{
- int error;
- unsigned long io_port;
- unsigned char bus, device_fn, irq;
-
- bus = va_arg(ap, unsigned char);
- device_fn = va_arg(ap, unsigned char);
-
- /*
- * Read esundry information from PCI BIOS.
- */
- error = pcibios_read_config_dword(bus,
- device_fn,
- PCI_BASE_ADDRESS_0,
- &io_port);
- if (error) {
- panic("aha294x_config: error %s reading i/o port\n",
- pcibios_strerror(error));
- }
-
- error = pcibios_read_config_byte(bus,
- device_fn,
- PCI_INTERRUPT_LINE,
- &irq);
- if (error) {
- panic("aha294x_config: error %s reading irq\n",
- pcibios_strerror(error));
- }
-
- /*
- * Make the base I/O register look like EISA and VL-bus.
- */
- p->base = io_port - 0xc01;
-
- /*
- * Give the AIC-7870 a reset - this forces a pause of the
- * Sequencer and returns everything to default values.
- */
- outb(1, O_HCNTRL(p->base)); /* CHIPRST */
+/*
+ * 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 int aic7xxx_extended = 0; /* extended translation on? */
+static int aic7xxx_no_reset = 0; /* no resetting of SCSI bus */
- p->irq = irq;
- p->maxscb = 16;
- p->unpause = 0xa;
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_setup
+ *
+ * Description:
+ * Handle Linux boot parameters.
+ *-F*************************************************************************/
+void
+aic7xxx_setup(char *s, int *dummy)
+{
+ int i;
+ char *p;
+
+ static struct {
+ char *name;
+ 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++)
+ {
+ if (!strcmp(options[i].name, p))
+ {
+ *(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));
+
+ asm volatile("cld\n\t"
+ "rep\n\t"
+ "outsb"
+ : /* no output */
+ :"S" (seqprog), "c" (sizeof(seqprog)), "d" (SEQRAM(base))
+ :"si", "cx", "dx");
+
+ /*
+ * 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;
- /*
- * XXX - these are values that I don't know how to query
- * the hardware for, so for now the SCSI host ID is
- * hardwired to 7, and the "extended translation"
- * flag is taken from boot-time flags.
- */
- p->scsi_id = 7;
- p->extended = aic7xxx_extended;
+ i = jiffies + (seconds * 100); /* compute time to stop */
- /*
- * XXX - force data fifo threshold to 100%. Why does this
- * need to be done?
- */
-# define DFTHRESH 3
+ while (jiffies < i)
+ {
+ ; /* Do nothing! */
+ }
+}
- outb(DFTHRESH << 6, O_DSPCISTATUS(p->base));
- outb(p->scsi_id | (DFTHRESH << 6), HA_SCSICONF(p->base));
+/*+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_putscb
+ *
+ * Description:
+ * Transfer a SCB to the controller.
+ *-F*************************************************************************/
+static void
+aic7xxx_putscb(int base, struct aic7xxx_scb *scb)
+{
+#ifdef AIC7XXX_USE_DMA
+ /*
+ * All we need to do, is to output the position
+ * of the SCB in the SCBARRAY to the QINFIFO
+ * of the host adapter.
+ */
+ outb(scb->position, QINFIFO(base));
+#else
+ /*
+ * 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.
+ */
+ outb(SCBAUTO, SCBCNT(base));
+
+ asm volatile("cld\n\t"
+ "rep\n\t"
+ "outsb"
+ : /* no output */
+ :"S" (scb), "c" (SCB_DOWNLOAD_SIZE), "d" (SCBARRAY(base))
+ :"si", "cx", "dx");
- /*
- * A reminder until this can be detected automatically.
- */
- printk("aha294x: extended translation %sabled\n",
- p->extended ? "en" : "dis");
+ outb(0, SCBCNT(base));
+#endif
}
-static
-int aic7xxx_register(Scsi_Host_Template *template, enum aha_type type, ...)
-{
- va_list ap;
- int i, base;
- struct Scsi_Host *host;
- struct aic7xxx_host *p;
- struct aic7xxx_host_config config;
-
- va_start(ap, type);
-
- switch (type) {
- case T_274X:
- aha274x_config(&config, ap);
- break;
- case T_284X:
- aha284x_config(&config, ap);
- break;
- case T_294X:
- aha294x_config(&config, ap);
- break;
- default:
- panic("aic7xxx_register: internal error\n");
- }
- va_end(ap);
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_putdmascb
+ *
+ * Description:
+ * DMA a SCB to the controller.
+ *-F*************************************************************************/
+static void
+aic7xxx_putdmascb(int base, struct aic7xxx_scb *scb)
+{
+ /*
+ * 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.
+ */
+ outb(SCBAUTO, SCBCNT(base));
+
+ asm volatile("cld\n\t"
+ "rep\n\t"
+ "outsb"
+ : /* no output */
+ :"S" (scb), "c" (31), "d" (SCBARRAY(base))
+ :"si", "cx", "dx");
+
+ outb(0, SCBCNT(base));
+}
+
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_getscb
+ *
+ * Description:
+ * Get a SCB from the controller.
+ *-F*************************************************************************/
+static void
+aic7xxx_getscb(int base, struct aic7xxx_scb *scb)
+{
+ /*
+ * This is almost identical to aic7xxx_putscb().
+ */
+ outb(SCBAUTO, SCBCNT(base));
+
+ asm volatile("cld\n\t"
+ "rep\n\t"
+ "insb"
+ : /* no output */
+ :"D" (scb), "c" (SCB_UPLOAD_SIZE), "d" (SCBARRAY(base))
+ :"di", "cx", "dx");
+
+ outb(0, SCBCNT(base));
+}
+
+/*+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(unsigned char *scsirate, unsigned char period,
+ unsigned char offset, int target)
+{
+ int i;
+
+ for (i = 0; i < num_aic7xxx_syncrates; i++)
+ {
+ if ((aic7xxx_syncrates[i].period - period) >= 0)
+ {
+ *scsirate = (aic7xxx_syncrates[i].rate << 4) | (offset & 0x0F);
+ printk("aic7xxx: target %d now synchronous at %sMb/s, offset = 0x%x\n",
+ target, aic7xxx_syncrates[i].english, offset);
+ return;
+ }
+ }
+
+ /*
+ * Default to asyncronous transfer
+ */
+ *scsirate = 0;
+ printk("aic7xxx: target %d using asynchronous transfers\n", target);
+}
+
+/*+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 active, ha_flags, transfer;
+ unsigned char scsi_id, bus_width;
+ unsigned char offset, rate, scratch;
+ unsigned char max_offset;
+ unsigned char head, tail;
+ unsigned short target_mask;
+ long flags;
+ void *addr;
+ int actual;
+ int target, tcl;
+ int scbptr;
+ Scsi_Cmnd *cmd;
+#if 0
+static int_count = 0;
+#endif
- base = config.base;
+ p = (struct aic7xxx_host *) aic7xxx_boards[irq]->hostdata;
+#ifdef AIC7XXX_SHARE_IRQS
+ /*
+ * Search for the host with a pending interrupt.
+ */
+ while ((p != NULL) && !(inb(INTSTAT(p->base)) & INT_PEND))
+ {
+ p = (struct aic7xxx_host *) p->next->hostdata;
+ }
+ if (p == NULL)
+ {
+ printk("aic7xxx_isr: Encountered spurious interrupt.\n");
+ return;
+ }
+#endif
+ base = p->base;
+ if (p->isr_count == 0xffffffff)
+ {
+ p->isr_count = 0;
+ }
+ else
+ {
+ p->isr_count = p->isr_count + 1;
+ }
+ if ((p->a_scanned == 0) && (p->isr_count == 1))
+ {
+ /* Allow for one interrupt when the card is enabled. */
+ return;
+ }
+
+ /*
+ * 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_isr: brkadrint (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_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 or a CMDCMPLT
+ * condition will have unpaused the sequencer before
+ * this point.
+ */
+ PAUSE_SEQUENCER(p);
+
+ switch (intstat & SEQINT_MASK)
+ {
+ case BAD_PHASE:
+ panic("aic7xxx_isr: unknown scsi bus phase\n");
+
+ case SEND_REJECT:
+ debug("aic7xxx_isr warning: issuing message reject, 1st byte 0x%x\n",
+ inb(HA_REJBYTE(base)));
+ break;
+
+ case NO_IDENT:
+ panic("aic7xxx_isr: reconnecting target %d at seqaddr 0x%x "
+ "didn't issue IDENTIFY message\n",
+ (inb(SELID(base)) >> 4) & 0x0F,
+ (inb(SEQADDR1(base)) << 8) | inb(SEQADDR0(base)));
+ break;
+
+ case NO_MATCH:
+ tcl = inb(SCBARRAY(base) + 1);
+ target = (tcl >> 4) & 0x0F;
+ /* Purposefully mask off the top bit of targets 8-15. */
+ target_mask = 0x01 << (target & 0x07);
+
+ debug("aic7xxx_isr: sequencer couldn't find match "
+ "for reconnecting target %d, channel %d, lun %d - "
+ "issuing ABORT\n", target, (tcl & 0x08) >> 3, tcl & 0x07);
+ if (tcl & 0x88)
+ {
+ /* Second channel stores its info in byte
+ * two of HA_ACTIVE
+ */
+ active = inb(HA_ACTIVE1(base));
+ active = active & ~(target_mask);
+ outb(active, HA_ACTIVE1(base));
+ }
+ else
+ {
+ active = inb(HA_ACTIVE0(base));
+ active = active & ~(target_mask);
+ outb(active, HA_ACTIVE0(base));
+ }
+#ifdef AIC7XXX_USE_DMA
+ outb(SCB_NEEDDMA, SCBARRAY(base));
+#endif
/*
- * 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?
+ * Check out why this use to be outb(0x80, CLRINT(base))
+ * clear the timeout
*/
- if (config.irq < 9 || config.irq > 15) {
- printk("aic7xxx uses unsupported IRQ level, ignoring\n");
- return(0);
+ outb(CLRSELTIMEO, CLRSINT1(base));
+ RESTART_SEQUENCER(p);
+ break;
+
+ case MSG_SDTR:
+ /*
+ * 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(HA_ARG_1(base)) << 2);
+ offset = inb(ACCUM(base));
+ scsi_id = inb(SCSIID(base)) >> 0x04;
+ if (inb(SBLKCTL(base)) & 0x08)
+ {
+ scsi_id = scsi_id + 8; /* B channel */
}
-
+ target_mask = (0x01 << scsi_id);
+ scratch = inb(HA_TARG_SCRATCH(base) + scsi_id);
/*
- * Lock out other contenders for our i/o space.
+ * The maximum offset for a wide device is 0x08; for a
+ * 8-bit bus device the maximum offset is 0x0f.
*/
- snarf_region(O_MINREG(base), O_MAXREG(base)-O_MINREG(base));
-
- /*
- * Any card-type-specific adjustments before we register
- * the scsi host(s).
- */
- switch (aic7xxx_type(base)) {
- case 'T':
- printk("aic7xxx warning: ignoring channel B of 274x-twin\n");
- break;
- case ' ':
- break;
- default:
- printk("aic7xxx is an unsupported type, ignoring\n");
- return(0);
+ if (scratch & 0x80)
+ {
+ max_offset = 0x08;
}
-
+ else
+ {
+ max_offset = 0x0f;
+ }
+ aic7xxx_scsirate(&rate, transfer, MIN(offset, max_offset), scsi_id);
/*
- * 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.
+ * Preserve the wide transfer flag.
*/
- if (template->sg_tablesize != SG_NONE) {
- struct scatterlist sg;
+ rate = rate | (scratch & 0x80);
+ outb(rate, HA_TARG_SCRATCH(base) + scsi_id);
+ outb(rate, SCSIRATE(base));
+ if ((rate & 0xf) == 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, HA_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, HA_RETURN_1(base));
+ }
+ else
+ {
+ /*
+ * Send our own SDTR in reply.
+ */
+ printk("Sending SDTR!!\n");
+ outb(SEND_SDTR, HA_RETURN_1(base));
+ }
+ }
+ /*
+ * Clear the flags.
+ */
+ p->needsdtr = p->needsdtr & ~target_mask;
+ p->sdtr_pending = p->sdtr_pending & ~target_mask;
+ break;
+
+ case MSG_WDTR:
+ {
+ bus_width = inb(ACCUM(base));
+ scsi_id = inb(SCSIID(base)) >> 0x04;
+ if (inb(SBLKCTL(base)) & 0x08)
+ {
+ scsi_id = scsi_id + 8; /* B channel */
+ }
+ printk("Received MSG_WDTR, scsi_id = %d, "
+ "needwdtr = 0x%x\n", scsi_id, p->needwdtr);
+ scratch = inb(HA_TARG_SCRATCH(base) + scsi_id);
- if (SG_STRUCT_CHECK(sg)) {
- printk("aic7xxx warning: kernel scatter-gather "
- "structures changed, disabling it\n");
- template->sg_tablesize = SG_NONE;
- }
+ target_mask = (0x01 << scsi_id);
+ if (p->wdtr_pending & target_mask)
+ {
+ /*
+ * Don't send an WDTR back to the target, since we asked first.
+ */
+ outb(0, HA_RETURN_1(base));
+ switch (bus_width)
+ {
+ case BUS_8_BIT:
+ scratch = scratch & 0x7F;
+ break;
+
+ case BUS_16_BIT:
+ printk("aic7xxx_isr: target %d using 16 bit transfers\n",
+ scsi_id);
+ scratch = scratch | 0x80;
+ break;
+ }
}
-
- /*
- * 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.
+ else
+ {
+ /*
+ * Send our own WDTR in reply.
+ */
+ printk("Will send WDTR!!\n");
+ switch (bus_width)
+ {
+ case BUS_8_BIT:
+ scratch = 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_isr: target %d using 16 bit transfers\n",
+ scsi_id);
+ scratch = scratch | 0x80;
+ break;
+ }
+ outb(bus_width | SEND_WDTR, HA_RETURN_1(base));
+ }
+ p->needwdtr = p->needwdtr & ~target_mask;
+ p->wdtr_pending = p->wdtr_pending & ~target_mask;
+ outb(scratch, HA_TARG_SCRATCH(base) + scsi_id);
+ outb(scratch, SCSIRATE(base));
+ break;
+ }
+
+ case MSG_REJECT:
+ {
+ /*
+ * 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.
*/
- host = scsi_register(template, sizeof(struct aic7xxx_host));
- host->can_queue = config.maxscb;
- host->this_id = config.scsi_id;
- host->irq = config.irq;
-
- aic7xxx_boards[config.irq] = host;
-
- p = (struct aic7xxx_host *)host->hostdata;
- for (i = 0; i < AIC7XXX_MAXSCB; i++)
- p->SCB_array[i] = NULL;
-
- p->base = config.base;
- p->maxscb = config.maxscb;
- p->extended = config.extended;
- /*
- * The interrupt trigger is different depending
- * on whether the card is EISA or VL-bus - sometimes.
- * The startup variable will be cleared once the first
- * command is queued, and is checked in the isr to
- * try and detect when the interrupt type is set
- * incorrectly, triggering an interrupt immediately.
- * This is now just set on a per-card-type basis.
- */
- p->unpause = config.unpause;
- p->startup = !0;
+ unsigned char targ_scratch, scsi_id;
+ unsigned short mask;
- /*
- * Register IRQ with the kernel _after_ the host information
- * is set up, in case we take an interrupt right away, due to
- * the interrupt type being set wrong.
- */
- if (request_irq(config.irq, aic7xxx_isr, SA_INTERRUPT, "aic7xxx")) {
- printk("aic7xxx couldn't register irq %d, ignoring\n",
- config.irq);
- return(0);
+ scsi_id = inb(SCSIID(base)) >> 0x04;
+ if (inb(SBLKCTL(base)) & 0x08)
+ {
+ scsi_id = scsi_id + 8;
}
- /*
- * Print out debugging information before re-enabling
- * the card - a lot of registers on it can't be read
- * when the sequencer is active.
- */
-#ifdef AIC7XXX_DEBUG
- debug_config(type, &config);
-#endif
+ mask = (0x01 << scsi_id);
- /*
- * Load the sequencer program, then re-enable the board -
- * resetting the AIC-7770 disables it, leaving the lights
- * on with nobody home. On the PCI bus you *may* be home,
- * but then your mailing address is dynamically assigned
- * so no one can find you anyway :-)
- */
- aic7xxx_loadram(base);
- if (type != T_294X)
- outb(1, O_BCTL(base)); /* ENABLE */
+ targ_scratch = inb(HA_TARG_SCRATCH(base) + scsi_id);
- /*
- * Set the host adapter registers to indicate that synchronous
- * negotiation should be attempted the first time the targets
- * are communicated with. Also initialize the active message
- * flag to indicate that there is no message.
- */
- outb(0xff, HA_NEEDSDTR(base));
- outb(0, HA_MSG_FLAGS(base));
+ if (p->wdtr_pending & mask)
+ {
+ /*
+ * note 8bit xfers and clear flag
+ */
+ targ_scratch = targ_scratch & 0x7F;
+ p->needwdtr = p->needwdtr & ~mask;
+ p->wdtr_pending = p->wdtr_pending & ~mask;
+ outb(targ_scratch, HA_TARG_SCRATCH(base) + scsi_id);
+ printk("aic7xxx: target %d refusing WIDE negotiation. Using "
+ "8 bit transfers\n", scsi_id);
+ }
+ else
+ {
+ if (p->sdtr_pending & mask)
+ {
+ /*
+ * note asynch xfers and clear flag
+ */
+ targ_scratch = targ_scratch & 0xF0;
+ p->needsdtr = p->needsdtr & ~mask;
+ p->sdtr_pending = p->sdtr_pending & ~mask;
+ outb(targ_scratch, HA_TARG_SCRATCH(base) + scsi_id);
+ printk("aic7xxx: target %d refusing syncronous negotiation. Using "
+ "asyncronous transfers\n", scsi_id);
+ }
+ /*
+ * Otherwise, we ignore it.
+ */
+ }
+ outb(targ_scratch, HA_TARG_SCRATCH(base) + scsi_id);
+ outb(targ_scratch, SCSIRATE(base));
+ break;
+ }
+
+ case BAD_STATUS:
+ scsi_id = inb(SCSIID(base)) >> 0x04;
+ scbptr = inb(SCBPTR(base));
+ scb = &(p->scb_array[scbptr]);
+ outb(0, HA_RETURN_1(base)); /* CHECK_CONDITION may change this */
+ if ((scb->state != SCB_ACTIVE) || (scb->cmd == NULL))
+ {
+ printk("aic7xxx_isr: referenced scb not valid "
+ "during seqint 0x%x scb(%d) state(%x), cmd(%x)\n",
+ intstat, scbptr, scb->state, (unsigned int) scb->cmd);
+ }
+ else
+ {
+ cmd = scb->cmd;
+ aic7xxx_getscb(base, scb);
+ aic7xxx_status(cmd) = scb->target_status;
+
+ cmd->result = cmd->result | scb->target_status;
+
+ /*
+ * This test is just here for debugging purposes.
+ * It will go away when the timeout problem is resolved.
+ */
+ switch (status_byte(scb->target_status))
+ {
+ case GOOD:
+ break;
+
+ case CHECK_CONDITION:
+ if ((aic7xxx_error(cmd) == 0) && !(cmd->flags & WAS_SENSE))
+ {
+ void *req_buf;
+#ifndef AIC7XXX_USE_SG
+ unsigned int req_buflen;
+#endif
- /*
- * For reconnecting targets, the sequencer code needs to
- * know how many SCBs it has to search through.
- */
- outb(config.maxscb, HA_SCBCOUNT(base));
+ /* Update the timeout for the SCSI command. */
+/* update_timeout(cmd, SENSE_TIMEOUT); */
- /*
- * 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(1);
-}
+ /* Send a sense command to the requesting target. */
+ cmd->flags = 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);
+
+#ifdef AIC7XXX_USE_SG
+ scb->sense_sg.address = (char *) &cmd->sense_buffer;
+ scb->sense_sg.length = sizeof(cmd->sense_buffer);
+ req_buf = &scb->sense_sg;
+#else
+ req_buf = &cmd->sense_buffer;
+ req_buflen = sizeof(cmd->sense_buffer);
+#endif
+ cmd->cmd_len = COMMAND_SIZE(cmd->cmnd[0]);
+ memset(scb, 0, SCB_DOWNLOAD_SIZE);
+ scb->target_channel_lun = ((cmd->target << 4) & 0xF0) |
+ ((cmd->channel & 0x01) << 3) | (cmd->lun & 0x07);
+ 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));
+#ifdef AIC7XXX_USE_SG
+ scb->SG_segment_count = 1;
+ memcpy (scb->SG_list_pointer, &req_buf,
+ sizeof(scb->SG_list_pointer));
+#else
+ scb->SG_segment_count = 0;
+ memcpy (scb->data_pointer, &req_buf,
+ sizeof(scb->data_pointer));
+ memcpy (scb->data_count, &req_buflen, 3);
+#endif
-int aic7xxx_detect(Scsi_Host_Template *template)
-{
- enum aha_type type;
- int found = 0, slot, base;
+ outb(SCBAUTO, SCBCNT(base));
+ asm volatile("cld\n\t"
+ "rep\n\t"
+ "outsb"
+ : /* no output */
+ :"S" (scb), "c" (SCB_DOWNLOAD_SIZE), "d" (SCBARRAY(base))
+ :"si", "cx", "dx");
+ outb(0, SCBCNT(base));
+ outb(SCB_LIST_NULL, (SCBARRAY(base) + 30));
- /*
- * EISA/VL-bus card signature probe.
- */
- for (slot = MINSLOT; slot <= MAXSLOT; slot++) {
-
- base = SLOTBASE(slot);
-
- if (check_region(O_MINREG(base),
- O_MAXREG(base)-O_MINREG(base)))
+ /*
+ * Add this SCB to the "waiting for selection" list.
+ */
+ head = inb(WAITING_SCBH(base));
+ tail = inb(WAITING_SCBT(base));
+ if (head & SCB_LIST_NULL)
+ { /* list is empty */
+ head = scb->position;
+ tail = SCB_LIST_NULL;
+ }
+ else
{
- /*
- * Some other driver has staked a
- * claim to this i/o region already.
- */
- continue;
+ if (tail & SCB_LIST_NULL)
+ { /* list has one element */
+ tail = scb->position;
+ outb(head, SCBPTR(base));
+ outb(tail, (SCBARRAY(base) + 30));
+ }
+ else
+ { /* list has more than one element */
+ outb(tail, SCBPTR(base));
+ tail = scb->position;
+ outb(tail, (SCBARRAY(base) + 30));
+ }
}
+ outb(head, WAITING_SCBH(base));
+ outb(tail, WAITING_SCBT(base));
+ outb(SEND_SENSE, HA_RETURN_1(base));
+ } /* first time sense, no errors */
+ else
+ {
+ /*
+ * Indicate that we asked for sense, have the sequencer do
+ * a normal command complete, and have the scsi driver handle
+ * this condition.
+ */
+ cmd->flags = cmd->flags | ASKED_FOR_SENSE;
+ }
+ break;
+
+ case BUSY:
+ printk("aic7xxx_isr: Target busy\n");
+ if (!aic7xxx_error(cmd))
+ {
+ aic7xxx_error(cmd) = DID_BUS_BUSY;
+ }
+ break;
+
+ case QUEUE_FULL:
+ printk("aic7xxx_isr: Queue full\n");
+ if (!aic7xxx_error(cmd))
+ {
+ aic7xxx_error(cmd) = DID_RETRY_COMMAND;
+ }
+ break;
- type = aic7xxx_probe(slot, O_BIDx(base));
-
- if (type != T_NONE) {
- /*
- * We "find" a 274x if we locate the card
- * signature and we can set it up and register
- * it with the kernel without incident.
- */
- found += aic7xxx_register(template, type, base);
- }
+ default:
+ printk("aic7xxx_isr: 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:
+ scbptr = inb(SCBPTR(base));
+ scb = &(p->scb_array[scbptr]);
+ if ((scb->state != SCB_ACTIVE) || (scb->cmd == NULL))
+ {
+ printk("aic7xxx_isr: referenced scb not valid "
+ "during seqint 0x%x scb(%d) state(%x), cmd(%x)\n",
+ intstat, scbptr, scb->state, (unsigned int) scb->cmd);
}
-
- /*
- * PCI-bus probe.
- */
- if (pcibios_present()) {
- int index = 0;
- unsigned char bus, device_fn;
-
- while (!pcibios_find_device(PCI_VENDOR_ID_ADAPTEC,
- PCI_DEVICE_ID_ADAPTEC_2940,
- index,
- &bus,
- &device_fn))
- {
- found += aic7xxx_register(template, T_294X,
- bus, device_fn);
- index += 1;
- }
+ 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(SCBARRAY(base + 17)) << 16) |
+ (inb(SCBARRAY(base + 16)) << 8) |
+ inb(SCBARRAY(base + 15)));
+
+ if (actual < cmd->underflow)
+ {
+ printk("aic7xxx: target %d underflow - "
+ "wanted (at least) %u, got %u\n",
+ cmd->target, cmd->underflow, actual);
+
+ aic7xxx_error(cmd) = DID_RETRY_COMMAND;
+ aic7xxx_status(cmd) = scb->target_status;
+ }
+ }
+ }
+ break;
+
+ case ABORT_TAG:
+ scbptr = inb(SCBPTR(base));
+ scb = &(p->scb_array[scbptr]);
+ if ((scb->state != SCB_ACTIVE) || (scb->cmd == NULL))
+ {
+ printk("aic7xxx_isr: referenced scb not valid "
+ "during seqint 0x%x scb(%d) state(%x), cmd(%x)\n",
+ intstat, scbptr, scb->state, (unsigned int) scb->cmd);
}
-
- template->name = (char *)aic7xxx_info(NULL);
- return(found);
-}
-
-const char *aic7xxx_info(struct Scsi_Host *notused)
-{
- return("Adaptec AHA274x/284x/294x (EISA/VL-bus/PCI -> Fast SCSI) "
- AIC7XXX_SEQ_VERSION "/"
- AIC7XXX_H_VERSION "/"
- "1.34");
-}
-
-static
-void aic7xxx_buildscb(struct aic7xxx_host *p,
- Scsi_Cmnd *cmd,
- struct aic7xxx_scb *scb)
-{
- void *addr;
- unsigned length;
-
- memset(scb, 0, sizeof(*scb));
-
- /*
- * NB. channel selection (bit 3) is always zero.
- */
- scb->target_channel_lun = ((cmd->target << 4) & 0xf0) |
- (cmd->lun & 0x7);
-
- /*
- * 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.
- *
- * The AIC-7770 can't support transfers of any sort larger
- * than 2^24 (three-byte count) without backflips. For what
- * the kernel is doing, this shouldn't occur. I hope.
- */
- length = aic7xxx_length(cmd, 0);
-
- /*
- * The sequencer code cannot yet handle scatter-gather segments
- * larger than 64k (two-byte length). The 1.1.x kernels, however,
- * have a four-byte length field in the struct scatterlist, so
- * make sure we don't exceed 64k on these kernels for now.
- */
- aic7xxx_sg_check(cmd);
-
- if (length > 0xffffff) {
- panic("aic7xxx_buildscb: can't transfer > 2^24 - 1 bytes\n");
+ else
+ {
+ cmd = scb->cmd;
+ /*
+ * We didn't recieve a valid tag back from the target
+ * on a reconnect.
+ */
+ printk("aic7xxx_isr: invalid tag recieved on channel %c "
+ "target %d, lun %d -- sending ABORT_TAG\n",
+ (cmd->channel & 0x01) ? 'B':'A',
+ cmd->target, cmd->lun & 0x07);
+ /*
+ * 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->cmd = NULL;
+ scb->next = p->free_scb; /* preserve next pointer */
+ p->free_scb = scb; /* add at head of list */
+
+ restore_flags (flags);
+ cmd->result = (DID_RETRY_COMMAND << 16);
+ cmd->scsi_done(cmd);
+ }
+ break;
+
+ case AWAITING_MSG:
+ scbptr = inb(SCBPTR(base));
+ scb = &(p->scb_array[scbptr]);
+ if ((scb->state != SCB_ACTIVE) || (scb->cmd == NULL))
+ {
+ printk("aic7xxx_isr: referenced scb not valid "
+ "during seqint 0x%x scb(%d) state(%x), cmd(%x)\n",
+ intstat, scbptr, 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)
+ {
+ outb(MSG_BUS_DEVICE_RESET, HA_MSG_START(base));
+ outb(1, HA_MSG_LEN(base));
+ }
+ else
+ {
+ panic ("aic7xxx_isr: AWAITING_SCB for an SCB that does "
+ "not have a waiting message");
+ }
+ }
+ break;
+
+ default: /* unknown */
+ debug("aic7xxx_isr: 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));
+
+ scbptr = inb(SCBPTR(base));
+ scb = &p->scb_array[scbptr];
+ if ((scb->state != SCB_ACTIVE) || (scb->cmd == NULL))
+ {
+ printk("aic7xxx_isr: 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));
+ status = 0;
+ 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 target_mask = (1 << (cmd->target & 0x07));
+ unsigned char waiting;
+
+ /*
+ * Hardware selection timer has expired. Turn
+ * off SCSI selection sequence.
+ */
+ outb(ENRSELI, SCSISEQ(base));
+ cmd->result = (DID_TIME_OUT << 16);
/*
- * XXX - this relies on the host data being stored in a
- * little-endian format.
+ * Clear an pending messages for the timed out
+ * target and mark the target as free.
*/
- addr = cmd->cmnd;
- scb->SCSI_cmd_length = cmd->cmd_len;
- memcpy(scb->SCSI_cmd_pointer, &addr, sizeof(scb->SCSI_cmd_pointer));
+ ha_flags = inb(HA_FLAGS(base));
+ outb(ha_flags & ~ACTIVE_MSG, HA_FLAGS(base));
- if (cmd->use_sg) {
-#if 0
- debug("aic7xxx_buildscb: SG used, %d segments, length %u\n",
- cmd->use_sg,
- length);
-#endif
- scb->SG_segment_count = cmd->use_sg;
- memcpy(scb->SG_list_pointer,
- &cmd->request_buffer,
- sizeof(scb->SG_list_pointer));
- } else {
- scb->SG_segment_count = 0;
- memcpy(scb->data_pointer,
- &cmd->request_buffer,
- sizeof(scb->data_pointer));
- memcpy(scb->data_count,
- &cmd->request_bufflen,
- sizeof(scb->data_count));
+ if (scb->target_channel_lun & 0x88)
+ {
+ active = inb(HA_ACTIVE1(base));
+ active = active & ~(target_mask);
+ outb(active, HA_ACTIVE1(base));
+ }
+ else
+ {
+ active = inb(HA_ACTIVE0(base));
+ active = active & ~(target_mask);
+ outb(active, HA_ACTIVE0(base));
}
-}
-
-static
-void aic7xxx_putscb(int base, struct aic7xxx_scb *scb)
-{
- /*
- * 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.
- */
- outb(0x80, O_SCBCNT(base)); /* SCBAUTO */
-
- asm volatile("cld\n\t"
- "rep\n\t"
- "outsb"
- : /* no output */
- :"S" (scb), "c" (sizeof(*scb)), "d" (O_SCBARRAY(base))
- :"si", "cx", "dx");
-
- outb(0, O_SCBCNT(base));
-}
-
-int aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *))
-{
- long flags;
- int empty, old_scbptr;
- struct aic7xxx_host *p;
- struct aic7xxx_scb scb;
-#if 0
- debug("aic7xxx_queue: cmd 0x%x (size %u), target %d, lun %d\n",
- cmd->cmnd[0],
- cmd->cmd_len,
- cmd->target,
- cmd->lun);
+#ifdef AIC7XXX_USE_DMA
+ outb(SCB_NEEDDMA, SCBARRAY(base));
#endif
- p = (struct aic7xxx_host *)cmd->host->hostdata;
-
- /*
- * Construct the SCB beforehand, so the sequencer is
- * paused a minimal amount of time.
- */
- aic7xxx_buildscb(p, cmd, &scb);
-
/*
- * Clear the startup flag - we can now legitimately
- * expect interrupts.
- */
- p->startup = 0;
+ * 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(SCBARRAY(base) + 30);
+ outb(waiting, WAITING_SCBH(base));
+ RESTART_SEQUENCER(p);
/*
- * 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.
+ * This is a critical section, since we don't want the
+ * queue routine mucking with the host data.
*/
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.
- */
- for (empty = 0; empty < p->maxscb; empty++)
- if (!p->SCB_array[empty])
- break;
- if (empty == p->maxscb)
- panic("aic7xxx_queue: couldn't find a free scb\n");
-
- /*
- * 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?
+ * Process the command after marking the scb as free
+ * and adding it to the free list.
*/
- PAUSE_SEQUENCER(p);
+ scb->state = SCB_FREE;
+ scb->cmd = NULL;
+ scb->next = p->free_scb; /* preserve next pointer */
+ p->free_scb = scb; /* add at head of list */
- /*
- * 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.
- */
- old_scbptr = inb(O_SCBPTR(p->base));
- outb(empty, O_SCBPTR(p->base));
-
- aic7xxx_putscb(p->base, &scb);
+ restore_flags(flags);
- outb(empty, O_QINFIFO(p->base));
- outb(old_scbptr, O_SCBPTR(p->base));
+ cmd->scsi_done(cmd);
+#if 0
+ printk("aic7xxx_isr: SELTO scb(%d) state(%x), cmd(%x)\n",
+ scb->position, scb->state, (unsigned int) scb->cmd);
+#endif
+ }
+ else
+ {
+ if (status & SCSIPERR)
+ {
+ /*
+ * A parity error has occurred during a data
+ * transfer phase. Flag it and continue.
+ */
+ printk("aic7xxx: parity error on target %d, "
+ "channel %d, lun %d\n",
+ cmd->target,
+ cmd->channel & 0x01,
+ cmd->lun & 0x07);
+ aic7xxx_error(cmd) = DID_PARITY;
+
+ /*
+ * Clear interrupt and resume as above.
+ */
+ outb(CLRSCSIPERR, CLRSINT1(base));
+ UNPAUSE_SEQUENCER(p);
- /*
- * 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;
- p->SCB_array[empty] = cmd;
- aic7xxx_parity(cmd) = DID_OK;
+ outb(CLRSCSIINT, CLRINT(base));
+ scb = NULL;
+ }
+ else
+ {
+ if (! (status & BUSFREE))
+ {
+ /*
+ * We don't know what's going on. Turn off the
+ * interrupt source and try to continue.
+ */
+ printk("aic7xxx_isr: sstat1 = 0x%x\n", status);
+ outb(status, CLRSINT1(base));
+ UNPAUSE_SEQUENCER(p);
+ outb(CLRSCSIINT, CLRINT(base));
+ scb = NULL;
+ }
+ }
+ }
+ } /* 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\n",
+ complete, inb(QOUTFIFO(base)),
+ scb->state, (unsigned int) scb->cmd);
+ outb(CLRCMDINT, CLRINT(base));
+ continue;
+ }
+ cmd = scb->cmd;
+
+ cmd->result = (aic7xxx_error(cmd) << 16) | aic7xxx_status(cmd);
+ if ((cmd->flags & WAS_SENSE) && !(cmd->flags & ASKED_FOR_SENSE))
+ { /* Got sense information. */
+ cmd->flags = cmd->flags & ASKED_FOR_SENSE;
+ }
+#if 0
+ printk("aic7xxx_intr: (complete) state = %d, cmd = 0x%x, free = 0x%x\n",
+ scb->state, (unsigned int) scb->cmd, (unsigned int) p->free_scb);
+#endif
+ /*
+ * This is a critical section, since we don't want the
+ * queue routine mucking with the host data.
+ */
+ save_flags(flags);
+ cli();
+
+ scb->state = SCB_FREE;
+ scb->next = p->free_scb;
+ scb->cmd = NULL;
+ p->free_scb = &(p->scb_array[scb->position]);
- UNPAUSE_SEQUENCER(p);
+ restore_flags(flags);
+#if 0
+ if (scb != &p->scb_array[scb->position])
+ {
+ printk("aic7xxx_isr: (complete) address mismatch, pos %d\n", scb->position);
+ }
+ printk("aic7xxx_isr: (complete) state = %d, cmd = 0x%x, free = 0x%x\n",
+ scb->state, (unsigned int) scb->cmd, (unsigned int) p->free_scb);
+#endif
- restore_flags(flags);
- return(0);
-}
+ cmd->scsi_done(cmd);
-/* return values from aic7xxx_kill */
+ /*
+ * 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));
+ } 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_274x }, /* host adapter 274x */
+ { 4, { 0x04, 0x90, 0x77, 0x70 }, AIC_274x }, /* motherboard 274x */
+ { 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_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, struct seeprom_config *sc)
+{
+ int i = 0, k = 0;
+ 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 = k + 1)
+ {
+ /* 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 = i + 1)
+ {
+ 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 = i - 1)
+ {
+ temp = k;
+ 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 = i + 1)
+ {
+ 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);
+ }
+
+ if (checksum != sc->checksum)
+ {
+ printk ("aic7xxx : SEEPROM checksum error, ignoring SEEPROM settings.\n");
+ return (0);
+ }
-enum k_state {
- k_ok, /* scb found and message sent */
- k_busy, /* message already present */
- k_absent, /* couldn't locate scb */
- k_disconnect, /* scb found, but disconnected */
-};
+#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 = k + 1)
+ {
+ if (((k % 8) == 0) && (k != 0))
+ {
+ printk ("\n ");
+ }
+ printk (" 0x%x", seeprom[k]);
+ }
+ printk ("\n");
+#endif
-/*
- * This must be called with interrupts disabled - it's going to
- * be messing around with the host data, and an interrupt being
- * fielded in the middle could get ugly.
- *
- * Since so much of the abort and reset code is shared, this
- * function performs more magic than it really should. If the
- * command completes ok, then it will call scsi_done with the
- * result code passed in. The unpause parameter controls whether
- * or not the sequencer gets unpaused - the reset function, for
- * instance, may want to do something more aggressive.
- *
- * Note that the command is checked for in our SCB_array first
- * before the sequencer is paused, so if k_absent is returned,
- * then the sequencer is NOT paused.
- */
-
-static
-enum k_state aic7xxx_kill(Scsi_Cmnd *cmd, unsigned char message,
- unsigned int result, int unpause)
-{
- struct aic7xxx_host *p;
- int i, scb, found, queued;
- unsigned char scbsave[AIC7XXX_MAXSCB];
+ /* Release access to the memory port and the serial EEPROM. */
+ outb(0, SEECTL(base));
+ return (1);
+}
- p = (struct aic7xxx_host *)cmd->host->hostdata;
+/*+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)
+{
+ unsigned char sblkctl_reg;
+ int maxscb = 0;
+
+ switch (type)
+ {
+ case AIC_274x:
+ 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 registor, 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("aic7770: Rev E and subsequent; using 4 SCB's\n");
+ outb(sblkctl_reg ^ AUTOFLUSHDIS, SBLKCTL(base));
+ maxscb = 4;
+ }
+ else
+ {
+ printk("aic7770: Rev C and previous; using 4 SCB's\n");
+ maxscb = 4;
+ }
+ break;
+
+ case AIC_7850:
+ maxscb = 3;
+ break;
+
+ case AIC_7870:
+ maxscb = 16;
+ break;
+
+ case AIC_7872:
+ /*
+ * Really has 255, but we'll wait to verify that we access
+ * them the same way and do not have to set the card to
+ * use the memory port to access external SCB RAM.
+ */
+ maxscb = 16;
+ break;
+
+ case AIC_NONE:
+ /*
+ * This should never happen... But just in case.
+ */
+ break;
+ }
+
+ 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, aha_type type,
+ int base, unsigned char irq)
+{
+ static char * board_name[] = {"", "274x", "284x", "7870", "7850", "7872"};
+ int i;
+ unsigned char sblkctl;
+ int max_targets;
+ int found = 1;
+ unsigned char target_settings;
+ unsigned char scsi_conf;
+ int have_seeprom = 0;
+ struct Scsi_Host *host;
+ struct aic7xxx_host *p;
+ struct aic7xxx_host_config config;
+ struct seeprom_config sc;
+
+ config.type = type;
+ config.base = base;
+ config.irq = irq;
+ config.parity = AIC_UNKNOWN;
+ config.low_term = AIC_UNKNOWN;
+ config.high_term = AIC_UNKNOWN;
+ config.busrtime = 0;
+
+ /*
+ * Lock out other contenders for our i/o space.
+ */
+ request_region(MINREG(base), MAXREG(base) - MINREG(base), "aic7xxx");
+
+ switch (type)
+ {
+ case AIC_274x:
+#if 1
+ printk("aha274x: aic7770 hcntrl=0x%x\n", inb(HCNTRL(config.base)));
+#endif
+ /*
+ * 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(config.base)) & IRQMS) | INTEN;
+ config.pause = config.unpause | PAUSE;
+ config.extended = aic7xxx_extended;
+
+ /*
+ * I don't think we need to kick the reset again, the initial probe
+ * does a reset, it seems that this is kicking a dead horse here.
+ * So... I will try to just verify that the chip has come out of the
+ * reset state and continue the same as the 284x.
+ * In the Calgary version of the driver:
+ * 1) Chip Reset
+ * 2) Set unpause to IRQMS | INTEN
+ * 3) If an interrupt occured without any commands queued, the
+ * unpause was set to just INTEN
+ * I changed the initial reset code to just mask in the CHIPRST bit
+ * and try to leave the other settings alone.
+ *
+ * I don't think we need the warning about chip reset not being clear.
+ * On both my test machines (2842 & 2940), they work just fine with a
+ * HCNTRL() of 0x5 (PAUSE | CHIPRST). Notice though, the 274x also
+ * adds the INTEN flag, where neither the 284x or 294x do.
+ */
+ outb(config.pause | CHIPRST, HCNTRL(config.base));
+ aic7xxx_delay(1);
+ if (inb(HCNTRL(config.base)) & CHIPRST)
+ {
+ printk("aic7xxx_register: Chip reset not cleared; clearing manually.\n");
+ }
+ outb(config.pause, HCNTRL(config.base));
+
+ /*
+ * Just to be on the safe side with the 274x, we will re-read the irq
+ * since there was some issue about reseting the board.
+ */
+ config.irq = inb(HA_INTDEF(config.base)) & 0x0F;
+ config.busrtime = inb(HA_SCSICONF(config.base)) & 0x3C;
+
+ /*
+ * A reminder until this can be detected automatically.
+ */
+ printk("aha274x: extended translation %sabled\n",
+ config.extended ? "en" : "dis");
+ break;
+
+ case AIC_284x:
+#if 1
+ printk("aha284x: aic7770 hcntrl=0x%x\n", inb(HCNTRL(config.base)));
+#endif
+ outb(CHIPRST, HCNTRL(config.base));
+ config.unpause = UNPAUSE_284X;
+ config.pause = REQ_PAUSE; /* DWG would like to be like the rest */
+ config.extended = aic7xxx_extended;
+ config.irq = inb(HA_INTDEF(config.base)) & 0x0F;
+
+ /*
+ * A reminder until this can be detected automatically.
+ */
+ printk("aha284x: extended translation %sabled\n",
+ config.extended ? "en" : "dis");
+ break;
+
+ case AIC_7850:
+ case AIC_7870:
+ case AIC_7872:
+#if 1
+ printk("aic%s hcntrl=0x%x\n", board_name[type], inb(HCNTRL(config.base)));
+#endif
- /*
- * If we can't find the command, assume it just completed
- * and shrug it away.
- */
- for (scb = 0; scb < p->maxscb; scb++)
- if (p->SCB_array[scb] == cmd)
- break;
+ outb(CHIPRST, HCNTRL(config.base));
+ config.unpause = UNPAUSE_294X;
+ config.pause = config.unpause | PAUSE;
+ config.extended = aic7xxx_extended;
+ config.scsi_id = 7;
+
+ printk ("aic78xx: Reading SEEPROM... ");
+ have_seeprom = read_seeprom(base, &sc);
+ if (! have_seeprom)
+ {
+ printk ("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;
+ }
+
+ /*
+ * XXX - force data fifo threshold to 100%. Why does this
+ * need to be done?
+ */
+ outb(inb(DSPCISTATUS(config.base)) | DFTHRESH, DSPCISTATUS(config.base));
+ outb(config.scsi_id | DFTHRESH, HA_SCSICONF(config.base));
+
+ /*
+ * In case we are a wide card, place scsi ID in second conf byte.
+ */
+ outb(config.scsi_id, (HA_SCSICONF(config.base) + 1));
+
+ /*
+ * A reminder until this can be detected automatically.
+ */
+ printk("aic%s: extended translation %sabled\n", board_name[type],
+ config.extended ? "en" : "dis");
+ break;
+
+ default:
+ panic("aic7xxx_register: internal error\n");
+ }
+
+ config.maxscb = detect_maxscb(type, base);
+
+ if ((config.type == AIC_274x) || (config.type == AIC_284x))
+ {
+ 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)) & 0x0F; /* mask out upper two bits */
+ switch (sblkctl)
+ {
+ case 0: /* narrow/normal bus */
+ config.scsi_id = inb(HA_SCSICONF(base)) & 0x07;
+ config.bus_type = AIC_SINGLE;
+ outb(0, HA_FLAGS(base));
+ break;
+
+ case 2: /* Wide bus */
+ config.scsi_id = inb(HA_SCSICONF(base) + 1) & 0x0F;
+ config.bus_type = AIC_WIDE;
+ printk("aic7xxx : Enabling wide channel of %s-Wide\n",
+ board_name[config.type]);
+ outb(WIDE_BUS, HA_FLAGS(base));
+ break;
+
+ case 8: /* Twin bus */
+ config.scsi_id = inb(HA_SCSICONF(base)) & 0x07;
+#ifdef AIC7XXX_TWIN_SUPPORT
+ config.scsi_id_b = inb(HA_SCSICONF(base) + 1) & 0x07;
+ config.bus_type = AIC_TWIN;
+ printk("aic7xxx : Enabled channel B of %s-Twin\n",
+ board_name[config.type]);
+ outb(TWIN_BUS, HA_FLAGS(base));
+#else
+ config.bus_type = AIC_SINGLE;
+ printk("aic7xxx : Channel B of %s-Twin will be ignored\n",
+ board_name[config.type]);
+ outb(0, HA_FLAGS(base));
+#endif
+ break;
- if (scb == p->maxscb)
- return(k_absent);
+ default:
+ printk("aic7xxx is an unsupported type 0x%x, please "
+ "mail deang@ims.com\n", inb(SBLKCTL(base)));
+ outb(0, HA_FLAGS(base));
+ return(0);
+ }
+
+ /*
+ * Clear the upper two bits. For the 294x cards, clearing the
+ * upper two bits, will take the card out of diagnostic mode
+ * and make the host adatper LED follow bus activity (will not
+ * always be on).
+ */
+ outb(sblkctl, 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 294x cards (PCI) get their interrupt from PCI BIOS.
+ */
+ if (((config.type == AIC_274x) || (config.type == AIC_284x))
+ && (config.irq < 9 || config.irq > 15))
+ {
+ printk("aic7xxx 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_register: Sharing of IRQs is not configured.\n");
+ return(0);
+ }
+#endif
- PAUSE_SEQUENCER(p);
+ /*
+ * 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;
+#ifdef AIC7XXX_TAGGED_QUEUEING
+ host->cmd_per_lun = 2;
+#else
+ host->cmd_per_lun = 1;
+#endif
+ 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 = i + 1)
+ {
+ 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 = 0;
+ p->b_scanned = 0;
+ p->base = config.base;
+ p->maxscb = config.maxscb;
+ p->numscb = 0;
+ p->extended = config.extended;
+ p->type = config.type;
+ 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)
+ {
+ /*
+ * 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);
+ return(0);
+ }
+ aic7xxx_boards[config.irq] = host;
+ }
+ 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->type == AIC_274x || p->type == AIC_284x))
+ {
+ outb(ENABLE, BCTL(base));
+ }
+
+ printk("done.\n");
+
+ /*
+ * Set the SCSI Id, SXFRCTL1, and SIMODE1, for both channels
+ */
+ if (p->bus_type == AIC_TWIN)
+ {
+ /*
+ * The device is gated to channel B after a chip reset,
+ * so set those values first.
+ */
+ outb(config.scsi_id_b, SCSIID(base));
+ scsi_conf = inb(HA_SCSICONF(base) + 1) & (ENSPCHK | STIMESEL);
+ scsi_conf = scsi_conf | ENSTIMER | ACTNEGEN | STPWEN;
+ outb(scsi_conf, SXFRCTL1(base));
+ outb(ENSELTIMO | ENSCSIPERR, SIMODE1(base));
+ /* Select Channel A */
+ outb(0, SBLKCTL(base));
+ }
+ outb(config.scsi_id, SCSIID(base));
+ scsi_conf = inb(HA_SCSICONF(base)) & (ENSPCHK | STIMESEL);
+ outb(scsi_conf | ENSTIMER | ACTNEGEN | STPWEN, SXFRCTL1(base));
+ outb(ENSELTIMO | ENSCSIPERR, SIMODE1(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 = 0;
+ p->sdtr_pending = 0;
+ p->needwdtr_copy = 0;
+ p->wdtr_pending = 0;
+ if (p->bus_type == AIC_SINGLE)
+ {
+ max_targets = 8;
+ }
+ else
+ {
+ max_targets = 16;
+ }
+
+ for (i = 0; i < max_targets; i = i + 1)
+ {
+ if (have_seeprom)
+ {
+ target_settings = (sc.device_flags[i] & CFXFER) << 4;
+ if (sc.device_flags[i] & CFSYNCH)
+ {
+ p->needsdtr_copy = p->needsdtr_copy | (0x01 << i);
+ }
+ if ((sc.device_flags[i] & CFWIDEB) && (p->bus_type == AIC_WIDE))
+ {
+ p->needwdtr_copy = p->needwdtr_copy | (0x01 << i);
+ }
+ }
+ else
+ {
+ target_settings = inb(HA_TARG_SCRATCH(base) + i);
+ if (target_settings & 0x0F)
+ {
+ p->needsdtr_copy = p->needsdtr_copy | (0x01 << i);
+ /*
+ * Default to asynchronous transfers (0 offset)
+ */
+ target_settings = target_settings & 0xF0;
+ }
+ /*
+ * 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 ((target_settings & 0x80) && (p->bus_type == AIC_WIDE))
+ {
+ p->needwdtr_copy = p->needwdtr_copy | (0x01 << i);
+ target_settings = target_settings & 0x7F;
+ }
+ }
+ outb(target_settings, (HA_TARG_SCRATCH(base) + i));
+ }
+
+ p->needsdtr = p->needsdtr_copy;
+ p->needwdtr = p->needwdtr_copy;
+ printk("NeedSdtr = 0x%x, 0x%x\n", p->needsdtr_copy, p->needsdtr);
+ printk("NeedWdtr = 0x%x, 0x%x\n", p->needwdtr_copy, p->needwdtr);
+
+ /*
+ * 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 = i + 1)
+ {
+ 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, HA_SCBCOUNT(base));
+
+ /*
+ * Clear the active flags - no targets are busy.
+ */
+ outb(0, HA_ACTIVE0(base));
+ outb(0, HA_ACTIVE1(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("Resetting the SCSI bus...\n");
+ outb(SCSIRSTO, SCSISEQ(base));
+ udelay(1000);
+ outb(0, SCSISEQ(base));
+ aic7xxx_delay(AIC7XXX_RESET_DELAY);
+ }
+
+ /*
+ * 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)
+{
+ aha_type type = AIC_NONE;
+ int found = 0, slot, base;
+ unsigned char irq = 0;
+ int i;
+
+ /*
+ * 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;
+ }
+
+ /*
+ * EISA/VL-bus card signature probe.
+ */
+ for (slot = MINSLOT; slot <= MAXSLOT; slot++)
+ {
+ base = SLOTBASE(slot);
+
+ if (check_region(MINREG(base), MAXREG(base) - MINREG(base)))
+ {
+ /*
+ * Some other driver has staked a
+ * claim to this i/o region already.
+ */
+ continue;
+ }
+
+ type = aic7xxx_probe(slot, HID0(base));
+ if (type != AIC_NONE)
+ {
+ printk("aic7xxx: hcntrl=0x%x\n", inb(HCNTRL(base)));
+#if 0
+ outb(inb(HCNTRL(base)) | CHIPRST, HCNTRL(base));
+ irq = inb(HA_INTDEF(base)) & 0x0F;
+#endif
- /*
- * This is the best case, really. Check to see if the
- * command is still in the sequencer's input queue. If
- * so, simply remove it. Reload the queue afterward.
- */
- queued = inb(O_QINCNT(p->base));
-
- for (i = found = 0; i < queued; i++) {
- scbsave[i] = inb(O_QINFIFO(p->base));
-
- if (scbsave[i] == scb) {
- found = 1;
- i -= 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.
+ */
+ found += aic7xxx_register(template, type, base, irq);
+ }
+ }
+
+#ifdef CONFIG_PCI
+
+#define DEVREVID 0x08
+#define DEVCONFIG 0x40
+#define DEVSTATUS 0x41
+#define RAMPSM 0x02
+
+/* This should be defined in pci.h */
+#define PCI_DEVICE_ID_ADAPTEC_7850 0x5078
+#define PCI_DEVICE_ID_ADAPTEC_7872 0x7278
+
+ /*
+ * PCI-bus probe.
+ */
+ if (pcibios_present())
+ {
+ int error;
+ int done = 0;
+ unsigned int io_port;
+ unsigned short index = 0;
+ unsigned char pci_bus, pci_device_fn;
+ unsigned char devrevid, devconfig, devstatus;
+ char rev_id[] = {'B', 'C', 'D'};
+
+ while (!done)
+ {
+ if ((!pcibios_find_device(PCI_VENDOR_ID_ADAPTEC,
+ PCI_DEVICE_ID_ADAPTEC_294x,
+ index, &pci_bus, &pci_device_fn)) ||
+ (!pcibios_find_device(PCI_VENDOR_ID_ADAPTEC,
+ PCI_DEVICE_ID_ADAPTEC_2940,
+ index, &pci_bus, &pci_device_fn)))
+ {
+ type = AIC_7870;
+ }
+ else
+ {
+ if (!pcibios_find_device(PCI_VENDOR_ID_ADAPTEC,
+ PCI_DEVICE_ID_ADAPTEC_7850,
+ index, &pci_bus, &pci_device_fn))
+ {
+ type = AIC_7850;
}
-
- queued -= found;
- for (i = 0; i < queued; i++)
- outb(scbsave[i], O_QINFIFO(p->base));
-
- if (found)
- goto complete;
-
+ else
+ {
+ if (!pcibios_find_device(PCI_VENDOR_ID_ADAPTEC,
+ PCI_DEVICE_ID_ADAPTEC_7872,
+ index, &pci_bus, &pci_device_fn))
+ {
+ type = AIC_7872;
+ }
+ else
+ {
+ type = AIC_NONE;
+ done = 1;
+ }
+ }
+ }
+
+ if (!done)
+ {
/*
- * Check the current SCB bank. If it's not the one belonging
- * to the command we want to kill, assume that the command
- * is disconnected. It's rather a pain to force a reconnect
- * and send a message to the target, so we abdicate responsibility
- * in this case.
+ * Read esundry information from PCI BIOS.
*/
- if (inb(O_SCBPTR(p->base)) != scb) {
- if (unpause)
- UNPAUSE_SEQUENCER(p);
- return(k_disconnect);
- }
+ error = pcibios_read_config_dword(pci_bus, pci_device_fn,
+ PCI_BASE_ADDRESS_0, &io_port);
- /*
- * Presumably at this point our target command is active. Check
- * to see if there's a message already in effect. If not, place
- * our message in and assert ATN so the target goes into MESSAGE
- * OUT phase.
- */
- if (inb(HA_MSG_FLAGS(p->base)) & 0x80) {
- if (unpause)
- UNPAUSE_SEQUENCER(p);
- return(k_busy);
+ if (error)
+ {
+ panic("aic7xxx_detect: error 0x%x reading i/o port.\n", error);
}
- outb(0x80, HA_MSG_FLAGS(p->base)); /* active message */
- outb(1, HA_MSG_LEN(p->base)); /* length = 1 */
- outb(message, HA_MSG_START(p->base)); /* message body */
-
- /*
- * Assert ATN. Use the value of SCSISIGO saved by the
- * sequencer code so we don't alter its contents radically
- * in the middle of something critical.
- */
- outb(inb(HA_SIGSTATE(p->base)) | 0x10, O_SCSISIGO(p->base));
+ error = pcibios_read_config_byte(pci_bus, pci_device_fn,
+ PCI_INTERRUPT_LINE, &irq);
+ if (error)
+ {
+ panic("aic7xxx_detect: error %d reading irq.\n", error);
+ }
/*
- * The command has been killed. Do the bookkeeping, unpause
- * the sequencer, and notify the higher-level SCSI code.
+ * Make the base I/O register look like EISA and VL-bus.
*/
-complete:
- p->SCB_array[scb] = NULL;
- if (unpause)
- UNPAUSE_SEQUENCER(p);
-
- cmd->result = result << 16;
- cmd->scsi_done(cmd);
- return(k_ok);
-}
-
-int aic7xxx_abort(Scsi_Cmnd *cmd)
-{
- int rv;
- long flags;
+ base = io_port - 0xC01;
- save_flags(flags);
- cli();
+ printk("aic7xxx: hcntrl=0x%x\n", inb(HCNTRL(base)));
+ outb(inb(HCNTRL(base)) | CHIPRST, HCNTRL(base));
- switch (aic7xxx_kill(cmd, ABORT, DID_ABORT, !0)) {
- case k_ok: rv = SCSI_ABORT_SUCCESS; break;
- case k_busy: rv = SCSI_ABORT_BUSY; break;
- case k_absent: rv = SCSI_ABORT_NOT_RUNNING; break;
- case k_disconnect: rv = SCSI_ABORT_SNOOZE; break;
- default:
- panic("aic7xxx_do_abort: internal error\n");
+ error = pcibios_read_config_byte(pci_bus, pci_device_fn,
+ DEVREVID, &devrevid);
+ if (devrevid < 3)
+ {
+ printk ("aic7xxx_detect: AIC-7870 Rev %c\n", rev_id[devrevid]);
+ }
+ error = pcibios_read_config_byte(pci_bus, pci_device_fn,
+ DEVCONFIG, &devconfig);
+ error = pcibios_read_config_byte(pci_bus, pci_device_fn,
+ DEVSTATUS, &devstatus);
+ printk ("aic7xxx_detect: devconfig 0x%x, devstatus 0x%x\n",
+ devconfig, devstatus);
+ if (devstatus & RAMPSM)
+ {
+ printk ("aic7xxx_detect: detected external SCB RAM, "
+ "mail deang@ims.com for test patch");
}
- restore_flags(flags);
- return(rv);
-}
+ found += aic7xxx_register(template, type, base, irq);
+ index += 1;
+ }
+ }
+ }
+#endif CONFIG_PCI
-/*
- * 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.
- */
+ template->name = (char *) aic7xxx_info(NULL);
+ return(found);
+}
-int aic7xxx_reset(Scsi_Cmnd *cmd)
-{
- int i;
- long flags;
- Scsi_Cmnd *reset;
- struct aic7xxx_host *p;
- p = (struct aic7xxx_host *)cmd->host->hostdata;
- save_flags(flags);
- cli();
+/*+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 length;
+ unsigned short mask;
+
+ /*
+ * 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_buildscb: 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 = cmd->device->current_tag + 1;
+ scb->control = scb->control | SCB_TE;
+ }
+#endif
+ mask = (0x01 << cmd->target);
+ if ((p->needwdtr & mask) && !(p->wdtr_pending & mask))
+ {
+ p->wdtr_pending = p->wdtr_pending | mask;
+ scb->control = scb->control | SCB_NEEDWDTR;
+#if 0
+ printk("Sending WDTR request to target %d.\n", cmd->target);
+#endif
+ }
+ else
+ {
+ if ((p->needsdtr & mask) && !(p->sdtr_pending & mask))
+ {
+ p->sdtr_pending = p->sdtr_pending | mask;
+ scb->control = scb->control | SCB_NEEDSDTR;
+#if 0
+ printk("Sending SDTR request to target %d.\n", cmd->target);
+#endif
+ }
+ }
- switch (aic7xxx_kill(cmd, BUS_DEVICE_RESET, DID_RESET, 0)) {
+#if 0
+ printk("aic7xxx_queue: 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);
- case k_ok:
- /*
- * The RESET message was sent to the target
- * with no problems. Flag that target as
- * needing a SDTR negotiation on the next
- * connection and restart the sequencer.
- */
- outb((1 << cmd->target), HA_NEEDSDTR(p->base));
- UNPAUSE_SEQUENCER(p);
- break;
+ /*
+ * 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.
+ *
+ * The AIC-7770 can't support transfers of any sort larger
+ * than 2^24 (three-byte count) without backflips. For what
+ * the kernel is doing, this shouldn't occur. I hope.
+ */
+ length = aic7xxx_length(cmd, 0);
+
+ if (length > 0xFFFFFF)
+ {
+ panic("aic7xxx_buildscb: can't transfer > 2^24 - 1 bytes\n");
+ }
+
+ /*
+ * 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));
- case k_absent:
- /*
- * The sequencer will not be paused if aic7xxx_kill()
- * couldn't find the command.
- */
- PAUSE_SEQUENCER(p);
- /* falls through */
+ if (cmd->use_sg)
+ {
+#if 0
+ debug("aic7xxx_buildscb: SG used, %d segments, length %u\n",
+ cmd->use_sg, length);
+#endif
+ scb->SG_segment_count = cmd->use_sg;
+ memcpy(scb->SG_list_pointer, &cmd->request_buffer,
+ sizeof(scb->SG_list_pointer));
+ }
+ else
+ {
+#if 0
+ debug ("aic7xxx_buildscb: Creating scatterlist, addr=0x%lx, length=%d.\n",
+ (unsigned long) cmd->request_buffer, cmd->request_bufflen);
+#endif
+#ifdef AIC7XXX_USE_SG
+ 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));
+#else
+ scb->SG_segment_count = 0;
+ memcpy(scb->data_pointer, &cmd->request_buffer, sizeof(scb->data_pointer));
+ memcpy(scb->data_count, &cmd->request_bufflen, 3);
+#endif
+ }
+}
- case k_busy:
- case k_disconnect:
- /*
- * Do a hard reset of the SCSI bus. According to the
- * SCSI-2 draft specification, reset has to be asserted
- * for at least 25us. I'm invoking the kernel delay
- * function for 30us since I'm not totally trusting of
- * the busy loop timing.
- *
- * XXX - I'm not convinced this works. I tried resetting
- * the bus before, trying to get the devices on the
- * bus to revert to asynchronous transfer, and it
- * never seemed to work.
- */
- debug("aic7xxx: attempting to reset scsi bus and card\n");
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_queue
+ *
+ * Description:
+ * Queue a SCB to the controller.
+ *-F*************************************************************************/
+int
+aic7xxx_queue(Scsi_Cmnd *cmd, void (*fn)(Scsi_Cmnd *))
+{
+ long flags;
+#ifndef AIC7XXX_USE_DMA
+ int old_scbptr;
+#endif
+ struct aic7xxx_host *p;
+ struct aic7xxx_scb *scb;
+ unsigned char curscb;
+
+ 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 = 1;
+ }
+ else
+ {
+ if (!p->b_scanned && (cmd->channel == 1))
+ {
+ printk("aic7xxx: Scanning channel B for devices.\n");
+ p->b_scanned = 1;
+ }
+ }
- outb(1, O_SCSISEQ(p->base)); /* SCSIRSTO */
- udelay(30);
- outb(0, O_SCSISEQ(p->base)); /* !SCSIRSTO */
+#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
- outb(0xff, HA_NEEDSDTR(p->base));
- UNPAUSE_SEQUENCER(p);
+ /*
+ * 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_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 = p->numscb + 1;
+ scb->state = SCB_ACTIVE;
+ scb->next_waiting = SCB_LIST_NULL;
+ memcpy(scb->host_scb, &scb, sizeof(scb));
+#ifdef AIC7XXX_USE_DMA
+ scb->control = SCB_NEEDDMA;
+#endif
+ PAUSE_SEQUENCER(p);
+ curscb = inb(SCBPTR(p->base));
+ outb(scb->position, SCBPTR(p->base));
+ aic7xxx_putdmascb(p->base, scb);
+ outb(curscb, SCBPTR(p->base));
+ UNPAUSE_SEQUENCER(p);
+ scb->control = 0;
+ }
+ }
+
+ scb->cmd = cmd;
+ aic7xxx_position(cmd) = scb->position;
+
+ /*
+ * Construct the SCB beforehand, so the sequencer is
+ * paused a minimal amount of time.
+ */
+ aic7xxx_buildscb(p, cmd, scb);
- /*
- * Locate the command and return a "reset" status
- * for it. This is not completely correct and will
- * probably return to haunt me later.
- */
- for (i = 0; i < p->maxscb; i++) {
- if (cmd == p->SCB_array[i]) {
- reset = (Scsi_Cmnd *)p->SCB_array[i];
- p->SCB_array[i] = NULL;
- reset->result = DID_RESET << 16;
- reset->scsi_done(reset);
- break;
- }
- }
- break;
+#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.
+ */
+#ifdef AIC7XXX_USE_DMA
+ aic7xxx_putscb(p->base, scb);
+#else
+ old_scbptr = inb(SCBPTR(p->base));
+ outb(scb->position, SCBPTR(p->base));
- default:
- panic("aic7xxx_reset: internal error\n");
- }
+ aic7xxx_putscb(p->base, scb);
- restore_flags(flags);
- return(SCSI_RESET_SUCCESS);
+ outb(scb->position, QINFIFO(p->base));
+ outb(old_scbptr, SCBPTR(p->base));
+#endif
+ /*
+ * 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);
+ restore_flags(flags);
+ return(0);
}
-int aic7xxx_biosparam(Disk *disk, int devno, int geom[])
-{
- int heads, sectors, cylinders;
- struct aic7xxx_host *p;
-
- p = (struct aic7xxx_host *)disk->device->host->hostdata;
+/* return values from aic7xxx_kill */
+typedef enum {
+ k_ok, /* scb found and message sent */
+ k_busy, /* message already present */
+ k_absent, /* couldn't locate scb */
+ k_disconnect, /* scb found, but disconnected */
+} k_state;
+
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_kill
+ *
+ * Description:
+ * This must be called with interrupts disabled - it's going to
+ * be messing around with the host data, and an interrupt being
+ * fielded in the middle could get ugly.
+ *
+ * Since so much of the abort and reset code is shared, this
+ * function performs more magic than it really should. If the
+ * command completes ok, then it will call scsi_done with the
+ * result code passed in. The unpause parameter controls whether
+ * or not the sequencer gets unpaused - the reset function, for
+ * instance, may want to do something more aggressive.
+ *
+ * Note that the command is checked for in our SCB_array first
+ * before the sequencer is paused, so if k_absent is returned,
+ * then the sequencer is NOT paused.
+ *-F*************************************************************************/
+static k_state
+aic7xxx_kill(Scsi_Cmnd *cmd, unsigned char message,
+ unsigned int result, int unpause)
+{
+ struct aic7xxx_host *p;
+ struct aic7xxx_scb *scb;
+ int i, active_scb, found, queued;
+ unsigned char scbsave[AIC7XXX_MAXSCB];
+ unsigned char flags;
+ int scb_control;
+ k_state status;
- /*
- * 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);
+ p = (struct aic7xxx_host *) cmd->host->hostdata;
+ scb = &p->scb_array[aic7xxx_position(cmd)];
- if (p->extended && cylinders > 1024) {
- heads = 255;
- sectors = 63;
- cylinders = disk->capacity / (255 * 63);
- }
+#if 0
+ printk("aic7xxx_kill: In the kill function...\n");
+#endif
+ PAUSE_SEQUENCER(p);
- geom[0] = heads;
- geom[1] = sectors;
- geom[2] = cylinders;
+ /*
+ * Case 1: In the QINFIFO
+ *
+ * This is the best case, really. Check to see if the
+ * command is still in the sequencer's input queue. If
+ * so, simply remove it. Reload the queue afterward.
+ */
+ queued = inb(QINCNT(p->base));
+
+ for (i = found = 0; i < (queued - found); i++)
+ {
+ scbsave[i] = inb(QINFIFO(p->base));
+
+ if (scbsave[i] == scb->position)
+ {
+ found = 1;
+ i = i - 1;
+ }
+ }
+
+ for (queued = 0; queued < i; queued++)
+ {
+ outb(scbsave[queued], QINFIFO(p->base));
+ }
+
+ if (found)
+ {
+ status = k_ok;
+ goto complete;
+ }
+
+ active_scb = inb(SCBPTR(p->base));
+ /*
+ * Case 2: Not the active command
+ *
+ * Check the current SCB bank. If it's not the one belonging
+ * to the command we want to kill, select the scb we want to
+ * abort and turn off the disconnected bit. The driver will
+ * then abort the command and notify us of the abort.
+ */
+ if (active_scb != scb->position)
+ {
+ outb(scb->position, SCBPTR(p->base));
+ scb_control = inb(SCBARRAY(p->base));
+ scb_control = scb_control & ~SCB_DIS;
+ outb(scb_control, SCBARRAY(p->base));
+ outb(active_scb, SCBPTR(p->base));
+ status = k_disconnect;
+ goto complete;
+ }
+
+ scb_control = inb(SCBARRAY(p->base));
+ if (scb_control & SCB_DIS)
+ {
+ scb_control = scb_control & ~SCB_DIS;
+ outb(scb_control, SCBARRAY(p->base));
+ status = k_disconnect;
+ goto complete;
+ }
+
+ /*
+ * Presumably at this point our target command is active. Check
+ * to see if there's a message already in effect. If not, place
+ * our message in and assert ATN so the target goes into MESSAGE
+ * OUT phase.
+ */
+ flags = inb(HA_FLAGS(p->base));
+ if (flags & ACTIVE_MSG)
+ {
+ /*
+ * If there is a message in progress, reset the bus
+ * and have all devices renegotiate.
+ */
+ if (cmd->channel & 0x01)
+ {
+ p->needsdtr = p->needsdtr_copy & 0xFF00;
+ p->sdtr_pending = p->sdtr_pending & 0x00FF;
+ outb(0, HA_ACTIVE1(p->base));
+ }
+ else
+ {
+ if (p->bus_type == AIC_WIDE)
+ {
+ p->needsdtr = p->needsdtr_copy;
+ p->needwdtr = p->needwdtr_copy;
+ p->sdtr_pending = 0;
+ p->wdtr_pending = 0;
+ outb(0, HA_ACTIVE0(p->base));
+ outb(0, HA_ACTIVE1(p->base));
+ }
+ else
+ {
+ p->needsdtr = p->needsdtr_copy & 0x00FF;
+ p->sdtr_pending = p->sdtr_pending & 0xFF00;
+ outb(0, HA_ACTIVE0(p->base));
+ }
+ }
+ /* Reset the bus. */
+ outb(SCSIRSTO, SCSISEQ(p->base));
+ udelay(1000);
+ outb(0, SCSISEQ(p->base));
+ aic7xxx_delay(AIC7XXX_RESET_DELAY);
+
+ status = k_busy;
+ goto complete;
+ }
+
+ outb(flags | ACTIVE_MSG, HA_FLAGS(p->base)); /* active message */
+ outb(1, HA_MSG_LEN(p->base)); /* length = 1 */
+ outb(message, HA_MSG_START(p->base)); /* message body */
+
+ /*
+ * Assert ATN. Use the value of SCSISIGO saved by the
+ * sequencer code so we don't alter its contents radically
+ * in the middle of something critical.
+ */
+ outb(inb(HA_SIGSTATE(p->base)) | 0x10, SCSISIGO(p->base));
+
+ status = k_ok;
+
+ /*
+ * The command has been killed. Do the bookkeeping, unpause
+ * the sequencer, and notify the higher-level SCSI code.
+ */
+complete:
+ if (unpause)
+ {
+ UNPAUSE_SEQUENCER(p);
+ }
+
+ /*
+ * Mark the scb as free and clear the scbs command pointer.
+ * Add the scb to the head of the free list being careful
+ * to preserve the next pointers.
+ */
+ scb->state = SCB_FREE; /* mark the scb as free */
+ scb->cmd = NULL; /* clear the command pointer */
+ scb->next = p->free_scb; /* preserve next pointer */
+ p->free_scb = scb; /* add at head of free list */
+ cmd->result = cmd->result << 16;
+ cmd->scsi_done(cmd);
+ return(status);
+}
+
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_abort
+ *
+ * Description:
+ * Abort the current SCSI command(s).
+ *-F*************************************************************************/
+int
+aic7xxx_abort(Scsi_Cmnd *cmd)
+{
+ int rv;
+ long flags;
+
+ save_flags(flags);
+ cli();
+
+ switch (aic7xxx_kill(cmd, ABORT, DID_ABORT, !0))
+ {
+ case k_ok: rv = SCSI_ABORT_SUCCESS; break;
+ case k_busy: rv = SCSI_ABORT_BUSY; break;
+ case k_absent: rv = SCSI_ABORT_NOT_RUNNING; break;
+ case k_disconnect: rv = SCSI_ABORT_SNOOZE; break;
+ default: panic("aic7xxx_abort: internal error\n");
+ }
+
+ restore_flags(flags);
+ return(rv);
+}
+
+/*+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)
+{
+ long flags;
+ struct aic7xxx_host *p;
+
+ p = (struct aic7xxx_host *) cmd->host->hostdata;
+ save_flags(flags);
+ cli();
+
+ switch (aic7xxx_kill(cmd, BUS_DEVICE_RESET, DID_RESET, 0))
+ {
+ case k_ok:
+ /*
+ * The RESET message was sent to the target
+ * with no problems. Flag that target as
+ * needing a SDTR negotiation on the next
+ * connection and restart the sequencer.
+ */
+ p->needsdtr = p->needsdtr & (1 << cmd->target);
+ UNPAUSE_SEQUENCER(p);
+ break;
+
+ case k_absent:
+ /*
+ * The sequencer will not be paused if aic7xxx_kill()
+ * couldn't find the command.
+ */
+ PAUSE_SEQUENCER(p);
+ /* falls through */
+
+ case k_busy:
+ cmd->result = DID_RESET << 16; /* return reset code */
+ cmd->scsi_done(cmd);
+ break;
+
+ case k_disconnect:
+ /*
+ * Do a hard reset of the SCSI bus. According to the
+ * SCSI-2 draft specification, reset has to be asserted
+ * for at least 25us. I'm invoking the kernel delay
+ * function for 30us since I'm not totally trusting of
+ * the busy loop timing.
+ *
+ * XXX - I'm not convinced this works. I tried resetting
+ * the bus before, trying to get the devices on the
+ * bus to revert to asynchronous transfer, and it
+ * never seemed to work.
+ */
+ debug("aic7xxx: attempting to reset scsi bus and card\n");
+
+ outb(SCSIRSTO, SCSISEQ(p->base));
+ udelay(1000);
+ outb(0, SCSISEQ(p->base));
+ aic7xxx_delay(AIC7XXX_RESET_DELAY);
+
+ UNPAUSE_SEQUENCER(p);
+
+ /*
+ * Locate the command and return a "reset" status
+ * for it. This is not completely correct and will
+ * probably return to haunt me later.
+ */
+ cmd->result = DID_RESET << 16; /* return reset code */
+ cmd->scsi_done(cmd);
+ break;
+
+ default:
+ panic("aic7xxx_reset: internal error\n");
+ }
+
+ restore_flags(flags);
+ return(SCSI_RESET_SUCCESS);
+}
+
+/*+F*************************************************************************
+ * Function:
+ * aic7xxx_biosparam
+ *
+ * Description:
+ * Return the disk geometry for the given SCSI device.
+ *-F*************************************************************************/
+int
+aic7xxx_biosparam(Disk *disk, int devno, 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 MODULE
+/* Eventually this will go into an include file, but this will be later */
+Scsi_Host_Template driver_template = AIC7XXX;
- return(0);
-}
+#include "scsi_module.c"
+#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov
with Sam's (original) version of this