/* @(#)sw.c 1.1 92/07/30 Copyr 1988 Sun Micro */ #include "sw.h" #if NSW > 0 #ifndef lint static char sccsid[] = "@(#)sw.c 1.1 92/07/30 Copyr 1988 Sun Micro"; #endif lint /*#define SCSI_DEBUG /* Turn on debugging code */ #define REL4 /* Enable release 4.00 mods */ /* * Generic scsi routines. */ #ifndef REL4 #include "../h/param.h" #include "../h/systm.h" #include "../h/dk.h" #include "../h/buf.h" #include "../h/conf.h" #include "../h/dir.h" #include "../h/user.h" #include "../h/map.h" #include "../h/vmmac.h" #include "../h/ioctl.h" #include "../h/uio.h" #include "../h/kernel.h" #include "../h/dkbad.h" #include "../h/mman.h" #include "../machine/pte.h" #include "../machine/psl.h" #include "../machine/mmu.h" #include "../machine/cpu.h" #include "../machine/scb.h" #include "../machine/enable.h" #include "../vm/seg.h" #include "../machine/seg_kmem.h" #include "../sun/dklabel.h" #include "../sun/dkio.h" #include "../sundev/mbvar.h" #include "../sundev/swreg.h" #include "../sundev/scsi.h" #else REL4 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif REL4 /* Shorthand, to make the code look a bit cleaner. */ #define SWNUM(sw) (sw - swctlrs) #define SWUNIT(dev) (minor(dev) & 0x03) int swwatch(); int swprobe(), swslave(), swattach(), swgo(), sw_done(), swpoll(); int swustart(), swstart(), sw_getstatus(), sw_reconnect(), sw_deque(); int sw_off(), sw_cmd(), sw_cmdwait(), sw_reset(); int sw_wait(), sw_sbc_wait(), sw_dmacnt(),sw_getdata(); extern int nsw, scsi_host_id, scsi_reset_delay; extern struct scsi_ctlr swctlrs[]; /* per controller structs */ extern struct mb_ctlr *swinfo[]; extern struct mb_device *sdinfo[]; struct mb_driver swdriver = { swprobe, swslave, swattach, swgo, sw_done, swpoll, sizeof (struct scsi_sw_reg), "sd", sdinfo, "sw", swinfo, MDR_BIODMA, }; /* routines available to devices specific portion of scsi driver */ struct scsi_ctlr_subr swsubr = { swustart, swstart, sw_done, sw_cmd, sw_getstatus, sw_cmdwait, sw_off, sw_reset, sw_dmacnt, swgo, sw_deque, }; extern struct scsi_unit_subr scsi_unit_subr[]; extern int scsi_ntype; extern int scsi_disre_enable; /* enable disconnect/reconnects */ extern int scsi_debug; /* 0 = normal operaion * 1 = enable error logging and error msgs. * 2 = as above + debug msgs. * 3 = enable all info msgs. */ /* * Patchable delays for debugging. */ static u_char junk; int sw_arbitration_delay = SI_ARBITRATION_DELAY; int sw_bus_clear_delay = SI_BUS_CLEAR_DELAY; int sw_bus_settle_delay = SI_BUS_SETTLE_DELAY; /* * possible return values from sw_arb_sel, sw_cmd, * sw_putdata, and sw_reconnect. */ #define OK 0 /* successful */ #define FAIL 1 /* failed maybe recoverable */ #define HARD_FAIL 2 /* failed not recoverable */ #define SCSI_FAIL 3 /* failed due to scsi bus fault */ #define RESEL_FAIL 4 /* failed due to target reselection */ /* possible return values from sw_process_complete_msg() */ #define CMD_CMPLT_DONE 0 /* cmd processing done */ #define CMD_CMPLT_WAIT 1 /* cmd processing waiting * on sense cmd complete */ /* * possible argument values for sw_reset */ #define NO_MSG 0 /* don't print reset warning message */ #define PRINT_MSG 1 /* print reset warning message */ /* * scsi CDB op code to command length decode table. */ static u_char cdb_size[] = { CDB_GROUP0, /* Group 0, 6 byte cdb */ CDB_GROUP1, /* Group 1, 10 byte cdb */ CDB_GROUP2, /* Group 2, ? byte cdb */ CDB_GROUP3, /* Group 3, ? byte cdb */ CDB_GROUP4, /* Group 4, ? byte cdb */ CDB_GROUP5, /* Group 5, ? byte cdb */ CDB_GROUP6, /* Group 6, ? byte cdb */ CDB_GROUP7 /* Group 7, ? byte cdb */ }; #ifdef SCSI_DEBUG int sw_dis_debug = 0; /* disconnect debug info */ u_int sw_winner = 0; /* # of times we had an intr at end of swintr */ u_int sw_loser = 0; /* # of times we didn't have an intr at end */ #define SW_WIN sw_winner++ #define SW_LOSE sw_loser++ /* Check for possible illegal SCSI-3 register access. */ #define SW_VME_OK(c, swr, str) {\ if ((IS_VME(c)) && (swr->csr & SI_CSR_DMA_EN)) \ printf("sw%d: reg access during dma <%s>, csr 0x%x\n", \ SWNUM(c), str, swr);\ } #define SW_DMA_OK(c, swr, str) {\ if (IS_VME(c)) { \ if (swr->csr & SI_CSR_DMA_EN) \ printf("%s: DMA DISABLED\n", str); \ if (swr->csr & SI_CSR_DMA_CONFLICT) { \ printf("%s: invalid reg access during dma\n", str); \ DELAY(50000000); \ } \ swr->csr &= ~SI_CSR_DMA_EN; \ } \ } #define SCSI_TRACE(where, swr, un) \ if (scsi_debug) scsi_trace(where, swr, un) #define SCSI_RECON_TRACE(where, c, data0, data1, data2) \ if (scsi_debug) scsi_recon_trace(where, c, data0, data1, data2) #define DEBUG_DELAY(cnt) \ if (scsi_debug) DELAY(cnt) /* Handy debugging 0, 1, and 2 argument printfs */ #define DPRINTF(str) \ if (scsi_debug > 1) printf(str) #define DPRINTF1(str, arg1) \ if (scsi_debug > 1) printf(str,arg1) #define DPRINTF2(str, arg1, arg2) \ if (scsi_debug > 1) printf(str,arg1,arg2) /* Handy error reporting 0, 1, and 2 argument printfs */ #define EPRINTF(str) \ if (scsi_debug) printf(str) #define EPRINTF1(str, arg1) \ if (scsi_debug) printf(str,arg1) #define EPRINTF2(str, arg1, arg2) \ if (scsi_debug) printf(str,arg1,arg2) #else SCSI_DEBUG #define SW_WIN #define SW_LOSE #define SW_VME_OK(c, swr, str) #define SW_DMA_OK(c, swr, str) #define DEBUG_DELAY(cnt) #define DPRINTF(str) #define DPRINTF1(str, arg2) #define DPRINTF2(str, arg1, arg2) #define EPRINTF(str) #define EPRINTF1(str, arg2) #define EPRINTF2(str, arg1, arg2) #define SCSI_TRACE(where, swr, un) #define SCSI_RECON_TRACE(where, c, data0, data1, data2) #endif SCSI_DEBUG #define CLEAR_INTERRUPT(msg) \ if (SBC_RD.bsr & SBC_BSR_INTR) { \ EPRINTF1("%s: int cleared\n", msg); \ junk = SBC_RD.clr; \ } else { \ EPRINTF1("%s: no int\n", msg); \ } /* * trace buffer stuff. */ #ifdef SCSI_DEBUG #define TRBUF_LEN 64 struct trace_buf { u_char wh[2]; /* where in code */ u_char r[6]; /* 5380 registers */ u_long csr; u_int dma_addr; u_int dma_count; }; struct disre_trace_buf { u_char wh[2]; struct scsi_cdb cdb; u_short target; u_short lun; u_int data[3]; u_int dma_count; }; /* trace buffer */ int sw_stbi = 0; struct trace_buf sw_trace_buf[TRBUF_LEN]; /* disconnect/reconnect trace buffer */ int sw_strbi = 0; struct disre_trace_buf sw_recon_trace_buf[TRBUF_LEN]; static scsi_trace(where, swr, un) register char where; register struct scsi_sw_reg *swr; struct scsi_unit *un; { register u_char *r = &(SBC_RD.cdr); register u_int i; register struct trace_buf *tb = &(sw_trace_buf[sw_stbi]); #ifdef lint un= un; #endif lint tb->wh[0] = tb->wh[1] = where; for (i = 0; i < 6; i++) tb->r[i] = *r++; tb->csr = swr->csr; tb->dma_addr = GET_DMA_ADDR(swr); tb->dma_count = GET_DMA_COUNT(swr); if (++sw_stbi >= TRBUF_LEN) sw_stbi = 0; sw_trace_buf[sw_stbi].wh[0] = '?'; /* mark end */ } static scsi_recon_trace(where, c, data0, data1, data2) char where; register struct scsi_ctlr *c; register int data0, data1, data2; { register struct scsi_unit *un = c->c_un; register struct disre_trace_buf *tb = &(sw_recon_trace_buf[sw_strbi]); tb->wh[0] = tb->wh[1] = where; tb->cdb = un->un_cdb; tb->target = un->un_target; tb->lun = un->un_lun; tb->data[0] = data0; tb->data[1] = data1; tb->data[2] = data2; if (++sw_strbi >= TRBUF_LEN) sw_strbi = 0; sw_recon_trace_buf[sw_strbi].wh[0] = '?'; /* mark end */ } #endif SCSI_DEBUG /* * Print out the cdb. */ static sw_print_cdb(un) register struct scsi_unit *un; { register u_char size; register u_char *cp; register u_char i; cp = (u_char *) &un->un_cdb; if ((size = cdb_size[CDB_GROUPID(*cp)]) == 0 && (size = un->un_cmd_len) == 0) { /* If all else fails, use structure size */ size = sizeof (struct scsi_cdb); } for (i = 0; i < size; i++) printf(" %x", *cp++); printf("\n"); } /* * returns string corresponding to the phase */ static char * sw_str_phase(phase) register u_char phase; { register int index = (phase & CBSR_PHASE_BITS) >> 2; static char *phase_strings[] = { "DATA OUT", "DATA IN", "COMMAND", "STATUS", "", "", "MSG OUT", "MSG IN", "BUS FREE", }; if (((phase & SBC_CBSR_BSY) == 0) && (index == 0)) return (phase_strings[8]); else return (phase_strings[index]); } /* * returns string corresponding to the last phase. * Note, also encoded are internal messages in * addition to the last bus phase. */ static char * sw_str_lastphase(phase) register u_char phase; { static char *invalid_phase = ""; static char *phase_strings[] = { "Spurious", /* 0x80 */ "Arbitration", /* 0x81 */ "Identify MSG", /* 0x82 */ "Save ptr MSG", /* 0x83 */ "Restore ptr MSG", /* 0x84 */ "Disconnect MSG", /* 0x85 */ "Reconnect MSG", /* 0x86 */ "Cmd complete MSG", /* 0x87 */ }; if (phase >= 0x80) { if (phase > 0x87) return (invalid_phase); return (phase_strings[phase - 0x80]); } else { return (sw_str_phase(phase)); } } /* * print out the current hardware state */ static sw_print_state(swr, c) register struct scsi_sw_reg *swr; register struct scsi_ctlr *c; { register struct scsi_unit *un = c->c_un; register short flag = 0; if ((swr->csr & SI_CSR_DMA_EN)) { swr->csr &= ~SI_CSR_DMA_EN; flag = 1; } printf("\tlast phase= 0x%x (%s)\n", c->c_last_phase, sw_str_lastphase((u_char)c->c_last_phase)); printf("\tcsr= 0x%x bcr= %d tcr= 0x%x\n", swr->csr, swr->bcr, SBC_RD.tcr); printf("\tcbsr= 0x%x (%s) cdr= 0x%x mr= 0x%x bsr= 0x%x\n", SBC_RD.cbsr, sw_str_phase(SBC_RD.cbsr), SBC_RD.cdr, SBC_RD.mr, SBC_RD.bsr); if (flag) { swr->csr |= SI_CSR_DMA_EN; } #ifdef SCSI_DEBUG printf("\tdriver wins= %d loses= %d\n", sw_winner, sw_loser); #endif SCSI_DEBUG if (un != NULL) { printf("\ttarget= %d, lun= %d ", un->un_target, un->un_lun); printf("DMA addr= 0x%x count= %d (%d)\n", un->un_dma_curaddr, un->un_dma_curcnt, un->un_dma_count); printf("\tcdb= "); sw_print_cdb(un); } } /* * This routine unlocks this driver when I/O * has ceased, and a call to swstart is * needed to start I/O up again. Refer to * xd.c and xy.c for similar routines upon * which this routine is based */ static swwatch(c) register struct scsi_ctlr *c; { register struct scsi_unit *un; /* * Find a valid un to call s?start with to for next command * to be queued in case DVMA ran out. Try the pending que * then the disconnect que. Otherwise, driver will hang * if DVMA runs out. The reason for this kludge is that * swstart should really be passed c instead of un, but it * would break lots of 3rd party S/W if this were fixed. */ un = (struct scsi_unit *)c->c_tab.b_actf; if (un == NULL) un = (struct scsi_unit *)c->c_disqtab.b_actf; if (un != NULL) swstart(un); timeout(swwatch, (caddr_t)c, 10*hz); } /* * Determine existence of SCSI host adapter. * Returns 0 for failure, size of sw data structure if ok. */ swprobe(swr, ctlr) register struct scsi_sw_reg *swr; register int ctlr; { register struct scsi_ctlr *c = &swctlrs[ctlr]; int val; /* * Check for sbc - NCR 5380 Scsi Bus Ctlr chip. * sbc is common to sun3/50 onboard scsi and vme * scsi board. */ if (peekc((char *)&swr->sbc_rreg.cbsr) == -1) { return (0); } /* * Determine whether the host adaptor interface is onboard or vme. */ if (cpu == CPU_SUN4_110) { /* probe for 4/110 dma interface */ if (peekl((long *)&swr->dma_addr, (long *)&val) == -1) { return (0); } c->c_flags = SCSI_COBRA; } /* probe for different scsi host adaptor interfaces */ EPRINTF2("sw%d: swprobe: swr= 0x%x, ", ctlr, (u_int)swr); EPRINTF1("c= 0x%x\n", (u_int)c ); /* init controller information */ if (scsi_disre_enable) { /* DPRINTF("swprobe: disconnects enabled\n"); */ c->c_flags |= SCSI_EN_DISCON; } else { EPRINTF("swprobe: all disconnects disabled\n"); } /* * Allocate a page for being able to * flush the last bit of a data transfer. */ c->c_kment = rmalloc(kernelmap, (long)mmu_btopr(MMU_PAGESIZE)); if (c->c_kment == 0) { printf("si%d: out of kernelmap for last DMA page\n", ctlr); return (0); } c->c_flags |= SCSI_PRESENT; c->c_last_phase = PHASE_CMD_CPLT; c->c_reg = (int)swr; c->c_ss = &swsubr; c->c_name = "sw"; c->c_tab.b_actf = NULL; c->c_tab.b_active = C_INACTIVE; c->c_intpri = 2; /* initalize before sw_reset() */ sw_reset(c, NO_MSG); /* quietly reset the scsi bus */ c->c_un = NULL; timeout(swwatch, (caddr_t)c, 10*hz); return (sizeof (struct scsi_sw_reg)); } /* * See if a slave exists. * Since it may exist but be powered off, we always say yes. */ /*ARGSUSED*/ swslave(md, swr) register struct mb_device *md; register struct scsi_sw_reg *swr; { register struct scsi_unit *un; register int type; #ifdef lint swr = swr; junk = junk; junk = scsi_debug; #endif lint /* * This kludge allows autoconfig to print out "sd" for * disks and "st" for tapes. The problem is that there * is only one md_driver for scsi devices. */ type = TYPE(md->md_flags); if (type >= scsi_ntype) { panic("swslave: unknown type in md_flags\n"); } /* link unit to its controller */ un = (struct scsi_unit *)(*scsi_unit_subr[type].ss_unit_ptr)(md); if (un == NULL) panic("swslave: md_flags scsi type not configured\n"); un->un_c = &swctlrs[md->md_ctlr]; md->md_driver->mdr_dname = scsi_unit_subr[type].ss_devname; return (1); } /* * Attach device (boot time). */ swattach(md) register struct mb_device *md; { register int type = TYPE(md->md_flags); register struct scsi_ctlr *c = &swctlrs[md->md_ctlr]; register struct scsi_sw_reg *swr = (struct scsi_sw_reg *)(c->c_reg); /* DPRINTF("swattach:\n"); */ #ifdef SCSI_DEBUG if ((scsi_disre_enable != 0) && scsi_debug) printf("sw%d: swattach: disconnects disabled\n", SWNUM(c)); #endif SCSI_DEBUG if (type >= scsi_ntype) panic("swattach: unknown type in md_flags\n"); (*scsi_unit_subr[type].ss_attach)(md); /* * Make sure dma enable bit is off or * SI_CSR_DMA_CONFLICT will occur when * the iv_am register is accessed. */ c->c_intpri = md->md_mc->mc_intpri; swr->csr &= ~SI_CSR_DMA_EN; return; } /* * corresponding to un is an md. if this md has SCSI commands * queued up (md->md_utab.b_actf != NULL) and md is currently * not active (md->md_utab.b_active == 0), this routine queues the * un on its queue of devices (c->c_tab) for which to run commands * Note that md is active if its un is already queued up on c->c_tab, * or its un is on the scsi_ctlr's disconnect queue c->c_disqtab. */ swustart (un) struct scsi_unit *un; { register struct scsi_ctlr *c = un->un_c; register struct mb_device *md = un->un_md; int s; DPRINTF("swustart:\n"); s = splr(pritospl(c->c_intpri)); if (md->md_utab.b_actf != NULL && md->md_utab.b_active == MD_INACTIVE) { if (c->c_tab.b_actf == NULL) c->c_tab.b_actf = (struct buf *)un; else ((struct scsi_unit *)(c->c_tab.b_actl))->un_forw = un; un->un_forw = NULL; c->c_tab.b_actl = (struct buf *)un; md->md_utab.b_active |= MD_QUEUED; DPRINTF1("swustart: md_utab = %x\n", md->md_utab.b_active); } (void) splx(s); } /* * this un is removed from the active * position of the scsi_ctlr's queue of devices (c->c_tab.b_actf) and * queued on scsi_ctlr's disconnect queue (c->c_disqtab). */ sw_discon_queue (un) struct scsi_unit *un; { register struct scsi_ctlr *c = un->un_c; /* DPRINTF("sw_discon_queue:\n"); */ /* a disconnect has occurred, so mb_ctlr is no longer active */ c->c_tab.b_active &= C_QUEUED; c->c_un = NULL; /* remove disconnected un from scsi_ctlr's queue */ if((c->c_tab.b_actf = (struct buf *)un->un_forw) == NULL) c->c_tab.b_actl = NULL; /* put un on scsi_ctlr's disconnect queue */ if (c->c_disqtab.b_actf == NULL) c->c_disqtab.b_actf = (struct buf *)un; else ((struct scsi_unit *)(c->c_disqtab.b_actl))->un_forw = un; c->c_disqtab.b_actl = (struct buf *)un; un->un_forw = NULL; } /* * searches the scsi_ctlr's disconnect queue for the un * which points to the scsi_unit defined by (target,lun). * then requeues this un on scsi_ctlr's queue of devices. */ sw_recon_queue (c, target, lun, flag) register struct scsi_ctlr *c; register short target, lun, flag; { register struct scsi_unit *un; register struct scsi_unit *pun; DPRINTF("sw_recon_queue:\n"); /* search the disconnect queue */ for (un=(struct scsi_unit *)(c->c_disqtab.b_actf), pun=NULL; un; pun=un, un=un->un_forw) { if ((un->un_target == target) && (un->un_lun == lun)) break; } /* could not find the device in the disconnect queue */ if (un == NULL) { printf("sw%d: sw_reconnect: can't find dis unit: target %d lun %d\n", SWNUM(c), target, lun); /* dump out disconnnect queue */ printf(" disconnect queue:\n"); for (un = (struct scsi_unit *)(c->c_disqtab.b_actf), pun = NULL; un; pun = un, un = un->un_forw) { printf("\tun = 0x%x --- target = %d, lun = %d\n", un, un->un_target, un->un_lun); } return (FAIL); } if (flag) return(OK); /* remove un from the disconnect queue */ if (un == (struct scsi_unit *)(c->c_disqtab.b_actf)) c->c_disqtab.b_actf = (struct buf *)(un->un_forw); else pun->un_forw = un->un_forw; if (un == (struct scsi_unit *)c->c_disqtab.b_actl) c->c_disqtab.b_actl = (struct buf *)pun; /* requeue un at the active position of scsi_ctlr's queue * of devices. */ if (c->c_tab.b_actf == NULL) { un->un_forw = NULL; c->c_tab.b_actf = c->c_tab.b_actl = (struct buf *)un; } else { un->un_forw = (struct scsi_unit *)(c->c_tab.b_actf); c->c_tab.b_actf = (struct buf *)un; } /* scsi_ctlr now has an actively running SCSI command */ c->c_tab.b_active |= C_ACTIVE; c->c_un = un; return (OK); } /* starts the next SCSI command */ swstart (un) struct scsi_unit *un; { struct scsi_ctlr *c; struct mb_device *md; struct buf *bp; int s; DPRINTF("swstart:\n"); if (un == NULL) return; /* return immediately, if the ctlr is already actively * running a SCSI command */ s = splr(pritospl(3)); c = un->un_c; #ifdef notdef c = un->un_c; s = splr(pritospl(c->c_intpri)); #endif notdef if (c->c_tab.b_active != C_INACTIVE) { DPRINTF1("sistart: locked (0x%x)\n", c->c_tab.b_active); (void) splx(s); return; } /* if there are currently no SCSI devices queued to run * a command, then simply return. otherwise, obtain the * next un for which a command should be run. */ if ((un=(struct scsi_unit *)(c->c_tab.b_actf)) == NULL) { DPRINTF("swstart: no device to run\n"); (void) splx(s); return; } md = un->un_md; c->c_tab.b_active |= C_QUEUED; /* if an attempt was already made to run * this command, but the attempt was * pre-empted by a SCSI bus reselection * then DVMA has already been set up, and * we can call swgo directly */ if (md->md_utab.b_active & MD_PREEMPT) { DPRINTF1("swstart: md_utab = %d\n", md->md_utab.b_active); DPRINTF("swstart: starting pre-empted"); un->un_c->c_un = un; swgo(md); c->c_tab.b_active &= C_ACTIVE; (void) splx(s); return; } if (md->md_utab.b_active & MD_IN_PROGRESS) { DPRINTF1("swstart: md_utab = %d\n", md->md_utab.b_active); DPRINTF("swstart: md in progress\n"); c->c_tab.b_active &= C_ACTIVE; (void) splx(s); return; } md->md_utab.b_active |= MD_IN_PROGRESS; bp = md->md_utab.b_actf; md->md_utab.b_forw = bp; /* only happens when called by intr */ if (bp == NULL) { EPRINTF("swstart: bp is NULL\n"); c->c_tab.b_active &= C_ACTIVE; (void) splx(s); return; } /* special commands which are initiated by * the high-level driver, are run using * its special buffer, un->un_sbuf. * in most cases, main bus set-up has * already been done, so swgo can be called * for on-line formatting, we need to * call mbsetup. */ if ((*un->un_ss->ss_start)(bp, un)) { un->un_c->c_un = un; if (bp == &un->un_sbuf && ((un->un_flags & SC_UNF_DVMA) == 0) && ((un->un_flags & SC_UNF_SPECIAL_DVMA) == 0)) { swgo(md); } else { if (mbugo(md) == 0) { md->md_utab.b_active |= MD_NODVMA; } } } else { sw_done(md); } c->c_tab.b_active &= C_ACTIVE; (void) splx(s); } /* * Start up a scsi operation. * Called via mbgo after buffer is in memory. */ swgo(md) register struct mb_device *md; { register struct scsi_ctlr *c; register struct scsi_unit *un; register struct buf *bp; register int unit; register int err; int type; int s; DPRINTF("swgo:\n"); s = splr(pritospl(3)); if (md == NULL) { (void) splx(s); panic("swgo: queueing error1\n"); } c = &swctlrs[md->md_mc->mc_ctlr]; #ifdef notdef if (md == NULL) panic("swgo: queueing error1\n"); c = &swctlrs[md->md_mc->mc_ctlr]; s = splr(pritospl(c->c_intpri)); #endif notdef type = TYPE(md->md_flags); un = (struct scsi_unit *) (*scsi_unit_subr[type].ss_unit_ptr)(md); bp = md->md_utab.b_forw; if (bp == NULL) { EPRINTF("swgo: bp is NULL\n"); (void) splx(s); return; } un->un_baddr = MBI_ADDR(md->md_mbinfo); if (md->md_utab.b_active & MD_NODVMA) { md->md_utab.b_active &= ~MD_NODVMA; md->md_utab.b_active |= MD_PREEMPT; (*un->un_ss->ss_mkcdb)(un); (void) splx(s); return; } c->c_tab.b_active |= C_QUEUED; /* * Diddle stats if necessary. */ if ((unit = un->un_md->md_dk) >= 0) { dk_busy |= 1<b_flags & B_READ) dk_read[unit]++; dk_wds[unit] += bp->b_bcount >> 6; } /* * Make the command block and fire it up in interrupt mode. * If it fails right off the bat, call the interrupt routine * to handle the failure. */ if (md->md_utab.b_active & MD_PREEMPT) md->md_utab.b_active &= ~MD_PREEMPT; else (*un->un_ss->ss_mkcdb)(un); DPRINTF1("swgo: md_utab = %d\n", md->md_utab.b_active); if ((err=sw_cmd(c, un, 1)) != OK) { if (err == FAIL) { /* DPRINTF("swgo: sw_cmd FAILED\n"); */ (*un->un_ss->ss_intr)(c, 0, SE_RETRYABLE); } else if (err == HARD_FAIL) { EPRINTF("swgo: sw_cmd hard FAIL\n"); (*un->un_ss->ss_intr)(c, 0, SE_FATAL); } else if (err == SCSI_FAIL) { DPRINTF("swgo: sw_cmd scsi FAIL\n"); (*un->un_ss->ss_intr)(c, 0, SE_FATAL); sw_off(c, un, SE_FATAL); } } c->c_tab.b_active &= C_ACTIVE; DPRINTF1("swgo: end md_utab = %d\n", md->md_utab.b_active); (void) splx(s); } /* * Handle a polling SCSI bus interrupt. */ swpoll() { register struct scsi_ctlr *c; register struct scsi_sw_reg *swr; register int serviced = 0; /* DPRINTF("swpoll:\n"); */ for (c = swctlrs; c < &swctlrs[nsw]; c++) { if ((c->c_flags & SCSI_PRESENT) == 0) continue; (int)swr = c->c_reg; if ((swr->csr & (SI_CSR_SBC_IP | SI_CSR_DMA_IP | SI_CSR_DMA_CONFLICT | SI_CSR_DMA_BUS_ERR)) == 0) { continue; } serviced = 1; swintr(c); } return (serviced); } /* the SCSI command is done, so start up the next command */ sw_done (md) struct mb_device *md; { struct scsi_ctlr *c; struct scsi_unit *un; struct buf *bp; int type; int s; EPRINTF("swdone:\n"); s = splr(pritospl(3)); c = &swctlrs[md->md_mc->mc_ctlr]; #ifdef notdef s = splr(pritospl(c->c_intpri)); #endif notdef bp = md->md_utab.b_forw; /* more reliable than un = c->c_un; */ type = TYPE(md->md_flags); un = (struct scsi_unit *) (*scsi_unit_subr[type].ss_unit_ptr)(md); /* advance mb_device queue */ md->md_utab.b_actf = bp->av_forw; /* we are done, so clear buf in active position of * md's queue. then call iodone to complete i/o */ md->md_utab.b_forw = NULL; /* just got done with i/o so mark ctlr inactive * then advance to next md on the ctlr. */ c->c_tab.b_actf = (struct buf *)(un->un_forw); /* advancing the ctlr's queue has removed un from * the queue. if there are any more i/o for this * md, swustart will queue up un again. at tail. * first, need to mark md as inactive (not on queue) */ DPRINTF1("swdone: md_utab = %d\n", md->md_utab.b_active); md->md_utab.b_active = MD_INACTIVE; swustart(un); iodone(bp); /* start up the next command on the scsi_ctlr */ swstart(un); (void) splx(s); } /* * Bring a unit offline. */ /*ARGSUSED*/ sw_off(c, un, flag) register struct scsi_ctlr *c; register struct scsi_unit *un; int flag; { register struct mb_device *md = un->un_md; char *msg = "online"; /* * Be warned, if you take the root device offline, * the system is going to have a heartattack ! * Note, if unit is already offline, don't bother * to print error message. */ if (un->un_present) { if (flag == SE_FATAL) { msg = "offline"; un->un_present = 0; } printf("sw%d: %s%d, unit %s\n", SWNUM(c), scsi_unit_subr[md->md_flags].ss_devname, SWUNIT(un->un_unit), msg); } } /* * Pass a command to the SCSI bus. * OK if fully successful, * Return FAIL on failure (may be retryable), * SCSI_FAIL if we failed due to timing problem with SCSI bus. (terminal) * RESEL_FAIL if we failed due to target being in process of reselecting us. * (posponed til after reconnect done) */ sw_cmd(c, un, intr) register struct scsi_ctlr *c; register struct scsi_unit *un; register int intr; /* if 0, run cmd to completion * in polled mode * if 1, allow disconnects * if enabled and use * interrupts */ { register struct scsi_sw_reg *swr; register struct mb_device *md = un->un_md; register int err; register int i; register u_char size; u_char msg; int s; (int)swr = c->c_reg; /* disallow disconnects if waiting for command completion */ DPRINTF("sw_cmd:\n"); if (intr == 0) { c->c_flags &= ~SCSI_EN_DISCON; } else { /* * If disconnect/reconnect globally disabled or only * disabled for this command set internal flag. * Otherwise, we enable disconnects and reconnects. */ if ((scsi_disre_enable == 0) || (un->un_flags & SC_UNF_NO_DISCON)) c->c_flags &= ~SCSI_EN_DISCON; else c->c_flags |= SCSI_EN_DISCON; un->un_wantint = 1; } /* Check for odd-byte boundary buffer */ if ((un->un_flags & SC_UNF_DVMA) && (un->un_dma_addr & 0x1)) { printf("sw%d: illegal odd byte DMA, address= 0x%x\n", SWNUM(c), un->un_dma_curaddr); return (HARD_FAIL); } /* * For vme host adaptor interface, dma enable bit may * be set to allow reconnect interrupts to come in. * This must be disabled before arbitration/selection * of target is done. Don't worry about re-enabling * dma. If arb/sel fails, then sw_idle will re-enable. * If arb/sel succeeds then handling of command will * re-enable. * * Also, disallow sbc to accept reconnect attempts. * Again, sw_idle will re-enable this if arb/sel fails. * If arb/sel succeeds then we do not want to allow * reconnects anyway. */ s = splr(pritospl(c->c_intpri)); if ((intr == 1) && (c->c_tab.b_active >= C_ACTIVE)) { md->md_utab.b_active |= 0x2; DPRINTF1("sw_cmd: md_utab = %d\n", md->md_utab.b_active); DPRINTF("sw_cmd: currently locked out\n"); (void) splx(s); return (OK); } c->c_tab.b_active |= C_ACTIVE; swr->csr &= ~SI_CSR_DMA_EN; SW_VME_OK(c, swr, "start of sw_cmd"); SCSI_TRACE('c', swr, un); un->un_flags &= ~SC_UNF_DMA_INITIALIZED; /* performing target selection */ if ((err = sw_arb_sel(c, swr, un)) != OK) { /* * May not be able to execute this command at this time due * to a target reselecting us. Indicate this in the unit * structure for when we perform this command later. */ SW_VME_OK(c, swr, "sw_cmd: arb_sel_fail"); if (err == RESEL_FAIL) { md->md_utab.b_active |= MD_PREEMPT; DPRINTF1("sw_cmd: preempted, tgt= %x\n", un->un_target); err = OK; /* not an error */ /* Check for lost reselect interrupt */ if ( !(SBC_RD.bsr & SBC_BSR_INTR)) { printf("sw_cmd: hardware reselect failure, software recovered.\n"); swintr(c); } } else { c->c_tab.b_active &= C_QUEUED; } un->un_wantint = 0; swr->csr |= SI_CSR_DMA_EN; (void) splx(s); return (err); } /* * We need to send out an identify message to target. */ SBC_WR.ser = 0; /* clear (re)sel int */ SBC_WR.mr &= ~SBC_MR_DMA; /* clear phase int */ (void) splx(s); /* * Must split dma setup into 2 parts due to sun3/50 * which requires bcr to be set before target * changes phase on scsi bus to data phase. * * Three fields in the per scsi unit structure * hold information pertaining to the current dma * operation: un_dma_curdir, un_dma_curaddr, and * un_dma_curcnt. These fields are used to track * the amount of data dma'd especially when disconnects * and reconnects occur. * If the current command does not involve dma, * these fields are set appropriately. */ if (un->un_dma_count != 0) { if (un->un_flags & SC_UNF_RECV_DATA) { un->un_dma_curdir = SI_RECV_DATA; swr->csr &= ~SI_CSR_SEND; } else { un->un_dma_curdir = SI_SEND_DATA; swr->csr |= SI_CSR_SEND; } /* save current dma info for disconnect. Note, * address tweak for cobra. */ un->un_dma_curaddr = un->un_dma_addr | 0xf00000; un->un_dma_curcnt = un->un_dma_count; un->un_dma_curbcr = 0; /* DPRINTF1("csr after resetting fifo 0x%x\n", swr->csr); */ /* * Currently we don't use all 24 bits of the * count register on the vme interface. To do * this changes are required other places, e.g. * in the scsi_unit structure the fields * un_dma_curcnt and un_dma_count would need to * be changed. */ swr->dma_count = 0; /* * New: we set up everything we can here, rather * than wait until data phase. */ SW_VME_OK(c, swr, "sw_cmd: before cmd phase"); sw_dma_setup(c, un); } else { un->un_dma_curdir = SI_NO_DATA; un->un_dma_curaddr = 0; un->un_dma_curcnt = 0; } SW_VME_OK(c, swr, "sw_cmd: before cmd phase"); SCSI_TRACE('C', swr, un); RETRY_CMD_PHASE: if (sw_wait_phase(swr, PHASE_COMMAND) != OK) { /* * Handle synchronous messages (6 bytes) and other * unknown messages. Note, all messages will be * rejected. It would be nice someday to figure * out what to do with them; but not today. */ register u_char *icrp = &SBC_WR.icr; if ((SBC_RD.cbsr & CBSR_PHASE_BITS) == PHASE_MSG_IN) { *icrp = SBC_ICR_ATN; msg = sw_getdata(c, PHASE_MSG_IN); EPRINTF1("sw_cmd: rejecting msg 0x%x\n", msg); i = 255; /* accept 255 message bytes (overkill) */ while((sw_getdata(c, PHASE_MSG_IN) != -1) && --i); if ((SBC_RD.cbsr & CBSR_PHASE_BITS) == PHASE_MSG_OUT) { msg = SC_MSG_REJECT; *icrp = 0; /* turn off ATN */ (void) sw_putdata(c, PHASE_MSG_OUT, &msg, 1, 0); } /* Should never fail this check. */ if (i > 0) goto RETRY_CMD_PHASE; } /* target is skipping cmd phase, resynchronize... */ if (SBC_RD.cbsr & SBC_CBSR_REQ) { #ifdef SCSI_DEBUG printf("sw_cmd: skipping cmd phase\n"); sw_print_state(swr, c); #endif SCSI_DEBUG s = splr(pritospl(c->c_intpri)); goto SW_CMD_EXIT; } /* we've had a target failure, report it and quit */ printf("sw%d: sw_cmd: no command phase\n", SWNUM(c)); sw_reset(c, PRINT_MSG); c->c_tab.b_active &= C_QUEUED; return (FAIL); } /* * Determine size of the cdb. Since we're smart, we * look at group code and guess. If we don't * recognize the group id, we use the specified * cdb length. If both are zero, use max. size * of data structure. */ if ((size = cdb_size[CDB_GROUPID(un->un_cdb.cmd)]) == 0 && (size = un->un_cmd_len) == 0) { printf("sw%d: invalid cdb size, using size= %d\n", SWNUM(c), sizeof (struct scsi_cdb)); size = sizeof (struct scsi_cdb); } c->c_last_phase = PHASE_COMMAND; s = splr(pritospl(3)); #ifdef notdef s = splr(pritospl(c->c_intpri)); #endif notdef SBC_WR.ser = scsi_host_id; /* enable (re)sel int */ if (sw_putdata(c, PHASE_COMMAND, (u_char *)&un->un_cdb, size, intr) != OK) { printf("sw%d: sw_cmd: put cmd failed\n", SWNUM(c)); sw_print_state(swr, c); sw_reset(c, PRINT_MSG); c->c_tab.b_active &= C_QUEUED; (void) splx(s); return (FAIL); } /* If not polled I/O mode, we're done */ SW_CMD_EXIT: if (intr) { /* Check for lost phase change interrupt */ if ((SBC_RD.cbsr & SBC_CBSR_REQ) && !(SBC_RD.bsr & SBC_BSR_INTR)) { printf("sw_cmd: interrupt failure\n"); swintr(c); } swr->csr |= SI_CSR_DMA_EN; (void) splx(s); return (OK); } (void) splx(s); /* * Polled SCSI data transfer mode. */ if (un->un_dma_curdir != SI_NO_DATA) { register u_char phase; if (un->un_dma_curdir == SI_RECV_DATA) phase = PHASE_DATA_IN; else phase = PHASE_DATA_OUT; SW_VME_OK(c, swr, "sw_cmd: before data xfer, sync"); /* * Check if we have a problem with the command * not going into data phase. If we do, * then we'll skip down and get any status. * Of course, this means that the command * failed. */ if ((sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_LONG_WAIT, 1) == OK) && ((SBC_RD.cbsr & CBSR_PHASE_BITS) == phase)) { /* * Must actually start DMA xfer here - setup * has already been done. So, put sbc in dma * mode and start dma transfer. */ sw_sbc_dma_setup(c, swr, (int)un->un_dma_curdir); /* * Wait for DMA to finish. If it fails, * attempt to get status and report failure. */ if ((err=sw_cmdwait(c)) != OK) { EPRINTF("sw_cmd: cmdwait failed\n"); if (err != SCSI_FAIL) msg = sw_getstatus(un); c->c_tab.b_active &= C_QUEUED; return (err); } } else { EPRINTF("sw_cmd: skipping data phase\n"); } } /* * Get completion status for polled command. * Note, if <0, it's retryable; if 0, it's fatal. * Someday I should give polled status results * more options. For now, everything is FATAL. */ c->c_last_phase = PHASE_CMD_CPLT; if ((err=sw_getstatus(un)) <= 0) { c->c_tab.b_active &= C_QUEUED; if (err == 0) return (HARD_FAIL); else return (FAIL); } c->c_tab.b_active &= C_QUEUED; return (OK); } /* * Perform the SCSI arbitration and selection phases. * Returns FAIL if unsuccessful, * returns RESEL_FAIL if unsuccessful due to target reselecting, * returns OK if all was cool. */ static sw_arb_sel(c, swr, un) register struct scsi_ctlr *c; register struct scsi_sw_reg *swr; register struct scsi_unit *un; { register u_char *icrp = &SBC_WR.icr; register u_char *mrp = &SBC_WR.mr; register u_char icr; u_char id; int ret_val; int s; int j; int sel_retries = 0; DPRINTF("sw_arb_sel:\n"); SW_VME_OK(c, swr, "sw_arb_sel"); /* * It seems that the tcr must be 0 for arbitration to work. */ SBC_WR.tcr = 0; *mrp &= ~SBC_MR_ARB; /* turn off arb */ *icrp = 0; SBC_WR.odr = scsi_host_id; #ifdef SCSI_DEBUG if (scsi_debug > 2) sw_print_state(swr, c); #endif SCSI_DEBUG /* arbitrate for the scsi bus */ SW_RETRY_SEL: for (j = 0; j < SI_ARB_RETRIES; j++) { /* wait for scsi bus to become free */ if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_BSY, SI_WAIT_COUNT, 0) != OK) { printf("sw%d: sw_arb_sel: scsi bus continuously busy\n", SWNUM(c)); sw_reset(c, PRINT_MSG); ret_val = FAIL; /* may be retryable */ goto SW_ARB_SEL_EXIT; } /* wait for sbc to begin arbitration */ *mrp |= SBC_MR_ARB; /* turn on arb */ if (sw_sbc_wait((caddr_t)icrp, SBC_ICR_AIP, SI_ARB_WAIT, 1) != OK) { /* * sbc may never begin arbitration due to a * target reselecting us, the initiator. */ *mrp &= ~SBC_MR_ARB; /* turn off arb */ if (((SBC_RD.cbsr & SBC_CBSR_RESEL) == SBC_CBSR_RESEL) && (SBC_RD.cdr & scsi_host_id)) { DPRINTF("sw_arb_sel: recon1\n"); ret_val = RESEL_FAIL; goto SW_ARB_SEL_EXIT; } EPRINTF1("sw_arb_sel: AIP never set, cbsr= 0x%x\n", SBC_RD.cbsr); #ifdef SCSI_DEBUG sw_print_state(swr, c); #endif SCSI_DEBUG goto SW_ARB_SEL_RETRY; } /* check to see if we won arbitration */ s = splr(pritospl(7)); /* time critical */ DELAY(sw_arbitration_delay); if ( (*icrp & SBC_ICR_LA) == 0 && ((SBC_RD.cdr & ~scsi_host_id) < scsi_host_id) ) { /* * WON ARBITRATION! Perform selection. * If disconnect/reconnect enabled, set ATN. * If not, skip ATN so target won't do disconnects. */ /* DPRINTF("sw_arb_sel: won arb\n"); */ icr = SBC_ICR_SEL | SBC_ICR_BUSY | SBC_ICR_DATA; if (c->c_flags & SCSI_EN_DISCON) { icr |= SBC_ICR_ATN; } SBC_WR.odr = (1 << un->un_target) | scsi_host_id; *icrp = icr; *mrp &= ~SBC_MR_ARB; /* turn off arb */ DELAY(sw_bus_clear_delay + sw_bus_settle_delay); goto SW_ARB_SEL_WON; } (void) splx(s); SW_ARB_SEL_RETRY: /* Lost arb, try again */ *mrp &= ~SBC_MR_ARB; /* turn off arb */ if (((SBC_RD.cbsr & SBC_CBSR_RESEL) == SBC_CBSR_RESEL) && (SBC_RD.cdr & scsi_host_id)) { DPRINTF("sw_arb_sel: recon2\n"); ret_val = RESEL_FAIL; goto SW_ARB_SEL_EXIT; } EPRINTF("sw_arb_sel: lost arbitration\n"); } /* * FAILED ARBITRATION even with retries. * This shouldn't ever happen since * we have the highest priority id on the scsi bus. */ *icrp = 0; printf("sw%d: sw_arb_sel: never won arbitration\n", SWNUM(c)); sw_print_state(swr, c); sw_reset(c, PRINT_MSG); ret_val = FAIL; /* may be retryable */ SW_ARB_SEL_EXIT: return (ret_val); SW_ARB_SEL_WON: /* wait for target to acknowledge selection */ *icrp &= ~SBC_ICR_BUSY; (void) splx(s); DELAY(1); if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_BSY, SI_SHORT_WAIT, 1) != OK) { EPRINTF("sw_arb_sel: busy never set\n"); #ifdef SCSI_DEBUG sw_print_state(swr, c); #endif SCSI_DEBUG *icrp = 0; /* Target failed selection */ if (un->un_present && (++sel_retries < SI_SEL_RETRIES)) goto SW_RETRY_SEL; ret_val = HARD_FAIL; goto SW_ARB_SEL_EXIT; } /* DPRINTF("sw_arb_sel: selected\n"); */ *icrp &= ~(SBC_ICR_SEL | SBC_ICR_DATA); c->c_last_phase = PHASE_ARBITRATION; /* If disconnects enabled, tell target it's ok to do it. */ if (c->c_flags & SCSI_EN_DISCON) { /* Tell target we do disconnects */ /* DPRINTF("sw_arb_sel: disconnects ENABLED\n"); */ id = SC_DR_IDENTIFY | un->un_cdb.lun; SBC_WR.tcr = TCR_MSG_OUT; if (sw_wait_phase(swr, PHASE_MSG_OUT) == OK) { *icrp = 0; /* turn off ATN */ if (sw_putdata(c, PHASE_MSG_OUT, &id, 1, 0) != OK) { EPRINTF1("sw%d: sw_arb_sel: id msg failed\n", SWNUM(c)); sw_reset(c, PRINT_MSG); return (FAIL); } } } return (OK); } /* * Set up the SCSI control logic for a dma transfer for vme host adaptor. */ static sw_dma_setup(c, un) register struct scsi_ctlr *c; register struct scsi_unit *un; { register struct scsi_sw_reg *swr; (int)swr = c->c_reg; SW_VME_OK(c, swr, "sw_dma_setup"); SCSI_RECON_TRACE('v', c, un->un_dma_curaddr, (int)un->un_dma_curcnt, (int)un->un_dma_curbcr); /* DPRINTF2("sw_dma_setup: after fifo reset, csr 0x%x, bcr 0x%x\n", * swr->csr, swr->bcr); */ /* Check for odd-byte boundary buffer */ /* NEED BETTER STRATEGY!! */ if ((u_int)(un->un_dma_curaddr) & 0x1) { printf("sw%d: illegal odd byte DMA address= 0x%x\n", SWNUM(c), un->un_dma_curaddr); /* sw_reset(c, PRINT_MSG); */ /* return; */ } /* * setup starting dma address and number bytes to dma * Note, the dma count is set to zero to prevent it from * starting up. It will be set later in sw_sbc_dma_setup */ swr->dma_addr = un->un_dma_curaddr; swr->dma_count = 0; /* set up byte packing control info */ if (swr->dma_addr & 0x2) { if (un->un_dma_curdir == SI_RECV_DATA) { EPRINTF2("sw_dma_setup: word transfer -- %X %X\n", un->un_dma_curaddr, un->un_dma_count); un->un_flags |= SC_UNF_WORD_XFER; } } /* * junk = GET_DMA_ADDR(swr); * DPRINTF1("sw_dma_setup: addr= 0x%x", junk); * junk = GET_DMA_COUNT(swr); * DPRINTF1(" count= %d ", junk); * DPRINTF2("csr= 0x%x bcr= 0x%x\n", swr->csr, swr->bcr); */ } /* * Setup and start the sbc for a dma operation. */ static sw_sbc_dma_setup(c, swr, dir) register struct scsi_ctlr *c; register struct scsi_sw_reg *swr; register int dir; { register struct scsi_unit *un = c->c_un; register int s; SCSI_TRACE('G', swr, un); SW_VME_OK(c, swr, "sw_sbc_dma_setup"); un->un_flags |= SC_UNF_DMA_INITIALIZED; if (un->un_flags & SC_UNF_WORD_XFER) { char *cp; /* * HARDWARE BUG: writing to swr->dma_addr works fine * but the read done here causes the most significant * byte to be lost, so it must be or'ed back in. * This bug was lots of fun to trace down. */ cp = (char *)swr->dma_addr; cp = (char *)((int)cp | 0xff000000); *cp++ = sw_getdata(c, PHASE_DATA_IN); *cp++ = sw_getdata(c, PHASE_DATA_IN); swr->dma_addr = (int)cp; } un->un_flags |= SC_UNF_DMA_INITIALIZED; if (un->un_flags & SC_UNF_WORD_XFER) { swr->dma_count = un->un_dma_curcnt - 2; un->un_flags &= ~SC_UNF_WORD_XFER; } else { swr->dma_count = un->un_dma_curcnt; } if (dir == SI_RECV_DATA) { /* DPRINTF("sw_sbc_dma_setup: RECEIVE DMA\n"); */ c->c_last_phase = PHASE_DATA_IN; s = splr(pritospl(7)); /* time critical */ SBC_WR.tcr = TCR_DATA_IN; junk = SBC_RD.clr; /* clear intr */ /* CRITICAL CODE SECTION DON'T TOUCH */ SBC_WR.mr |= SBC_MR_DMA; SBC_WR.ircv = 0; swr->csr |= SI_CSR_DMA_EN; (void) splx(s); } else { /* DPRINTF("sw_sbc_dma_setup: XMIT DMA\n"); */ c->c_last_phase = PHASE_DATA_OUT; s = splr(pritospl(7)); /* time critical */ SBC_WR.tcr = TCR_DATA_OUT; junk = SBC_RD.clr; /* clear intr */ SBC_WR.icr = SBC_ICR_DATA; /* CRITICAL CODE SECTION DON'T TOUCH */ SBC_WR.mr |= SBC_MR_DMA; SBC_WR.send = 0; swr->csr |= SI_CSR_DMA_EN; (void) splx(s); } } /* * Cleanup up the SCSI control logic after a dma transfer. */ static sw_dma_cleanup(c) register struct scsi_ctlr *c; { register struct scsi_sw_reg *swr; (int)swr = c->c_reg; /* DPRINTF("sw_dma_cleanup:\n"); */ /* disable dma controller */ swr->dma_addr = 0; swr->dma_count = 0; } /* * Handle special dma receive situations, e.g. an odd number of bytes * in a dma transfer. * The Sun3/50 onboard interface has different situations which * must be handled than the vme interface. * Returns OK if sucessful; Otherwise FAIL. */ static sw_dma_recv(c) register struct scsi_ctlr *c; { register struct scsi_sw_reg *swr; register struct scsi_unit *un = c->c_un; register int offset, addr; (int)swr = c->c_reg; #ifdef lint un = un; #endif lint /* DPRINTF("sw_dma_recv:\n"); */ SCSI_RECON_TRACE('R', c, un->un_dma_curaddr, (int)un->un_dma_curcnt, offset); SCSI_TRACE('u', swr, un); /* * Grabs last few bytes which may not have been dma'd. * Worst case is when longword dma transfers are being done * and there are 3 bytes leftover. * If BPCON bit is set then longword dmas were being done, * otherwise word dmas were being done. */ addr = swr->dma_addr; offset = (addr - (int)DVMA) & 0xFFFFFC; switch (swr->dma_addr & 0x3) { case 3: /* three bytes left */ if (MBI_MR(offset) < dvmasize) { DVMA[offset] = (swr->bpr & 0xff000000) >> 24; DVMA[offset + 1] = (swr->bpr & 0x00ff0000) >> 16; DVMA[offset + 2] = (swr->bpr & 0x0000ff00) >> 8; } else { sw_flush_vmebyte(c, (offset), ((swr->bpr & 0xff000000) >> 24)); sw_flush_vmebyte(c, (offset + 1), ((swr->bpr & 0x00ff0000) >> 16)); sw_flush_vmebyte(c, (offset + 2), ((swr->bpr & 0x0000ff00) >> 8)); } break; case 2: /* two bytes left */ if (MBI_MR(offset) < dvmasize) { DVMA[offset] = (swr->bpr & 0xff000000) >> 24; DVMA[offset + 1] = (swr->bpr & 0x00ff0000) >> 16; } else { sw_flush_vmebyte(c, (offset), ((swr->bpr & 0xff000000) >> 24)); sw_flush_vmebyte(c, (offset + 1), ((swr->bpr & 0x00ff0000) >> 16)); } break; case 1: /* one byte left */ if (MBI_MR(offset) < dvmasize) { DVMA[offset] = (swr->bpr & 0xff000000) >> 24; } else { sw_flush_vmebyte(c, (offset), ((swr->bpr & 0xff000000) >> 24)); } break; } return (OK); } sw_flush_vmebyte(c, offset, byte) struct scsi_ctlr *c; int offset; u_char byte; { u_char *mapaddr; u_int pv; #ifdef sun3x pv = btop (VME24D16_BASE + (offset & VME24D16_MASK)); #else sun3x pv = PGT_VME_D16 | btop(VME24_BASE | (offset & VME24_MASK)); #endif sun3x mapaddr = (u_char *) ((u_long) kmxtob(c->c_kment) | (u_long) MBI_OFFSET(offset)); segkmem_mapin(&kseg, (addr_t) (((int)mapaddr) & PAGEMASK), (u_int) mmu_ptob(1), PROT_READ | PROT_WRITE, pv, 0); *mapaddr = byte; DPRINTF2("sw_flush_vmebyte: mapaddr = %x, byte= %x\n", mapaddr, byte); segkmem_mapout(&kseg, (addr_t) (((int)mapaddr) & PAGEMASK), (u_int) mmu_ptob(1)); } /* * Handle a scsi interrupt. */ swintr(c) register struct scsi_ctlr *c; { register struct scsi_sw_reg *swr; register struct scsi_unit *un; register u_char *cp; register int resid; int status; short lun; u_short bcr; /* get it for discon stuff BEFORE we clr int */ u_char msg; (int)swr = c->c_reg; /* * For vme host adaptor interface, must disable dma before * accessing any registers other than the csr or the * SI_CSR_DMA_CONFLICT bit in the csr will be set. */ /* DPRINTF("swintr:\n"); */ HEAD_SWINTR: un = c->c_un; if (swr->csr & SI_CSR_DMA_CONFLICT) printf("swintr: head CSR %X\n", swr->csr); swr->csr &= ~SI_CSR_DMA_EN; SW_VME_OK(c, swr, "top of swintr"); /* * We need to store the contents of the byte count register * before we change the state of the 5380. The 5380 has * a habit of prefetching data before it knows whether it * needs it or not, and this can throw off the bcr. * Note: Cobra does not have a bcr so we must figure * this out from the dma_addr register. Also un could be zero * coming into the interrupt routine so we need to check it * and not use it if it is (will this cause problems later?) */ resid = 0; bcr = 0; SCSI_TRACE('i', swr, un); if (un != NULL) { /* prepare for a disconnect */ if (un->un_dma_curdir != SI_NO_DATA) { bcr = un->un_dma_curcnt - (swr->dma_addr - un->un_dma_curaddr); DPRINTF2("swintr: bcr= %X tcr= %X ", bcr, SBC_RD.tcr); DPRINTF2("dma addr= %X cur addr= %X ", swr->dma_addr, un->un_dma_curaddr); DPRINTF1("count= %X\n", un->un_dma_curcnt ); if ((swr->dma_addr - un->un_dma_curaddr) && !(SBC_RD.tcr & SBC_TCR_LAST) && (un->un_dma_curdir == SI_SEND_DATA)) bcr++; } } /* * Determine source of interrupt. */ if (swr->csr & (SI_CSR_DMA_CONFLICT | SI_CSR_DMA_BUS_ERR)) { /* * DMA related error. */ if (swr->csr & SI_CSR_DMA_BUS_ERR) { printf("sw%d: swintr: bus error during dma\n", SWNUM(c)); /* goto RESET_AND_LEAVE; */ } else if (swr->csr & SI_CSR_DMA_CONFLICT) { printf("sw%d: swintr: invalid reg access during dma\n", SWNUM(c)); } else { /* * I really think that this is also DMA * in progress. */ printf("sw%d: swintr: dma overrun\n", SWNUM(c)); } junk = SBC_RD.clr; /* clear int, if any */ sw_print_state(swr, c); /* * Either we were waiting for an interrupt on a phase change * on the scsi bus, an interrupt on a reconnect attempt, * or an interrupt upon completion of a real dma operation. * Each of these situations must be handled appropriately. */ if (un == NULL) { /* Spurious reconnect. */ printf("sw%d: swintr: illegal reconnection for DMA\n", SWNUM(c)); goto RESET_AND_LEAVE; } else if (un->un_flags & SC_UNF_DMA_ACTIVE) { /* * Unit was DMAing, must clean up. * This is a bit tricky. The bcr is set to * zero for the SCSI-3 host adaptor until we * actually go into data phase. If we run * a data xfer command, but never go into * data phase, the bcr will be zero. We must * guard against interpeting this as meaning * that we have transferred all our data - * in this instance, we have really transferred none. * SC_UNF_DMA_INITIALIZED tells us (for SCSI-3) * whether we have loaded the bcr. */ if (un->un_flags & SC_UNF_DMA_INITIALIZED) { resid = un->un_dma_curcnt = bcr; } else { resid = un->un_dma_curcnt; } sw_dma_cleanup(c); un->un_flags &= ~SC_UNF_DMA_ACTIVE; } /* * We have probably blew it on the DMA and should reset. * But, let's pospone it until things really screw-up. */ goto HANDLE_SPURIOUS_AND_LEAVE; } /* * We have an SBC interrupt due to a phase change on the bus * or a reconnection attempt. First, check for reconnect * attempt. */ if (((SBC_RD.cbsr & SBC_CBSR_RESEL) == SBC_CBSR_RESEL) && (SBC_RD.cdr & scsi_host_id)) { register u_char cdr; register int i; HANDLE_RECONNECT: /* get reselecting target scsi id */ /* CRITICAL CODE SECTION DON'T TOUCH */ junk = SBC_RD.clr; /* clear int */ /*SBC_WR.ser = 0; /* clear (re)sel int */ cdr = SBC_RD.cdr & ~scsi_host_id; /* make sure there are only 2 scsi id's set */ DPRINTF1("swintr: reconnecting, cdr 0x%x\n", cdr); for (i=0; i < 8; i++) { if (cdr & (1<2 scsi reselection ids, cdr 0x%x\n", SWNUM(c), SBC_RD.cdr); SBC_WR.ser = scsi_host_id; /* enable (re)sel int */ goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; } /* acknowledge reselection */ #ifdef SCSI_DEBUG if (scsi_debug > 2) { printf("swintr: before resel ack\n"); sw_print_state(swr, c); } #endif SCSI_DEBUG SBC_WR.icr |= SBC_ICR_BUSY; c->c_recon_target = i; /* save for reconnect */ #ifdef SCSI_DEBUG if (scsi_debug > 2) { printf("swintr: after resel ack\n"); sw_print_state(swr, c); } #endif SCSI_DEBUG /* * If reselection ok, target should drop select. * Otherwise, we took too long. It would be nice * to wait for next resel attempt...someday. */ if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_SEL, SI_WAIT_COUNT, 0) != OK) { printf("sw%d: swintr: SEL not released\n", SWNUM(c)); SBC_WR.icr &= ~SBC_ICR_BUSY; SBC_WR.ser = scsi_host_id; /* enable (re)sel int */ goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; } SBC_WR.icr &= ~SBC_ICR_BUSY; #ifdef SCSI_DEBUG if (scsi_debug > 2) { printf("swintr: reconnecting\n"); sw_print_state(swr, c); } #endif SCSI_DEBUG /* After reselection, target should go into MSG_IN phase. */ if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_WAIT_COUNT, 1) != OK) { printf("sw%d: swintr: no MSG_IN req\n", SWNUM(c)); SBC_WR.ser = scsi_host_id; /* enable (re)sel int */ /* goto RESET_AND_LEAVE; */ goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; } c->c_last_phase = PHASE_RECONNECT; SBC_WR.ser = 0; /* clear (re)sel int */ SBC_WR.ser = scsi_host_id; /* enable (re)sel int */ /* FALL THROUGH INTO SYNCHRONIZE_PHASE */ } SBC_WR.tcr = TCR_UNSPECIFIED; SYNCHRONIZE_PHASE: /* * We know that we have a new phase we have to handle. */ DPRINTF("swintr: synch\n"); if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_PHASE_WAIT, 1) != OK) { /* DPRINTF("swintr: phase timeout\n"); */ goto HANDLE_SPURIOUS_AND_LEAVE; } SW_WIN; junk = SBC_RD.clr; /* clear any pending sbc interrupt */ un = c->c_un; switch (SBC_RD.cbsr & CBSR_PHASE_BITS) { case PHASE_DATA_IN: case PHASE_DATA_OUT: DPRINTF(" DATA\n"); #ifdef SCSI_DEBUG if (scsi_debug > 2) sw_print_state(swr, c); #endif SCSI_DEBUG SW_VME_OK(c, swr, "swintr: data_out"); SBC_WR.mr &= ~SBC_MR_DMA; /* clear phase int */ if (un == NULL) { printf("sw%d: swintr: no unit expecting DATA phase\n", SWNUM(c)); goto RESET_AND_LEAVE; } if (un->un_dma_curcnt == 0 || un->un_dma_curdir == SI_NO_DATA) { printf("sw%d: swintr: unexpected DATA phase, curcnt %d, curdir 0x%x\n", SWNUM(c), un->un_dma_curcnt, un->un_dma_curdir); goto RESET_AND_LEAVE; } /* data is expected, start dma data transfer and exit */ sw_sbc_dma_setup(c, swr, (int)un->un_dma_curdir); goto LEAVE; case PHASE_MSG_IN: DPRINTF(" MSG"); #ifdef SCSI_DEBUG if (scsi_debug > 2) sw_print_state(swr, c); #endif SCSI_DEBUG SW_VME_OK(c, swr, "swintr: msg_in"); msg = SBC_RD.cdr; /* peek at message */ lun = msg & 0x07; msg &= 0xf0; /* mask off unit number */ /* DPRINTF2("swintr: msg 0x%x, lun %d\n", msg, lun); */ if ((msg == SC_IDENTIFY) || (msg == SC_DR_IDENTIFY)) { /* * If we have a reconnect, we want to do our * DMA setup before we go into DATA phase. * This is why we peek at the message via the cdr * rather than doing an sw_getdata from the start. * sw_reconnect acknowledges the reconnect message. */ /* EPRINTF("swintr: msg= identify\n"); */ if (sw_reconnect(c, c->c_recon_target, lun, 0) != OK){ printf("sw$d: swintr: reconnection failure\n", SWNUM(c)); goto RESET_AND_LEAVE; } DPRINTF("swintr: after return from sw_reconnect\n"); un = c->c_un; if ( un->un_dma_curdir == SI_SEND_DATA) { DPRINTF2("cnt= %d, dma= %d ", un->un_dma_curcnt, un->un_dma_count); DPRINTF2("tgt= %d, lun= %d\n", un->un_target, un->un_lun); } /* DMA is setup, but NOT running */ un->un_flags &= ~SC_UNF_DMA_INITIALIZED; c->c_last_phase = PHASE_RECONNECT; goto SYNCHRONIZE_PHASE; } if (SBC_RD.cdr == SC_DISCONNECT) { if (un->un_dma_curdir == SI_RECV_DATA) { un->un_dma_curbcr = bcr; SCSI_RECON_TRACE('a', c, (int)un->un_dma_curaddr, (int)un->un_dma_curbcr, (int)swr->bcr); } } c->c_last_phase = PHASE_MSG_IN; msg = sw_getdata(c, PHASE_MSG_IN); switch (msg) { case SC_COMMAND_COMPLETE: DPRINTF("swintr: msg= command complete\n"); c->c_last_phase = PHASE_CMD_CPLT; cp = (u_char *) &un->un_scb; if (cp[0] & SCB_STATUS_MASK) status = SE_RETRYABLE; else status = SE_NO_ERROR; goto HAND_OFF_INTR; case SC_DISCONNECT: DPRINTF("swintr: msg= disconnect\n"); c->c_last_phase = PHASE_DISCONNECT; if ((un->un_flags & SC_UNF_DMA_INITIALIZED) == 0 && (un->un_dma_curdir == SI_SEND_DATA) && (un->un_dma_curcnt != un->un_dma_count)) { EPRINTF("swintr: Warning, write disconnect w/o DMA\n"); EPRINTF2("\tcnt= %d (%d) ", un->un_dma_curcnt, un->un_dma_count); EPRINTF2("bcr= %d tgt= %d ", un->un_dma_curbcr, un->un_target); EPRINTF1("lun= %d cdb =", un->un_lun); #ifdef SCSI_DEBUG if (scsi_debug) sw_print_cdb(un); #endif SCSI_DEBUG } if (sw_disconnect(c) != OK) { printf("sw%d: swintr: disconnect failure\n", SWNUM(c)); goto RESET_AND_LEAVE; } goto START_NEXT_COMMAND_AND_LEAVE; case SC_RESTORE_PTRS: /* these messages are noise - ignore them */ DPRINTF("swintr: msg= restore pointer\n"); c->c_last_phase = PHASE_RESTORE_PTR; goto SYNCHRONIZE_PHASE; case SC_SAVE_DATA_PTR: /* save the bcr before the bastard pre-fetches again */ DPRINTF("swintr: msg= save pointer\n"); un->un_dma_curbcr = bcr; c->c_last_phase = PHASE_SAVE_PTR; goto SYNCHRONIZE_PHASE; case SC_SYNCHRONOUS: EPRINTF("swintr: msg= extended\n"); if ((SBC_RD.cbsr & CBSR_PHASE_BITS) == PHASE_MSG_IN) { SBC_WR.icr = SBC_ICR_ATN; /* accept 255 message bytes (overkill) */ msg = 255; while((sw_getdata(c, PHASE_MSG_IN) != 0xff) && --msg); if ((SBC_RD.cbsr & CBSR_PHASE_BITS) == PHASE_MSG_OUT) { msg = SC_MSG_REJECT; SBC_WR.icr = 0; /* turn off ATN */ (void) sw_putdata(c, PHASE_MSG_OUT, &msg, 1, 0); } } goto SYNCHRONIZE_PHASE; case SC_NO_OP: EPRINTF("swintr: msg= no op\n"); goto SYNCHRONIZE_PHASE; case SC_PARITY: EPRINTF("swintr: msg= parity error\n"); goto SYNCHRONIZE_PHASE; case SC_ABORT: EPRINTF("swintr: msg= abort\n"); goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; case SC_DEVICE_RESET: EPRINTF("swintr: msg= reset device\n"); goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; default: #ifdef SCSI_DEBUG printf("sw%d: swintr: ignoring unknown message= 0x%x\n", SWNUM(c), msg); sw_print_state(swr, c); #endif SCSI_DEBUG goto HANDLE_SPURIOUS_AND_LEAVE; } case PHASE_STATUS: DPRINTF(" STATUS"); #ifdef SCSI_DEBUG if (scsi_debug > 2) sw_print_state(swr, c); #endif SCSI_DEBUG SW_VME_OK(c, swr, "swintr: status"); if ((un->un_dma_curdir == SI_RECV_DATA) && (sw_dma_recv(c) != OK)) { /* DMA failure, time to reset SCSI bus */ printf("sw%d: swintr: DMA failure\n", SWNUM(c)); goto RESET_AND_LEAVE; } /* DPRINTF("swintr: getting status bytes\n"); */ cp = (u_char *) &un->un_scb; cp[0] = sw_getdata(c, PHASE_STATUS); if (cp[0] == 0xff) { /* status failure, time to reset SCSI bus */ printf("sw%d: swintr: no status\n", SWNUM(c)); goto RESET_AND_LEAVE; } SCSI_TRACE('s', swr, un); c->c_last_phase = PHASE_STATUS; goto SYNCHRONIZE_PHASE; default: printf("sw%d: swintr: ignoring spurious phase\n", SWNUM(c)); sw_print_state(swr, c); goto HANDLE_SPURIOUS_AND_LEAVE; } HAND_OFF_INTR: DPRINTF("hand_off_intr:\n"); SW_VME_OK(c, swr, "swintr: hand off intr"); c->c_tab.b_active &= 0x1; /* Release que interlock */ swr->csr &= ~SI_CSR_DMA_EN; /* pass interrupt info to unit */ if (un && un->un_wantint) { un->un_wantint = 0; SCSI_RECON_TRACE('f', c, 0, 0, 0); if (un->un_dma_curdir != SI_NO_DATA) { if (bcr == 0xffff) /* fix pre-fetch botch */ bcr = 0; /* * This is a bit tricky. The bcr is set to * zero for the SCSI-3 host adaptor until we * actually go into data phase. If we run * a data xfer command, but never go into * data phase, the bcr will be zero. We must * guard against interpeting this as meaning * that we have transferred all our data - * in this instance, we didn't really transfer any. * SC_UNF_DMA_INITIALIZED is used to handle * the problem. */ if (un->un_flags & SC_UNF_DMA_INITIALIZED) { /* bcr is valid */ resid = un->un_dma_curcnt = bcr; } else { /* bcr is invalid */ resid = un->un_dma_curcnt; } } #ifdef SCSI_DEBUG if ((scsi_debug > 2) && resid) { printf("swintr: residue error\n"); sw_print_state(swr, c); } #endif SCSI_DEBUG /* call high-level scsi device interrupt handler to finish */ (*un->un_ss->ss_intr)(c, resid, status); un = c->c_un; swr->csr &= ~SI_CSR_DMA_EN; } /* fall through to start_next_command_and_leave */ START_NEXT_COMMAND_AND_LEAVE: /* start next I/O activity on controller */ DPRINTF("swintr: start_next_command\n"); if (IS_VME(c)) swr->csr &= ~SI_CSR_DMA_EN; SW_VME_OK(c, swr, "swintr: start next command"); /* * Check if we have a reconnect pending already. * This happens with some stupid SCSI controllers * which automatically disconnect, even when * they don't have to. Rather than field another * interrupt, let's go handle it. */ if (un == NULL) goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; if ((un->un_c->c_tab.b_actf) && (un->un_c->c_tab.b_active == 0)) { msg = SBC_RD.cbsr; if (((msg & SBC_CBSR_RESEL) == SBC_CBSR_RESEL) && (SBC_RD.cdr & scsi_host_id)) { DPRINTF1("swintr: recon1, cbsr= 0x%x\n", msg); goto HANDLE_RECONNECT; } } swstart(un); /* * Either we've started a command or there weren't any to start. * In any case, we're done... */ goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; RESET_AND_LEAVE: EPRINTF1("swintr: reset_and_leave: RESET to state 0x%x\n",sw_stbi); sw_reset(c, PRINT_MSG); goto SET_UP_FOR_NEXT_INTR_AND_LEAVE; HANDLE_SPURIOUS_AND_LEAVE: /* FALL THROUGH */ SET_UP_FOR_NEXT_INTR_AND_LEAVE: DPRINTF("swintr: set_up_for_next_intr\n"); swr->csr &= ~SI_CSR_DMA_EN; junk = SBC_RD.clr; /* clear int */ SW_VME_OK(c, swr, "swintr: setup for next intr"); /* * For depending on the last phase, we need to * check either for target reselection or a * SCSI bus phase change. */ if (c->c_last_phase == PHASE_CMD_CPLT || c->c_last_phase == PHASE_DISCONNECT) { /* Check for reselection. */ msg = SBC_RD.cbsr; if (((msg & SBC_CBSR_RESEL) == SBC_CBSR_RESEL) && (SBC_RD.cdr & scsi_host_id)) { SW_WIN; /* DPRINTF("swintr: recon2\n"); */ goto HEAD_SWINTR; } else { SW_LOSE; /* EPRINTF(" lose2"); */ } } else { /* Check for phase change. */ SBC_WR.tcr = TCR_UNSPECIFIED; SBC_WR.mr |= SBC_MR_DMA; junk = SBC_RD.clr; /* clear int */ msg = SBC_RD.cbsr; if (msg & SBC_CBSR_REQ) { SW_WIN; /* EPRINTF("swintr: recon3\n"); */ goto HEAD_SWINTR; } else { SW_LOSE; /* EPRINTF(" lose3"); */ } } /* Enable interrupts and DMA. */ swr->csr |= SI_CSR_DMA_EN; LEAVE: DPRINTF("\n"); return; } /* * Handle target disconnecting. * Returns true if all was OK, false otherwise. */ static sw_disconnect(c) register struct scsi_ctlr *c; { register struct scsi_unit *un = c->c_un; register struct scsi_sw_reg *swr = (struct scsi_sw_reg *)c->c_reg; register u_short bcr; bcr = un->un_dma_curbcr; /* DPRINTF("sw_disconnect:\n"); */ SCSI_RECON_TRACE('d', c, un->un_dma_curaddr, (int) un->un_dma_curbcr, (int) bcr); /* * If command doen't require dma, don't save dma info. * for reconnect. If it does, but data phase was missing, * don't update dma info. */ if ((un->un_dma_curdir != SI_NO_DATA) && (un->un_flags & SC_UNF_DMA_INITIALIZED)) { if ((un->un_dma_curdir == SI_RECV_DATA) && (sw_dma_recv(c) != OK)) { printf("sw%d: sw_disconnect: DMA bogosity\n",SWNUM(c)); return (FAIL); } /* * Save dma information so dma can be restarted when * a reconnect occurs. */ un->un_dma_curaddr += un->un_dma_curcnt - bcr; un->un_dma_curcnt = bcr; SCSI_RECON_TRACE('q', c, un->un_dma_curaddr, (int)un->un_dma_curcnt, (int)bcr); #ifdef SCSI_DEBUG if (bcr == 1) printf("sw_disconnect: bcr is 1\n"); if (sw_dis_debug) { printf("sw_disconnect: addr= 0x%x count= %d bcr= 0x%x ", un->un_dma_curaddr, un->un_dma_curcnt, bcr); printf("sr= 0x%x baddr= 0x%x\n", swr->csr, un->un_baddr); } #endif SCSI_DEBUG } /* * Remove this disconnected task from the ctlr ready queue and save * on disconnect queue until a reconnect is done. */ sw_discon_queue(un); SW_VME_OK(c, swr, "sw_disconnect"); swr->dma_count = 0; /* DPRINTF("sw_disconnect: done\n"); */ return (OK); } /* * Complete reselection phase and reconnect to target. * * Return OK if sucessful, FAIL if not. * * NOTE: this routine cannot use sw_getdata to get identify msg * from reconnecting target due to sun3/50 scsi interface. The bcr * must be setup before the target changes scsi bus to data phase * if the command being reconnected involves dma (which we do not * know until we get the identify msg). Thus we cannot acknowledge * the identify msg until some setup of the host adaptor registers * is done. */ sw_reconnect(c, target, lun, flag) register struct scsi_ctlr *c; register short target, lun; int flag; { register struct scsi_sw_reg *swr = (struct scsi_sw_reg *)(c->c_reg); register struct scsi_unit *un; /* search disconnect queue for reconnecting task */ DPRINTF2("sw_reconnect: target %d lun %d\n", target, lun); if (sw_recon_queue(c, target, lun, flag) != OK) return (FAIL); un = c->c_un; /* restart disconnect activity */ SCSI_RECON_TRACE('r', c, 0, 0, 0); if (un->un_dma_curdir != SI_NO_DATA) { /* do initial dma setup */ swr->dma_count = 0; if (un->un_dma_curdir == SI_RECV_DATA) swr->csr &= ~SI_CSR_SEND; else swr->csr |= SI_CSR_SEND; /* * New: we set up everything we can here, rather * than wait until data phase. */ swr->csr &= ~SI_CSR_DMA_EN; sw_dma_setup(c, un); #ifdef SCSI_DEBUG if (sw_dis_debug) { printf("sw_reconnect: addr= 0x%x count= 0x%x bcr= 0x%x", un->un_dma_curaddr, un->un_dma_curcnt, swr->bcr); printf(" sr= 0x%x baddr= 0x%x\n", swr->csr, un->un_baddr); } #endif SCSI_DEBUG } /* we can finally acknowledge identify message */ SBC_WR.icr = SBC_ICR_ACK; if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_WAIT_COUNT, 0) != OK) { printf("sw%d: sw_reconnect: REQ not INactive\n", SWNUM(c)); return (FAIL); } SBC_WR.icr = 0; /* clear ack */ return (OK); } /* * Remove timed-out I/O request and report error to * it's interrupt handler. * Return OK if sucessful, FAIL if not. */ sw_deque(c, un) register struct scsi_ctlr *c; register struct scsi_unit *un; { register struct scsi_sw_reg *swr = (struct scsi_sw_reg *)c->c_reg; int s; EPRINTF("sw_deque:\n"); /* Lock out the rest of sw till we've finished the dirty work. */ s = splr(pritospl(c->c_intpri)); /* * If current SCSI I/O request is the one that timed out, * Reset the SCSI bus as this looks serious. */ if (un == c->c_un) { if (swr->csr & (SI_CSR_SBC_IP | SI_CSR_DMA_CONFLICT | SI_CSR_DMA_BUS_ERR)) { /* Hardware lost int., attempt restart */ (void) splx(s); printf("sw%d: sw_deque: lost interrupt\n", SWNUM(c)); sw_print_state(swr, c); swintr(c); return (OK); } else { /* Really did timeout */ (void) splx(s); return (FAIL); } } if (c->c_tab.b_actf == NULL) { /* Search for entry on disconnect queue */ if (sw_recon_queue(c, un->un_target, un->un_lun, 0)) { /* died in active que, don't restart */ (void) splx(s); return (OK); } /* died in disconnect que, restart */ (void) splx(s); EPRINTF("sw_deque: reactivating request\n"); sw_print_state(swr, c); (*un->un_ss->ss_intr)(c, un->un_dma_count, SE_TIMEOUT); c->c_tab.b_active &= C_QUEUED; /* clear interlock */ return (OK); } else { /* Search for entry on disconnect queue; but don't reconnect */ if (sw_recon_queue(c, un->un_target, un->un_lun, 1)) { /* died in active que, don't restart */ (void) splx(s); return (OK); } /* died in disconnect que, restart */ (void) splx(s); EPRINTF("sw_deque: queue was not NULL\n"); return (FAIL); } } /* * No current activity for the scsi bus. May need to flush some * disconnected tasks if a scsi bus reset occurred before the * target reconnected, since a scsi bus reset causes targets to * "forget" about any disconnected activity. * Also, enable reconnect attempts. */ sw_idle(c, flag) register struct scsi_ctlr *c; int flag; { register struct scsi_unit *un; register struct scsi_sw_reg *swr = (struct scsi_sw_reg *)c->c_reg; int s, resid; /* DPRINTF("sw_idle:\n"); */ if (c->c_flags & SCSI_FLUSHING) { EPRINTF1("sw_idle: flushing, flags 0x%x\n", c->c_flags); return; } /* flush disconnect tasks if a reconnect will never occur */ if (c->c_flags & SCSI_FLUSH_DISQ) { EPRINTF("sw_idle: flushing disconnect que\n"); s = splr(pritospl(c->c_intpri)); /* time critical */ c->c_tab.b_active = C_ACTIVE; /* engage interlock */ /* * Force current I/O request to be preempted and put it * on disconnect que so we can flush it. */ un = (struct scsi_unit *)(c->c_tab.b_actf); if (un != NULL && un->un_md->md_utab.b_active & MD_IN_PROGRESS) { /*un->un_md->md_utab.b_active |= MD_PREEMPT;*/ sw_discon_queue(un); printf("si_idle: dequeueing active request\n"); } /* now in process of flushing tasks */ c->c_flags &= ~SCSI_FLUSH_DISQ; c->c_flags |= SCSI_FLUSHING; c->c_flush = c->c_disqtab.b_actl; for (un = (struct scsi_unit *)c->c_disqtab.b_actf; un && c->c_flush; un = (struct scsi_unit *)c->c_disqtab.b_actf) { /* keep track of last task to flush */ if (c->c_flush == (struct buf *) un) c->c_flush = NULL; /* requeue on controller active queue */ if (sw_recon_queue(c, un->un_target, un->un_lun, 0)) continue; /* inform device routines of error */ if (un->un_dma_curdir != SI_NO_DATA) { resid = un->un_dma_curcnt; } else { resid = 0; } (*un->un_ss->ss_intr)(c, resid, flag); sw_off(c, un, flag); /* unit is going offline */ } c->c_flags &= ~SCSI_FLUSHING; c->c_tab.b_active &= C_QUEUED; /* Clear interlock */ (void) splx(s); } /* enable reconnect attempts */ swr->csr &= ~SI_CSR_DMA_EN; /* turn off before SBC access */ swr->dma_count = 0; swr->csr &= ~SI_CSR_SEND; swr->csr |= SI_CSR_DMA_EN; } /* * Get status bytes from scsi bus. * Returns number of status bytes read if no error. * If error, returns -1. * If scsi bus error, returns 0. */ sw_getstatus(un) register struct scsi_unit *un; { register struct scsi_sw_reg *swr; register struct scsi_ctlr *c = un->un_c; register u_char *cp; register u_char msg; (int)swr = c->c_reg; /* DPRINTF("sw_getstatus:\n"); */ SW_VME_OK(c, swr, "sw_getstatus:"); if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_LONG_WAIT, 1) != OK) { printf("sw%d: sw_getstatus: REQ inactive\n", SWNUM(c)); sw_print_state(swr, c); return (0); } if (sw_wait_phase(swr, PHASE_STATUS) != OK) { printf("sw%d: sw_getstatus: no STATUS phase\n", SWNUM(c)); sw_print_state(swr, c); return (0); } cp = (u_char *) &un->un_scb; cp[0] = sw_getdata(c, PHASE_STATUS); DPRINTF1("si_getstatus: status = 0x%x", cp[0]); if (sw_wait_phase(swr, PHASE_MSG_IN) != OK) { printf("sw%d: sw_getstatus: no MSG_IN phase\n", SWNUM(c)); sw_print_state(swr, c); return (0); } SW_VME_OK(c, swr, "sw_getstatus: msg_in"); msg = sw_getdata(c, PHASE_MSG_IN); if (msg != SC_COMMAND_COMPLETE) { EPRINTF1("sw_getstatus: bogus msg_in 0x%x\n", msg); } SBC_WR.tcr = TCR_UNSPECIFIED; /* * Check status for error condition, return -1 if error. * Otherwise, return 1 for no error. */ if (cp[0] & SCB_STATUS_MASK) return (-1); /* retryable */ return (1); /* no error */ } /* * Wait for a scsi dma request to complete. * Disconnects were disabled in sw_cmd when polling for command completion. * Called by drivers in order to poll on command completion. */ sw_cmdwait(c) register struct scsi_ctlr *c; { register struct scsi_sw_reg *swr; register struct scsi_unit *un = c->c_un; register int ret_val; (int)swr = c->c_reg; DPRINTF("sw_cmdwait:\n"); /* if command does not involve dma activity, then we are finished */ if (un->un_dma_curdir == SI_NO_DATA) { return (OK); } /* wait for indication of dma completion */ if (sw_wait((u_int *)&swr->csr, (SI_CSR_SBC_IP | SI_CSR_DMA_CONFLICT | SI_CSR_DMA_BUS_ERR), 1) != OK) { printf("sw%d: sw_cmdwait: dma never completed\n", SWNUM(c)); sw_reset(c, PRINT_MSG); ret_val = SCSI_FAIL; goto SW_CMDWAIT_EXIT; } /* * Early Cobra units have a faulty gate array. * It can cause an illegal memory access if full * page DMA is being used. For less than a full * page, no problem. This problem typically shows * up when dumping core. The following code fixes * this problem. */ swr->csr &= ~SI_CSR_DMA_EN; if(swr->csr & SI_CSR_DMA_BUS_ERR){ (void) sw_dma_recv(c); junk = SBC_RD.clr; ret_val = OK; goto SW_CMDWAIT_EXIT; } /* handle special dma recv situations */ if ((un->un_dma_curdir == SI_RECV_DATA) && (sw_dma_recv(c) != OK)) { printf("sw%d: sw_cmdwait: special DMA failure\n", SWNUM(c)); sw_reset(c, PRINT_MSG); ret_val = SCSI_FAIL; goto SW_CMDWAIT_EXIT; } /* ack sbc interrupt and cleanup */ junk = SBC_RD.clr; sw_dma_cleanup(c); ret_val = OK; SW_CMDWAIT_EXIT: if (swr->csr & SI_CSR_SBC_IP) { junk = SBC_RD.clr; /* clear sbc int */ } swr->csr &= ~SI_CSR_DMA_EN; /* turn it off to be sure */ return (ret_val); } /* * Wait for a condition to be (de)asserted on the scsi bus. * Returns OK for successful. Otherwise, returns * FAIL. */ static sw_sbc_wait(reg, cond, wait_count, set) register caddr_t reg; register u_char cond; register int wait_count; register int set; { register int i; register u_char regval; for (i = 0; i < wait_count; i++) { regval = *reg; if ((set == 1) && (regval & cond)) { return (OK); } if ((set == 0) && !(regval & cond)) { return (OK); } DELAY(10); } DPRINTF("sw_sbc_wait: timeout\n"); return (FAIL); } /* * Wait for a condition to be (de)asserted. * Used for monitor DMA controller. * Returns OK for successful. Otherwise, * returns FAIL. */ static sw_wait(reg, cond, set) register u_int *reg; register u_int cond; register int set; { register int i; register u_int regval; for (i = 0; i < SI_WAIT_COUNT; i++) { regval = *reg; if ((set == 1) && (regval & cond)) { return (OK); } if ((set == 0) && !(regval & cond)) { return (OK); } DELAY(10); } return (FAIL); } /* * Wait for a phase on the SCSI bus. * Returns OK for successful. Otherwise, * returns FAIL. */ static sw_wait_phase(swr, phase) register struct scsi_sw_reg *swr; register u_char phase; { register int i; DPRINTF2("sw_wait_phase: %s phase (0x%x)\n",sw_str_phase(phase),phase); for (i = 0; i < SI_WAIT_COUNT; i++) { if ((SBC_RD.cbsr & CBSR_PHASE_BITS) == phase) return (OK); DELAY(10); } return (FAIL); } /* * Put data onto the scsi bus. * Returns OK if successful, FAIL otherwise. */ static sw_putdata(c, phase, data, num, want_intr) register struct scsi_ctlr *c; register u_short phase; register u_char *data; register u_char num; register int want_intr; { register struct scsi_sw_reg *swr; register u_char i; swr = (struct scsi_sw_reg *)c->c_reg; DPRINTF2("sw_putdata: %s phase (0x%x): ", sw_str_phase(phase), phase); SW_VME_OK(c, swr, "sw_putdata"); /* Set up tcr so we can transmit data. */ SBC_WR.tcr = phase >> 2; /* put all desired bytes onto scsi bus */ for (i = 0; i < num; i++ ) { SBC_WR.icr = SBC_ICR_DATA; /* clear ack, enable data bus */ /* wait for target to request a byte */ if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_WAIT_COUNT, 1) != OK) { printf("sw%d: putdata, REQ not active\n", SWNUM(c)); return (FAIL); } /* load data */ DPRINTF1(" 0x%x", *data); SBC_WR.odr = *data++; /* complete req/ack handshake */ SBC_WR.icr |= SBC_ICR_ACK; if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_WAIT_COUNT, 0) != OK) { printf("sw%d: putdata, req not INactive\n", SWNUM(c)); return (FAIL); } } DPRINTF("\n"); SBC_WR.tcr = TCR_UNSPECIFIED; junk = SBC_RD.clr; /* clear int */ if (want_intr) { /* CRITICAL CODE SECTION DON'T TOUCH */ SBC_WR.mr |= SBC_MR_DMA; SBC_WR.icr = 0; /* ack last byte */ } else { SBC_WR.icr = 0; /* clear ack */ } return (OK); } /* * Get data from the scsi bus. * Returns a single byte of data, -1 if unsuccessful. */ static sw_getdata(c, phase) register struct scsi_ctlr *c; register u_short phase; { register struct scsi_sw_reg *swr; register u_char data; register u_char icr; (int)swr = c->c_reg; DPRINTF2("sw_getdata: %s phase (0x%x)",sw_str_phase(phase),phase); SW_VME_OK(c, swr, "sw_getdata"); /* wait for target request */ if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_WAIT_COUNT, 1) != OK) { printf("sw%d: getdata, REQ not active, cbsr 0x%x\n", SWNUM(c), SBC_RD.cbsr); sw_print_state(swr, c); return (-1); } if ((SBC_RD.cbsr & CBSR_PHASE_BITS) != phase) { /* not the phase we expected */ DPRINTF1("sw_getdata: unexpected phase, expecting %s\n", sw_str_phase(phase)); return (-1); } /* grab data and complete req/ack handshake */ data = SBC_RD.cdr; icr = SBC_WR.icr; DPRINTF1(" icr= %x ", icr); SBC_WR.icr = icr | SBC_ICR_ACK; if (sw_sbc_wait((caddr_t)&SBC_RD.cbsr, SBC_CBSR_REQ, SI_WAIT_COUNT, 0) != OK) { printf("sw%d: getdata, REQ not inactive\n", SWNUM(c)); return (-1); } DPRINTF1(" data= 0x%x\n", data); if ((phase == PHASE_MSG_IN) && ((data == SC_COMMAND_COMPLETE) || (data == SC_DISCONNECT))) { /* CRITICAL CODE SECTION DON'T TOUCH */ SBC_WR.tcr = TCR_UNSPECIFIED; SBC_WR.mr &= ~SBC_MR_DMA; /* clear phase int */ junk = SBC_RD.clr; /* clear int */ SBC_WR.icr = icr; /* clear ack */ } else { SBC_WR.icr = icr; /* clear ack */ } return (data); } /* * Reset SCSI control logic and bus. */ sw_reset(c, msg_enable) register struct scsi_ctlr *c; register int msg_enable; { register struct scsi_sw_reg *swr; (int)swr = c->c_reg; if (msg_enable) { printf("sw%d: resetting scsi bus\n", SWNUM(c)); sw_print_state(swr, c); DEBUG_DELAY(100000); } /* reset scsi control logic */ swr->csr = 0; DELAY(10); swr->csr = SI_CSR_SCSI_RES; swr->dma_addr = 0; swr->dma_count = 0; /* issue scsi bus reset (make sure interrupts from sbc are disabled) */ SBC_WR.icr = SBC_ICR_RST; DELAY(1000); SBC_WR.icr = 0; /* clear reset */ /* give reset scsi devices time to recover (> 2 Sec) */ DELAY(scsi_reset_delay); junk = SBC_RD.clr; /* Disable sbc interrupts. Reconnects enabled by sw_cmd. */ SBC_WR.mr &= ~SBC_MR_DMA; /* clear phase int */ swr->csr |= SI_CSR_INTR_EN; /* disconnect queue needs to be flushed */ if ((c->c_tab.b_actf != NULL) || (c->c_disqtab.b_actf != NULL)) { c->c_flags |= SCSI_FLUSH_DISQ; sw_idle(c, SE_TIMEOUT); } } /* * Return residual count for a dma. */ /*ARGSUSED*/ sw_dmacnt(c) struct scsi_ctlr *c; { return (0); } #endif NSW > 0