Coherent4.2.10/conf/hai154x/src/hai154x.c
/* (-lgl
* Coherent 386 release 4.2
* Copyright (c) 1982, 1994 by Mark Williams Company.
* All rights reserved. May not be copied without permission.
* For copying permission and licensing info, write licensing@mwc.com
-lgl) */
/***********************************************************************
* Module: hai154x.c
*
* Host Adapter Interface routines for the Adaptec AHA154XX series
* of host adapters.
*
* Much of the information used to create this driver was obtained
* from the AHA1540B/1542B Technical Reference Manual available from
* Adaptec by phone or snail mail at:
*
* Adaptec - Literature Department
* 691 South Milpitas Blvd.
* Milpitas, CA 95035
* (408) 945-8600
*
* Copyright (c), 1993 Christopher Sean Hilton, All Rights Reserved.
*
* Last Modified: Wed Aug 25 07:19:10 1993 CDT
*/
#define HA_MODULE /* Host Adapter Module */
#include <stddef.h>
#include <sys/coherent.h>
#include <sys/con.h>
#include <sys/cmn_err.h>
#include <sys/devices.h>
#include <sys/types.h>
#include <sys/hdioctl.h>
#include <sys/haiscsi.h>
#include <sys/stat.h>
#define LOCAL static
#if __GNUC__
#define Register
#else
#define Register register
#endif
/*
* Configurable variables - see /etc/conf/hai/Space.c
*/
extern unsigned short hai154x_base;
extern unsigned short hai154x_intr;
extern unsigned short hai154x_dma;
extern unsigned short hai154x_haid;
#if 0
extern int HAI_SD_HDS;
extern int HAI_SD_SPT;
#endif
extern unsigned char hai154x_xferspeed;
extern unsigned char hai154x_busofftime;
extern unsigned char hai154x_busontime;
/* Actually, the following is just a forward declaration. */
extern haft_t _154x_haft;
#define CTRLREG (hai154x_base + 0) /* Control Register (Write) */
#define HRST bit(7) /* Hard Reset */
#define SRST bit(6) /* Soft Reset */
#define IRST bit(5) /* Interrupt Reset */
#define SCRST bit(4) /* SCSI Bus Reset */
#define STSREG (hai154x_base + 0) /* Status Register (Read) */
#define STST bit(7) /* Self Test in progress */
#define DIAGF bit(6) /* Internal Diagnostic Failure */
#define INIT bit(5) /* Mailbox Initialization Required */
#define IDLE bit(4) /* SCSI Host Adapter Idle */
#define CDF bit(3) /* Command/Data Out Port Full */
#define DF bit(2) /* Data In Port Full */
#define INVDCMD bit(0) /* Invalid HA Command */
#define CMDDATAOUT (hai154x_base + 1) /* Command/Data Out (Write) */
#define NOP 0x00 /* No Operation (really?) */
#define MBINIT 0x01 /* Mail Box Initialization */
#define STARTSCSI 0x02 /* Start a SCSI Command */
#define STARTBIOS 0x03 /* Start a BIOS Command */
#define HAINQUIRY 0x04 /* HA Inquiry */
#define ENBLMBOA 0x05 /* Enable Mailbox Out Available Interrupt */
#define SETSELTO 0x06 /* Set Selection Timeout */
#define SETBUSON 0x07 /* Set Bus on time */
#define SETBUSOFF 0x08 /* Set Bus off time */
#define SETXFERSPD 0x09 /* Set transfer speed */
#define RETINSTDEV 0x0a /* Return Installed Devices */
#define RETCFGDATA 0x0b /* Return Configuration Data */
#define ENBLTRGTMD 0x0c /* Enable Target Mode */
#define RETSUDATA 0x0d /* Return Setup Data */
/***********************************************************************
* These commands are specific to the Adaptec AHA-154xC.
*/
#define RETEXTBIOS 0x28 /* Return Extended BIOS Information */
#define EXTBIOSON bit(3)
#define SETMBIENBL 0x29 /* Set Mailbox Interface Enable */
#define MBIENABLED bit(1)
#define DATAIN (hai154x_base + 1)
#define INTRFLGS (hai154x_base + 2)
#define ANYINTR bit(7) /* Any Interrupt */
#define SCRD bit(3) /* SCSI Reset Detected */
#define HACC bit(2) /* HA Command Complete */
#define MBOA bit(1) /* MBO Empty */
#define MBIF bit(0) /* MBI Full */
#define MBOFREE 0x00 /* Mailbox out is free */
#define MBOSTART 0x01 /* Start CCB in this Mailbox */
#define MBOABORT 0x02 /* Abort CCB in this Mailbox */
#define MBIFREE 0x00 /* Mailbox in is free */
#define MBISUCCESS 0x01 /* Mailbox's CCB Completed Successfully */
#define MBIABORTED 0x02 /* Mailbox's CCB Aborted */
#define MBIABRTFLD 0x03 /* CCB to Abort not found */
#define MBIERROR 0x04 /* CCB Completed with error */
#define TIMEOUT -1 /* Timeout from pollxxx() functions. */
#define ST_HAINIT 0x0001 /* Host Adapter being initialized */
#define ST_HAIDLE 0x0002 /* Host Adapter is idle */
#define ST_HASTART 0x0003 /* Host Adapter is in start state */
/***********************************************************************
* Types
*/
#pragma align 1
typedef union addr3_u { /* addr3_u for big/little endian conversions */
unsigned long value;
unsigned char byteval[sizeof(unsigned long)];
} addr3_t;
typedef union mbo_u *mbo_p; /* Out Box to host adapter */
typedef union mbo_u {
unsigned char cmd;
paddr_t ccbaddr;
} mbo_t;
typedef union mbi_u *mbi_p; /* In Box from host adapter */
typedef union mbi_u {
unsigned char sts;
paddr_t ccbaddr;
} mbi_t;
typedef struct mb_s { /* Host adapter mailbox type */
mbo_t o[MAXDEVS]; /* One out box for each device */
mbi_t i[MAXDEVS]; /* One in box for each possible reply */
} mb_t;
typedef struct haccb_s *haccb_p; /* Host Adapter Command/Control Block */
typedef struct haccb_s {
unsigned char opcode;
unsigned char addrctrl;
unsigned char cdblen;
unsigned char senselen;
unsigned char datalen[3];
unsigned char bufaddr[3];
unsigned char linkaddr[3];
unsigned char linkid;
unsigned char hoststs;
unsigned char trgtsts;
unsigned char pad[2];
cdb_t cdb;
} haccb_t;
typedef struct dsentry_s *dsentry_p;
typedef struct dsentry_s {
unsigned char size[3];
unsigned char addr[3];
} dsentry_t;
typedef struct dslist_s *dslist_p;
typedef struct dslist_s {
dsentry_t entries[16];
} dslist_t;
#pragma align
/***********************************************************************
* Variables
*/
static int hastate; /* Host Adapter State */
static volatile int hainit_stsreg = 0; /* Host Adapter status flags. */
static volatile int hainit_ccflag = 0; /* Host Adapter Command Complete flag. */
static mb_t mb; /* Mailboxes for host adapter */
static haccb_t ccb[MAXDEVS]; /* CCB's for mailboxes */
static paddr_t ccbbase; /* ccbbase address for quick checkmail */
static srb_p actv[MAXDEVS]; /* Active srb's for each target */
static dslist_t ds[MAXDEVS]; /* Data segment lists one for each target */
static unsigned chkport = 0, /* Port for chkset/ckhclr */
chkstop = 0, /* Target value for chkset/chkclr */
chkval = 0; /* Value in port chkset/chkclr */
/***********************************************************************
* Support Functions
*/
#ifndef DEBUG
#define dbg_printf(lvl, msg)
#else
unsigned AHA_DBGLVL = 1;
#define dbg_printf(lvl, msg) { if (AHA_DBGLVL >= lvl) cmn_err(CE_CONT, msg); }
#endif
#define min(a, b) (((a) <= (b)) ? (a) : (b))
/***********************************************************************
* chkclr() -- Check port (chkport) for bits (chkstop) to be clear.
* If clear return 1 else return 0. Leave value of
* port in chkval;
*/
#if __USE_PROTO__
LOCAL int chkclr(void)
#else
LOCAL int
chkclr()
#endif
{
return ((chkval = inb(chkport)) & chkstop) == 0;
} /* chkclr() */
/***********************************************************************
* chkset() -- Check port (chkport) for bits (chkstop) to be set.
* If all bits are set return 1 else return 0. Leave
* value of port in chkval.
*/
#if __USE_PROTO__
LOCAL int chkset(void)
#else
LOCAL int
chkset()
#endif
{
return ((chkval = inb(chkport)) & chkstop) == chkstop;
} /* chkset() */
/***********************************************************************
* pollclr() -- Wait ticks clock ticks for bit(s) to clear in a
* port.
*/
#if __USE_PROTO__
LOCAL int pollclr(register unsigned port, /* port to watch */
register unsigned bits, /* bits to watch for */
unsigned ticks) /* time to wait. */
#else
LOCAL int pollclr(port, bits, ticks)
register unsigned port; /* port to watch */
register unsigned bits; /* bits to watch for */
unsigned ticks; /* number of milliseconds to wait for */
#endif
{
register s;
s = sphi();
chkport = port;
chkstop = bits;
busyWait(chkclr, ticks);
spl(s);
#if 1 /* DEBUG */
if ((chkval & bits) == 0)
return chkval;
else {
cmn_err(CE_NOTE,
"!pollclr: <Timeout Reg: 0x%x mask 0x%x value 0x%x>",
port,
bits,
chkval);
return TIMEOUT;
}
#else
return ((chkval & bits) == 0) ? chkval : TIMEOUT;
#endif
} /* pollclr() */
/***********************************************************************
* pollset() -- Wait ticks clocks for bit(s) to get set
* in a port.
*/
#if __USE_PROTO__
LOCAL int pollset(register unsigned port, /* port to watch */
register unsigned bits, /* bits to watch for */
unsigned ticks) /* time to wait */
#else
LOCAL int pollset(port, bits, ticks)
register unsigned port; /* port to watch */
register unsigned bits; /* bits to watch for */
unsigned ticks; /* number of milliseconds to wait for */
#endif
{
register s;
s = sphi();
chkport = port;
chkstop = bits;
busyWait(chkset, ticks);
spl(s);
#if 1 /* DEBUG */
if ((chkval & bits) == bits)
return chkval;
else {
cmn_err(CE_NOTE,
"!pollset: <Timeout Reg: 0x%x mask 0x%x value 0x%x>",
port,
bits,
chkval);
return TIMEOUT;
}
#else
return ((chkval & bits) == bits) ? chkval : TIMEOUT;
#endif
} /* pollset() */
/***********************************************************************
* hacc()
*
* Host Adapter Command Completed - Returns 1 if last host adapter
* command completed without error.
*/
#if __USE_PROTO__
LOCAL int hacc(void)
#else
LOCAL int
hacc()
#endif
{
register unsigned stsreg;
if (pollset(INTRFLGS, HACC, 350) == TIMEOUT)
return 0;
stsreg = pollset(STSREG, IDLE, 350) & (IDLE | INIT | CDF | INVDCMD);
if (stsreg != IDLE) {
cmn_err(CE_WARN, "Aha154x stuck - STSREG: 0x%x", stsreg);
return 0;
}
return 1;
} /* hacc() */
/***********************************************************************
* haidle()
*
* Returns 1 if the Idle Bit is on in the adapter status register
*/
#define haidle() (pollset(STSREG, IDLE, 350) != TIMEOUT)
/*
* hai_flag_set(port, flags)
*
* Returns non-zero if all specified flags are set in the port.
*/
#define hai_flag_set(port, flags) ((inb((port)) & (flags)) == (flags))
/***********************************************************************
* gethabyte()
*
* Get a byte from the host adapter Data In register.
*/
#if __USE_PROTO__
LOCAL int gethabyte(void)
#else
LOCAL int
gethabyte()
#endif
{
if (pollset(STSREG, DF, 350) == TIMEOUT) {
cmn_err(CE_NOTE,
"!hai154x: <gethabyte timeout (0x%x) 0x%x 0x%x>",
STSREG,
DF,
chkval);
return TIMEOUT;
}
else /* Beware of sign extension */
return ((int)inb(DATAIN) & 0x00ff);
} /* gethabyte() */
/***********************************************************************
* puthabyte()
*
* Write a byte to the host adapter Command/Data Out Register.
*/
#if __USE_PROTO__
LOCAL int puthabyte(unsigned char b)
#else
LOCAL int
puthabyte(b)
unsigned char b;
#endif
{
if (pollclr(STSREG, CDF, 350) == TIMEOUT)
return 0;
else {
outb(CMDDATAOUT, b);
return 1;
}
} /* puthabyte() */
/*
* We need to wait until either a HACC or DF occurs signifying
* the end of a pending command.
*/
static int hainit_status()
{
int status;
/* Check for hacc and/or DF */
if (hainit_ccflag == 0 && hai_flag_set(STSREG, DF) == 0)
return 0;
else /* HACC or DF came through */
return 1;
}
/***********************************************************************
* Function: aha_icmd()
*
* Send a command to the host adapter. and do all the handshaking
* etc. This assumes interrupts are enabled.
*/
#if __USE_PROTO__
LOCAL int aha_icmd(unsigned char *cmd, size_t cmdlen, unsigned char *data,
size_t datalen, int stifle)
#else
LOCAL int
aha_icmd(cmd, cmdlen, data, datalen, stifle)
unsigned char *cmd;
size_t cmdlen;
unsigned char *data;
size_t datalen;
int stifle;
#endif
{
int errorflag = 0;
int ch;
unsigned char *p;
int cnt;
/* Wait for card to go idle */
if (!haidle()) {
cmn_err(CE_WARN, "<hai154x: Timeout waiting for host adapter idle>");
errorflag = 1;
goto bail_cmd;
}
/*******************************************************
* Send as many of the command bytes as possible to
* the host.
*/
hainit_ccflag = 0;
hainit_stsreg = 0;
p = cmd;
for (cnt = 0; cnt < cmdlen && !errorflag && !hainit_ccflag; cnt++)
errorflag = !puthabyte(*p++);
/*******************************************************
* If the handshake failed then message to that effect.
*/
if (errorflag || (hainit_ccflag && (datalen != 0))) {
if (hainit_stsreg & INVDCMD)
cmn_err(CE_WARN, "<hai154x: Invalid command or parameter (send)>");
else
cmn_err(CE_WARN, "<hai154x: Command handshake failed>");
goto bail_cmd;
}
/* Busy wait for HACC or DF to occur */
if (busyWait(hainit_status, 350) == 0) {
cmn_err(CE_WARN, "<hai154x: status timeout (send)>");
goto bail_cmd;
}
/*
* Need to double check in case interrupt occured after we sent
* last byte.
*/
if (hainit_ccflag && datalen != 0) {
if (hai_flag_set(STSREG, INVDCMD) && !stifle)
cmn_err(CE_WARN, "<hai154x: Invalid command or parameter (last)>");
else if (!stifle)
cmn_err(CE_WARN, "<hai154x: Abnormal command completion>");
goto bail_cmd;
}
/* If return data is expected, receive it */
if (data && datalen) {
p = data;
for (cnt = 0; cnt < datalen && !errorflag && !hainit_ccflag; cnt++) {
errorflag = ((ch = gethabyte()) == TIMEOUT);
if (errorflag == 0)
*p++ = ch;
else {
cmn_err(CE_WARN, "<hai154x: Timeout on command data recv>");
goto bail_cmd;
}
}
/* Check for premature end of data */
if (hainit_ccflag && (cnt < datalen)) {
cmn_err(CE_WARN, "<hai154x: command data underrun>");
goto bail_cmd;
}
/* Wait for final HACC to acknowledge end of data bytes */
if (busyWait(hainit_status, 350) == 0) {
cmn_err(CE_WARN, "<hai154x: status timeout (command complete)>");
goto bail_cmd;
}
/*
* Check for data overrun; if busyWait ended, but
* hainit_ccflag isn't set, then it ended because of DF
* and there is unknown data waiting for us.
*/
if (hainit_ccflag == 0) {
cmn_err(CE_WARN, "<hai154x: command data overrun>");
goto bail_cmd;
}
}
hainit_ccflag = 0;
return 1; /* Return success */
/***************************************************************
* In case of any sort of error handshake bytes into the bit
* bucket.
*/
bail_cmd:
while (inb(STSREG) & DF) {
ch = gethabyte();
busyWait(NULL, 1);
}
hainit_ccflag = 0;
return 0; /* Return as an error */
}
/***********************************************************************
* Function: aha_checkbios()
*
* On the AHA154xC/154xCF check to see if the user is using the BIOS
* extensions available on this card. If so turn them off. This also
* determines if the card is using 255 head translation.
*/
#if __USE_PROTO__
LOCAL int aha_checkbios(void)
#else
LOCAL int
aha_checkbios()
#endif
{
unsigned char cmddata[3];
int retval;
cmddata[0] = RETEXTBIOS;
hainit_ccflag = 0;
if (!aha_icmd(cmddata, 1, cmddata + 1, 2, 1))
return 0;
retval = (cmddata[1] & EXTBIOSON) ? 255 : 64;
if (cmddata[2] == 0)
return retval;
cmddata[0] = SETMBIENBL;
cmddata[1] = 0;
hainit_ccflag = 0;
if (!aha_icmd(cmddata, 3, NULL, 0, 0)) {
cmn_err(CE_WARN,
"<hai154x: Set mailbox interface enable failed>");
return 0;
}
return retval;
} /* aha_checkbios() */
/***********************************************************************
* Function: aha_inquiry()
*
* Do an inquiry on the host adapter to find out what type it is.
*/
#if __USE_PROTO__
LOCAL int aha_inquiry(void)
#else
LOCAL int
aha_inquiry()
#endif
{
unsigned char buf[5];
unsigned char *command = &buf[0];
unsigned char *data = &buf[1];
command[0] = HAINQUIRY;
if (!aha_icmd(command, 1, data, 4, 0)) {
cmn_err(CE_WARN, "<hai154x: Host adapter inquiry command failure>");
return -1;
}
return data[0];
}
/***********************************************************************
* Function: aha_tunescsibus()
*
* Set host adapter bus on/bus off times from the external constants
* patched into the kernel. These constants are currently selectable
* by patching them into the kernel. They should be set up to work
* with idtune. [csh]
*/
#if __USE_PROTO__
LOCAL int aha_tunescsibus(void)
#else
LOCAL int
aha_tunescsibus()
#endif
{
unsigned char busonbuf[2],
*busoncmd = &busonbuf[0],
*busondata = &busonbuf[1],
busoffbuf[2],
*busoffcmd = &busoffbuf[0],
*busoffdata = &busoffbuf[1],
xferspdbuf[2],
*xferspdcmd = &xferspdbuf[0],
*xferspddata = &xferspdbuf[1];
/***************************************************************
* Set up command buffers for bus on/off times and xfer speed.
*/
busoncmd[0] = SETBUSON;
busondata[0] = hai154x_busontime;
busoffcmd[0] = SETBUSOFF;
busoffdata[0] = hai154x_busofftime;
xferspdcmd[0] = SETXFERSPD;
xferspddata[0] = hai154x_xferspeed;
/*******************************************************
* Set the bus on time.
*/
if (/* hai154x_busontime != DEFAULT && */
!aha_icmd(busoncmd, 2, NULL, 0, 0)) {
cmn_err(CE_WARN, "<hai154x: Set bus on time failed>");
return 0;
}
/*******************************************************
* And finally the bus off time.
*/
if (/* hai154x_busofftime != DEFAULT && */
!aha_icmd(busoffcmd, 2, NULL, 0, 0)) {
cmn_err(CE_WARN, "<hai154x: Set bus off time failed>");
return 0;
}
if (/* hai154x_busofftime != DEFAULT && */
!aha_icmd(xferspdcmd, 2, NULL, 0, 0)) {
cmn_err(CE_WARN, "<hai154x: Set bus off time failed>");
return 0;
}
return 1;
} /* aha_tunescsibus() */
/***********************************************************************
* Function: aha_hareset()
*
* Reset the host adapter. There are two types of reset. A hard reset
* resets both the host adapter and all the devices attached to the
* bus. A soft reset only resets the host adapter. During initialization
* a hard reset is called for. This seems to be the only thing that
* will work with the AHA-154xC adapters as well as the 174x adapters
* operating in standard mode. The Bus Logic controllers also seem
* to need the hard reset. During operation a soft reset is called
* for as this will allow operations on the devices attached to continue
* without problems. When calling this function as a part of error
* recovery try the soft reset first before going to the hard reset.
*/
#if __USE_PROTO__
LOCAL int aha_hareset(int hardreset)
#else
LOCAL int aha_hareset(hardreset)
int hardreset;
#endif
{
paddr_t mbaddr; /* Mail box array's paddr. */
unsigned stsreg;
unsigned char mbibuf[5],
*mbicmd = &mbibuf[0],
*mbidata = &mbibuf[1];
int retries = 0;
/***************************************************************
* Set up the mailbox command in the buffer.
*/
mbaddr = vtop(&mb);
mbicmd[0] = MBINIT;
mbidata[0] = (sizeof(mb) / (sizeof(mbo_t) + sizeof(mbi_t)));
mbidata[1] = (( unsigned char *) &mbaddr)[2];
mbidata[2] = (( unsigned char *) &mbaddr)[1];
mbidata[3] = (( unsigned char *) &mbaddr)[0];
do {
/*******************************************************
* If desired try a soft reset first. The soft reset
* doesn't halt operations in progress on devices on
* the bus so if the soft reset will work then more
* power to it. If the soft reset doesn't cut go back
* to the hard reset.
*/
if (hardreset || retries > 0)
outb(CTRLREG, HRST);
else
outb(CTRLREG, SRST);
/*******************************************************
* Wait for the self-test status to clear in the status
* register. give it 75 clock ticks (750 millisec).
*/
stsreg = pollclr(STSREG, STST, 75);
if (stsreg == TIMEOUT)
cmn_err(CE_WARN,
"<hai154x: Host adapter self-test timeout>");
/*******************************************************
* After the self test clears make sure the diagnostics
* didn't fail.
*/
else if (stsreg & DIAGF)
cmn_err(CE_WARN,
"<hai: Host adapter diagnostic failed>");
/*******************************************************
* If the diagnostics passed the host adapter should
* now be idle waiting for mailbox initilization.
*/
else if ((stsreg & (INIT | IDLE)) != (INIT | IDLE))
cmn_err(CE_WARN,
"<hai154x: Host adapter stuck (0x%x)>",
stsreg);
/*******************************************************
* Initialize the mailboxes.
*/
else if (!aha_icmd(mbicmd, 5, NULL, 0, 0))
cmn_err(CE_WARN,
"<hai154x: Mailbox init failed>");
/*******************************************************
* Lastly, attempt to tune the SCSI Bus.
*/
else if (!aha_tunescsibus())
cmn_err(CE_WARN,
"<hai154x: Set SCSI bus parameters failed>");
/*******************************************************
* Complete success, return.
*/
else
return 1;
} while (++retries < 1);
return 0;
} /* aha_hareset() */
#define hardreset() aha_hareset(1)
#define softreset() aha_hareset(0)
/***********************************************************************
* dmacascade()
*
* Set the selected (hai154x_dma) dma channel to cascade mode.
*/
#if __USE_PROTO__
LOCAL void dmacascade(void)
#else
LOCAL void
dmacascade()
#endif
{
int dmaporta, dmaportb, s;
s = sphi();
if (hai154x_dma == 0) {
dmaporta = 0x0b;
dmaportb = 0x0a;
} else {
dmaporta = 0xd6;
dmaportb = 0xd4;
}
outb(dmaporta, 0xc0 | (hai154x_dma & 3));
outb(dmaportb, (hai154x_dma & 3));
spl(s);
} /* dmacascade() */
/***********************************************************************
* checkmail()
*
* Check the incoming mailboxes for messages. Do this on any Mail
* Box In Full interrupt and before you fail a command for timeout.
* The code to determine which target device finished by tid is a
* bit tricky and relies on the following assumptions:
*
* 1) The host adapter is installed in a Intel (big-endian)
* machine. Believe it or not there is (are) non-Intel CPU
* ISA bus machines. and the one that I know of is a M68000
* machine where this would not work.
*
* 2) The kernel's data space is physically contiguous and is
* never swapped out.
*/
#if __USE_PROTO__
LOCAL int checkmail(void)
#else
LOCAL int
checkmail()
#endif
{
static int startid = 0;
int msgs = 0;
int sts;
int s;
register int id;
register srb_p r;
register int i = startid;
do {
if (mb.i[i].sts != MBIFREE) {
s = sphi();
sts = mb.i[i].sts;
flip(mb.i[i].ccbaddr);
id = (unsigned) ((mb.i[i].ccbaddr & 0x00ffffffL) - ccbbase) / sizeof(haccb_t);
if (actv[id]) {
switch (sts) {
case MBIABRTFLD:
cmn_err(CE_WARN,
"(%d, %d) Command 0x%x abort failed",
major(actv[id]->dev),
minor(actv[id]->dev),
actv[id]->cdb.g0.opcode);
actv[id]->status = ST_ABRTFAIL;
break;
case MBISUCCESS:
case MBIERROR:
actv[id]->status = ccb[id].trgtsts;
break;
case MBIABORTED:
actv[id]->status = ST_DRVABRT;
break;
default:
cmn_err(CE_WARN,
"Host Adapter Mailbox In value corrupted");
break;
}
r = actv[id];
actv[id] = NULL;
if (r->cleanup)
(*(r->cleanup))(r);
msgs |= bit(id);
}
mb.i[i].ccbaddr = 0;
spl(s);
}
else if (msgs)
break;
i = (i + 1) & 7;
} while (i != startid);
startid = i;
return msgs;
} /* checkmail() */
/***********************************************************************
* resetdevice()
*
* Reset a SCSI target.
*/
#if __USE_PROTO__
LOCAL void _154x_bdr(register int id)
#else
LOCAL void
_154x_bdr(id)
register int id;
#endif
{
register haccb_p c = &(ccb[hai154x_haid]);
int tries;
c->opcode = 0x81;
c->addrctrl = (id << 5);
for (tries = 10; tries > 0; --tries) {
if (!_154x_haft.hf_present)
_154x_haft.hf_present = softreset();
if (_154x_haft.hf_present) {
mb.o[hai154x_haid].cmd = MBOSTART;
if (puthabyte(STARTSCSI) && (inb(STSREG) & INVDCMD) == 0)
break;
}
}
} /* _154x_bdr() */
/***********************************************************************
* abortscsi()
*
* Abort the SCSI Command at a target device on the bus.
*/
#if __USE_PROTO__
LOCAL void _154x_abort(register srb_p r)
#else
LOCAL void
_154x_abort(r)
register srb_p r;
#endif
{
int s,
tries;
s = sphi();
r->timeout = 0;
for (tries = 10; _154x_haft.hf_present && tries > 0; --tries) {
mb.o[r->target].cmd = MBOABORT;
if (puthabyte(STARTSCSI) && (inb(STSREG) & INVDCMD) == 0)
break;
cmn_err(CE_WARN,
"hai154x: _154x_abort() - Command start failed.");
_154x_haft.hf_present = softreset();
/*******************************************************
* This is strange. The soft reset call may have to
* do a hard reset. If it does then the command is
* gone anyhow. What should happen is the command
* start should work and then the hardware should
* report that It cannot find the command in question.
* [csh]
*/
}
if (tries <= 0)
cmn_err(CE_WARN,
"hai154x: _154x_abort() - Cannot reach host adapter.");
else {
busyWait(checkmail, 100);
if (r->status == ST_PENDING) {
cmn_err(CE_WARN,
"hai154x: _154x_abort() failed: id %d",
r->target);
_154x_bdr(r->target);
actv[r->target] = NULL;
r->status = ST_DRVABRT;
if (r->cleanup)
(*r->cleanup)(r);
}
}
spl(s);
} /* _154x_abort() */
/***********************************************************************
* Driver Interface Functions
*/
/***********************************************************************
* hatimer()
*
* Host adapter Timeout handler.
*/
#if __USE_PROTO__
LOCAL void _154x_timer(void)
#else
LOCAL void
_154x_timer()
#endif
{
register int id;
register srb_p r;
register int active;
int s;
s = sphi();
checkmail(); /* Cleanup any missed interrupts, etc. */
active = 0;
for (id = 0; id < MAXDEVS; ++id) {
if (actv[id] != NULL && actv[id]->timeout != 0) {
if (--actv[id]->timeout == 0) {
cmn_err(CE_WARN,
"(%d, %d) <hai154x: timeout - id: %d lun: %d scmd: (0x%x)>",
major(actv[id]->dev), minor(actv[id]->dev), actv[id]->target,
actv[id]->lun, actv[id]->cdb.g0.opcode);
_154x_abort(actv[id]);
if (actv[id]) {
cmn_err(CE_WARN, "hai154x: No cleanup");
actv[id]->status = ST_DRVABRT;
r = actv[id];
actv[id] = NULL;
if (r->cleanup)
(*(r->cleanup))(r);
}
}
else
active = 1;
}
}
drvl[SCSIMAJOR].d_time = active;
spl(s);
}
/***********************************************************************
* haintr()
*
* SCSI interrupt handler for host adapter.
*/
#if __USE_PROTO__
LOCAL void haintr(void)
#else
void
haintr()
#endif
{
int intrflgs;
int stsreg;
#if defined(DEBUG)
int old_cc_flag;
#endif
intrflgs = inb(INTRFLGS);
switch (hastate) {
case ST_HAINIT: /* Init - Interrupts shouldn't be allowed! */
if (intrflgs & ANYINTR)
outb(CTRLREG, IRST);
#if defined(DEBUG)
cmn_err(CE_WARN, "<hai_154x: unexpected interrupt during init: 0x%x>",
intrflgs);
#endif
break;
case ST_HASTART: /* Start - Mailboxes shouldn't be up. */
#if defined(DEBUG)
old_cc_flag = hainit_ccflag;
#endif
hainit_stsreg = inb(STSREG);
hainit_ccflag = ((intrflgs & HACC) != 0);
if (intrflgs & ANYINTR)
outb(CTRLREG, IRST);
#if defined(DEBUG)
if (hainit_ccflag == 0)
cmn_err(CE_WARN, "<hai154x: Unexpected interrupt during start: 0x%x>",
intrflgs);
if (old_cc_flag && hainit_ccflag)
cmn_err(CE_WARN, "<hai154x: ccflag before previous completion>");
#endif
break;
default: /* Normal operation */
/***************************************************************
* If the host adapter command complete flag is set get the
* status register (for invalid command flag [INVDCMD]).
*/
if (intrflgs & HACC) {
stsreg = inb(STSREG);
#if 0
/*******************************************************
* If the state here is "just tried to start a SCSI
* command" then that command failed (invalid command)
* mark this condition and bail.
*/
#endif
}
/***************************************************************
* If this is a real interrupt do an Interrupt reset to clear
* it.
*/
if (intrflgs & ANYINTR)
outb(CTRLREG, IRST);
if (intrflgs & MBIF) {
/*******************************************************
* During normal operation the only interrupt that
* we concern ourselves with is the MBIF (Mailbox
* in full) interrupt. This means that a SCSI command
* has finished.
*/
checkmail();
}
break;
}
}
#if __USE_PROTO__
static int _154x_init(void)
#else
static int
_154x_init()
#endif /* __USE_PROTO__ */
{
/*
* While we cannot assume that interrupts are enabled,
* we set the interrupt vector just in case. Under
* DDI/DKI there is a clear difference between
* xxinit() and xxstart(), but for the old type drivers there isn't.
*/
hastate = ST_HAINIT;
setivec(hai154x_intr, haintr);
return 0;
}
#if __USE_PROTO__
static int _154x_start_driver(void)
#else
static int
_154x_start_driver()
#endif /* __USE_PROTO__ */
{
register int i;
hastate = ST_HASTART;
/* Do an inquiry to find out which 154x it is (or isn't) */
i = aha_inquiry();
switch (i) {
case '\0':
cmn_err(CE_CONT, "AHA-1540, 16 head BIOS\n");
break;
case '0':
cmn_err(CE_CONT, "AHA-1540, 64 head BIOS\n");
break;
case 'B':
cmn_err(CE_CONT, "AHA-1640, 64 head BIOS\n");
break;
case 'C':
cmn_err(CE_CONT, "AHA-1740A/1742A/1744, standard mode\n");
break;
case 'A':
cmn_err(CE_CONT, "AHA-1540B/AHA-1542B\n");
break;
case 'D':
cmn_err(CE_CONT, "AHA-1540C/AHA-1542C\n");
break;
case 'E':
cmn_err(CE_CONT, "AHA-1540CF/AHA-1542CF\n");
break;
default:
cmn_err(CE_NOTE, "Adaptec AHA-154x not found (inq = 0x%x).", i);
break;
}
/* Check to see if the card has an extended BIOS enabled */
if (aha_checkbios() == 255) {
cmn_err(CE_CONT, "Extended BIOS detected.\n");
}
/* Hard reset the card so we start from a known state */
if (!(_154x_haft.hf_present = hardreset())) {
cmn_err(CE_WARN,
"hai154x: Initialization failed on AHA-154x at (0x%x)",
hai154x_base);
return 1;
}
/* Set up the mailbox structures to the initial state */
for (i = 0; i < MAXDEVS; ++i) {
actv[i] = NULL;
mb.o[i].ccbaddr = vtop(ccb + i);
flip(mb.o[i].ccbaddr);
mb.o[i].cmd = MBOFREE;
}
/* Set up cascading DMA for the card. */
ccbbase = vtop(ccb);
dmacascade();
/*
* This call doesn't belong here. This really should be done from
* the disk drive init routines. But this is in testing so...
*/
{
extern int at_drive_ct;
extern int HAI_DISK;
int biosnum = at_drive_ct;
int id;
for (id = 0 ; id < MAXDEVS ; ++id)
if (HAI_DISK & bit(id))
loadbiosparms(biosnum++, id);
}
return 0;
}
/***********************************************************************
* _154x_load()
*
* Initialize the host adapter for operation.
*/
#if __USE_PROTO__
LOCAL int _154x_load(void)
#else
LOCAL int
_154x_load()
#endif
{
/*
* DDI/DKI specifies separate init and start routines.
* While this is not a DDI/DKI driver, it should be followed in
* the case that interrupts are disabled during the init routine.
*/
/* Call init routine */
if (_154x_init())
return 0;
/* Call start routine */
if (_154x_start_driver())
return 0;
/* Set state to idle. The load is complete. */
hastate = ST_HAIDLE;
return 1;
}
/*
* _154x_unload() -- Unload routine. For this adapter it's just a stub.
*/
#if __USE_PROTO__
LOCAL void _154x_unload(void)
#else
LOCAL void
_154x_unload()
#endif
{
return;
}
#if __USE_PROTO__
LOCAL int mkdslist(dsentry_p d, bufaddr_p b)
#else
LOCAL int mkdslist(d, b)
dsentry_p d;
bufaddr_p b;
#endif
{
int s;
size_t index;
static haisgsegm_t seglist[16];
haisgsegm_p seg = seglist;
size_t segcount;
s = sphi();
switch (b->space & SPACE_MASK) {
case KRNL_ADDR:
case USER_ADDR:
segcount = ukbuftosgl(b->ba_virt,
b->size,
seglist,
sizeof(seglist) / sizeof(haisgsegm_t));
break;
case SYSGLBL_ADDR:
segcount = sysgbuftosgl(b->ba_phys,
b->size,
seglist,
sizeof(seglist) / sizeof(haisgsegm_t));
break;
case PHYS_ADDR:
spl(s);
return 0;
case SGLIST_ADDR:
seg = b->ba_sglist;
segcount = b->size;
break;
} /* switch */
for (index = 0; index < segcount; ++index) {
d[index].size[2] = ((unsigned char *) &(seg[index].sgs_segsize))[0];
d[index].size[1] = ((unsigned char *) &(seg[index].sgs_segsize))[1];
d[index].size[0] = ((unsigned char *) &(seg[index].sgs_segsize))[2];
d[index].addr[2] = ((unsigned char *) &(seg[index].sgs_segstart))[0];
d[index].addr[1] = ((unsigned char *) &(seg[index].sgs_segstart))[1];
d[index].addr[0] = ((unsigned char *) &(seg[index].sgs_segstart))[2];
} /* for */
spl(s);
return segcount;
} /* mkdslist() */
/***********************************************************************
* startscsi()
*
* Send a SCSI CDB to a target device on the bus.
*/
#if __USE_PROTO__
LOCAL int _154x_start(register srb_p r)
#else
LOCAL int
_154x_start(r)
register srb_p r;
#endif
{
register haccb_p c;
paddr_t bufaddr;
size_t datalen;
register int s;
if (r->target >= MAXDEVS || r->lun >= MAXUNITS) {
cmn_err(CE_WARN,
"hai154x: Illegal device ID: %d LUN: %d",
r->target,
r->lun);
return 0;
}
++r->tries;
if (actv[r->target]) {
cmn_err(CE_WARN,
"(%d, %d) hai154x: Device busy: old opcode (0x%x) new opcode (0x%x)",
major(r->dev),
minor(r->dev),
ccb[r->target].cdb.g0.opcode,
r->cdb.g0.opcode);
return 0;
}
s = sphi();
r->status = ST_PENDING;
c = ccb + r->target;
memset(c, 0, sizeof(haccb_t));
c->opcode = 0; /* Start SCSI CDB */
c->addrctrl = (r->target << 5) | (r->lun & 7);
if (r->xferdir & DMAREAD) c->addrctrl |= 0x08;
if (r->xferdir & DMAWRITE) c->addrctrl |= 0x10;
c->cdblen = cpycdb(&(c->cdb), &(r->cdb));
c->senselen = 1;
/***********************************************************************
* Set up the CCB's Address here. This turned out to be a bit more
* complicated than I thought it would be.
*/
if ((r->buf.space & SPACE_MASK) == PHYS_ADDR) {
c->datalen[0] = ((unsigned char *) &(r->buf.size))[2];
c->datalen[1] = ((unsigned char *) &(r->buf.size))[1];
c->datalen[2] = ((unsigned char *) &(r->buf.size))[0];
c->bufaddr[0] = ((unsigned char *) &(r->buf.addr.paddr))[2];
c->bufaddr[1] = ((unsigned char *) &(r->buf.addr.paddr))[1];
c->bufaddr[2] = ((unsigned char *) &(r->buf.addr.paddr))[0];
}
else {
datalen = mkdslist(ds[r->target].entries, &(r->buf));
if (datalen == 0) {
cmn_err(CE_WARN,
"SCSI ID %d - Bad Scatter/Gather list",
r->target);
spl(s);
return 0;
}
else if (datalen == 1)
memcpy(c->datalen, ds[r->target].entries, 6);
else {
c->opcode = 2;
bufaddr = vtop(ds[r->target].entries);
datalen *= 6;
c->datalen[0] = ((unsigned char *) &datalen)[2];
c->datalen[1] = ((unsigned char *) &datalen)[1];
c->datalen[2] = ((unsigned char *) &datalen)[0];
c->bufaddr[0] = ((unsigned char *) &bufaddr)[2];
c->bufaddr[1] = ((unsigned char *) &bufaddr)[1];
c->bufaddr[2] = ((unsigned char *) &bufaddr)[0];
}
}
mb.o[r->target].cmd = MBOSTART;
/***************************************************************
* CLEAN THIS UP.
*
* The _154x_start command will NOT generate an hacc interrupt
* if the command bytes and ccb are okay, It will just go
* on and do its thing. If however there is a problem _154x_start
* will generate a HACC interrupt with invalid command set
* high. WE SHOULD TRAP this condition.
*
* [csh]
*/
if (puthabyte(STARTSCSI) && (inb(STSREG) & INVDCMD) == 0) {
actv[r->target] = r;
if (r->timeout)
drvl[SCSIMAJOR].d_time = 1;
}
else {
cmn_err(CE_WARN,
"_154x_start() failed: Resetting host adapter.");
mb.o[r->target].cmd = MBOFREE;
actv[r->target] = NULL;
r->status = ST_DRVABRT;
if (r->cleanup)
(*r->cleanup)(r);
_154x_haft.hf_present = softreset();
spl(s);
return 0;
}
spl(s);
return 1;
} /* _154x_start() */
/*
* Host adapter function table.
*
* Device modules will use this table to find the functions that they
* need to access the host adapter.
*/
haft_t _154x_haft = {
0,
_154x_timer,
_154x_load,
_154x_unload,
_154x_start,
_154x_abort,
_154x_bdr
};