/* (-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 };