#ifndef lint static char sccsid[] = "@(#)ar.c 1.1 94/10/31 Copyr 1986 Sun Micro"; #endif /* * Copyright (c) 1986 by Sun Microsystems, Inc. */ /* * Standalone Driver for Archive Intelligent Streaming Tape */ #include #include #include #include #define min(a,b) ((a) < (b) ? (a) : (b)) int ardebug = 0; #define Dprintf if (ardebug) printf /* Trace all writes to control reg */ #define DebugTrace Dprintf(ar_ctrl_hdr, *(long*)(2+(char*)araddr)) /* Define assorted debugging strings to save repeating them N times */ #ifdef DEBUG char ar_bits[] = ARCH_LONG_CTRL_BITS; char ar_ctrl_hdr[] = "ar* Bits: %x\n"; char ar_set_hdr[] = "ar* CMD: %x\n"; #endif DEBUG char ar_stat_bits[] = ARCH_BITS; /* * Standard I/O addresses. */ #define NARADDR 2 u_long araddrs[] = { 0x200, 0x208}; /* * What resources we need to run */ struct devinfo arinfo = { sizeof (struct ardevice), /* I/O regs */ 0, /* no DMA */ sizeof (struct ar_softc), /* work area */ NARADDR, araddrs, MAP_MBIO, DEV_BSIZE, /* transfer size */ }; /* * What facilities we export to the world */ int aropen(), arclose(), arstrategy(); extern int xxboot(), xxprobe(); struct boottab ardriver = { "ar", xxprobe, xxboot, aropen, arclose, arstrategy, "ar: Multibus Archive tape controller", &arinfo, }; /* * Translation between generic tape commands and initial states of * the Archive state machine. */ enum ARstates ar_cmds[] = { /* CLOSE */ CLOSEinit, /* REWIND */ REWinit, /* STATUS */ RDSTinit, /* READ */ READinit, /* WRITE */ WRinit, /* WEOF */ WFMinit, /* ERASE */ ERASEinit, /* SELECT */ SELinit, /* DELSELECT */ DESELinit, /* TENSE */ TENSEinit, /* SKIPFILE */ RFMinit, /* CMD OK? */ CMDOKinit, }; #define SPININIT 1000000 /* * Initialize a controller */ aropen(sip) register struct saioreq *sip; { register struct ar_softc *sc; register int count; register struct ardevice *araddr; int skip; sc = (struct ar_softc *)sip->si_devdata; araddr = sc->sc_addr = (struct ardevice *)sip->si_devaddr; sc->sc_lastiow = 0; sc->sc_state = IDLEstate; /* sc->sc_status = 0; Doesn't work, so leave it alone. */ sc->sc_size = 0; sc->sc_bufptr = (char *) 0x1ff001; /* very funny buf addr */ /* sc->sc_attached had better already be 1. */ sc->sc_initted = 0; /* until later */ sc->sc_opened = 0; /* until later */ sc->sc_drive = 0; sc->sc_histate = 0; /* sc->sc_addr had better already be initialized. */ sc->sc_qidle = 1; sc->sc_eoflag = 0; sc->sc_cmdok = 0; sc->sc_selecteddev = -1; /* When adding new fields to softc, be sure to initialize them here. */ /* FIXME, should initialize buffer headers here too */ araddr->arunwedge = 1; /* Take it out of burst mode wedge */ araddr->arburst = 0; araddr->arreq = 0; araddr->arxfer = 0; araddr->arcatch = 0; araddr->arexcie = 0; araddr->arrdyie = 0; /* * If tape is up from previous system operation, * take it down gently. */ if (araddr->aronline) { Dprintf("ar*init tape online\n"); araddr->aronline = 0; /* Writes TM (if writing) & rewinds */ } count = SPININIT; while (!araddr->arrdy && !araddr->arexc && count) count--; if (count == 0) Dprintf("ar: Timeout waiting for Ready at %x\n", araddr); if (araddr->arrdy) Dprintf("ar*init arrdy on before reset\n"); if (araddr->arexc) Dprintf("ar*init arexc on before reset\n"); /* Tape is ready or exceptional. Reset it for good measure. */ #ifdef PRF Dprintf("ar*init about to arreset\n"); #endif PRF araddr->arreset = 1; #ifdef PRF Dprintf("ar*init asserted arreset\n"); #endif PRF DELAY(25); /* at least 13 usec */ araddr->arreset = 0; #ifdef PRF Dprintf("ar*init Reset complete\n"); #endif PRF count = SPININIT; while (!araddr->arexc && count) count--; if (count == 0) { Dprintf("ar: Timeout waiting for Exception after reset\n"); return (-1); } /* Now read back status from the reset. */ sc->sc_initted = 1; /* Must do first so interrupt OK */ sc->sc_opened = 1; /* Must do first so interrupt OK */ araddr->aronline = 1; /* Must do first so RDST microcode doesn't play games with arrdy line. See comments in open(). */ if (arcmd(sc, AR_STATUS)) { Dprintf("ar*init Error from command STATUS\n"); araddr->aronline = 0; sc->sc_initted = 0; /* Try again on next open */ sc->sc_opened = 0; /* Try again on next open */ return (-1); } /* * FIXME, this is a kludge. open() won't select the drive unless * the in-core status claims we are at BOT, since the tape drive * will reject the command if indeed a tape was in use and is not * at BOT. However, tapes at BOT after a Reset do not necessarily * indicate BOT in their status. We should probably do a rewind * here instead, if the tape exists. */ sc->sc_status.BOT = 1; /* Pretend we're at BOT for open() */ /* * There is a problem in the Archive in that after each command, * it goes thru a cleanup routine. If aronline is not asserted, * this cleanup routine drops arrdy while it rewinds the tape * to BOT. It deasserts arrdy for 90 us even if the tape is * already at BOT. This causes us problems because we get a arrdy * interrupt and then discover that arrdy is gone. * * The problem has been circumvented at the low level by checking * for arrdy in the interrupt routine, and looping until it (or * arexc) comes on. We attempt to fix the problem here, to avoid * looping at SPL(), by having aronline always on when we are doing * anything. * * The problem seems especially a problem for us on Select and Read * Status commands. (That's because those are the only commands we * do with aronline deasserted.) * * This info was obtained from Lou Domshy, Mgr. of Product Mgmt and * Applications Engineering(?) at the Archive factory, 714-641-0279, * on 1 December 1982, by John Gilmore of Sun Microsystems. */ araddr->aronline = 1; /* Let ctrlr know we are doing a series */ /* * First select the drive we're interested in. * * Since the select command doesn't work when we aren't at BOT, * we just have to hope the same drive is still selected as last * time. FIXME. We should record this info in softc and keep it * up to date. FIXME: also, I'm not happy about using status.BOT * here, even tho it should always be up to date. -- JCGnu 22Nov82 */ sc->sc_cmdok = 0; (void) arcmd(sc, AR_CMDOK); /* See if OK to issue cmds */ /* * Now get its status and check on a few things. */ if (sc->sc_cmdok) { if (arcmd(sc, AR_STATUS)) { /* interrupted */ Dprintf("ar*open command STATUS error\n"); goto err; } if (sc->sc_status.NoDrive) { printf("ar: no drive\n"); goto err; } if (sc->sc_status.NoCart) { printf("ar: no cartridge in drive\n"); goto err; } } if ((sip->si_flgs&F_WRITE) && sc->sc_status.WriteProt) { printf("ar: cartridge is write protected\n"); goto err; } sc->sc_lastiow = 0; skip = sip->si_boff; while (skip--) { arcmd(sc, AR_SKIPFILE); arcmd(sc, AR_STATUS); } sc->sc_eoflag = 0; Dprintf("ar*open exiting\n"); return (0); err: arcmd(sc, AR_DESELECT); araddr->aronline = 0; sc->sc_opened = 0; return (-1); } /* * Close tape device. * * If tape was open for writing or last operation was * a write, then write two EOF's and backspace over the last one. * Unless this is a non-rewinding special file, rewind the tape. * Make the tape available to others. */ arclose(sip) struct saioreq *sip; { register struct ar_softc *sc = (struct ar_softc *)sip->si_devdata; register struct ardevice *araddr = sc->sc_addr; /* * Write file mark and rewind, by dropping aronline. * FIXME. These 3 commands should be moved into AR_CLOSE * in order that the user program can continue while the * tape is rewinding. */ arcmd(sc, AR_CLOSE); /* Shut down things */ araddr->aronline = 1; /* After rewind, set aronline */ /* See comments in open() about aronline and read status cmds */ /* FIXME, this might screw low level code if it affects arrdy */ arcmd(sc, AR_STATUS); /* Read block counts */ arcmd(sc, AR_DESELECT); /* Turn LED off */ sc->sc_selecteddev = -1; sc->sc_eoflag = 0; /* Not at eof after rewind */ sc->sc_opened = 0; /* Available to be opened again */ Dprintf("ar*close exiting\n"); } arstrategy(sip, rw) register struct saioreq *sip; int rw; { register struct ar_softc *sc = (struct ar_softc *)sip->si_devdata; int func = (rw == WRITE) ? AR_WRITE : AR_READ; if (sc->sc_eoflag) { sc->sc_eoflag = 0; return (0); } sc->sc_size = sip->si_cc; sc->sc_bufptr = sip->si_ma; if (arcmd(sc, func)) return (-1); return (sip->si_cc); } /* * Begin execution of a device command for the device pointed to by * sc. The command begins execution in state newstate. * * This is HARDWARE oriented software. It doesn't know or care of * state of buffers, etc. Its result reflects what the hardware is * doing, not what the software is doing. * * The device is assumed to be in one of the FIN or IDLE states: * IDLEstate, FINstate, READfin, READidle, WRfin, or WRidle. * This is a requirement, since various fields in sc have already * been set up for us, and the use of those fields would conflict * with their use by the interrupt routine if we weren't idle or fin. * * Our result is: * 0 if the operation completed normally * 1 if the operation completed abnormally * */ arcmd(sc, cmd) register struct ar_softc *sc; { struct ardevice *araddr = sc->sc_addr; if (!sc->sc_opened) return (-1); sc->sc_state = ar_cmds[cmd]; for (;;) { if (araddr->arexc) { /* * This interrupt is from the true level of arexc. * * An error has occurred. Deal with it somehow. * If we are reading status, just do it; else do our own * RDST to find out the problem, and cancel the current * operation. */ if (sc->sc_state == RDSTinit) goto doit; Dprintf("ar*intr arexc set, old state %x\n", sc->sc_state); sc->sc_oldstate = sc->sc_state; sc->sc_state = RDSTinit; /* * Clear EdgeReady and enable it so the next arrdy * edge will be caught (possibly inside armachine()). */ araddr->arcatch = 0; araddr->arcatch = 1; while (!armachine(sc)) { /* Shouldn't happen! */ printf("ar*intr RDST did not return 1\n"); } if (!sc->sc_status.FileMark) { printf("ar: error %x\n", *(u_short *)&sc->sc_status); return (1); } /* Eof signaled now means that NEXT block is an EOF. */ sc->sc_eoflag = 1; return (0); } if (araddr->arrdy) { doit: araddr->arcatch = 0; araddr->arcatch = 1; if (armachine(sc)) return (0); } } } /* * State machine for archive tape drive controller. * This actually accomplishes things w.r.t. the tape drive. * Returns 0 if operation still in progress, 1 if finished. * Note that we may take further interrupts after claiming that an * operation is "finished". For example, we say a write is done when * we have transferred the last byte of the block; but there will be * an interrupt 5.5ms later to tell us it's ok to send the next block. * Eventually, we will rewind the tape asynchronously after the file is * closed, letting the user go free while it spins. FIXME: THIS CANNOT * BE DONE until we clean up the high level code so it doesn't clobber * our variables as it is setting up to call arstart_cmd(). */ armachine(sc) register struct ar_softc *sc; { register struct ardevice *araddr = sc->sc_addr; register int count, i, x; register char *byteptr; Dprintf("ar*machine(%x, %x) state %x\n", sc, araddr, sc->sc_state); switch (sc->sc_state) { case CMDOKinit: /* * If we got here the command state is ok, * i.e., not in the middle of a read or write. */ sc->sc_state = IDLEstate; sc->sc_cmdok = 1; return (1); case CLOSEinit: /* FIXME, this is time dependent and not documented in the Archive manual */ araddr->aronline = 0; /* Drop online; we're done. */ if (araddr->arrdy) goto IdleState; /* No interrupt will occur. */ else goto FinState; /* Rewinding; wait for interrupt. */ #ifdef FIXME case CLOSEend: /* This is entered from READidle or WRidle. */ araddr->aronline = 0; /* Drop it, causing rewind. */ goto FinState; /* Interrupt will signal end of rew. */ #endif case WFMinit: sc->sc_status.BOT = 0; araddr->ardata = ARCMD_WREOF; araddr->arreq = 1; goto CmdState; case RFMinit: araddr->ardata = ARCMD_RDEOF; araddr->arreq = 1; goto CmdState; case REWinit: araddr->ardata = ARCMD_REWIND; araddr->arreq = 1; goto CmdState; case TENSEinit: araddr->ardata = ARCMD_TENSION; araddr->arreq = 1; goto CmdState; case ERASEinit: araddr->ardata = ARCMD_ERASE; araddr->arreq = 1; goto CmdState; case SELinit: araddr->ardata = ARCMD_LED | (1 << sc->sc_drive); araddr->arreq = 1; goto CmdState; case DESELinit: araddr->ardata = 1 << sc->sc_drive; araddr->arreq = 1; goto CmdState; RDSTagain: printf("ar: RDST gave Exception, retrying\n"); /* Fall thru... */ case RDSTinit: byteptr = (char *) &sc->sc_status; /* We could have either arrdy or arexc; remember which */ count = 0; if (araddr->arrdy) count = 1; araddr->ardata = ARCMD_RDSTAT; araddr->arreq = 1; /* * Now wait for arrdy indicating command accepted. * Check for Exception, if we started with arrdy. * (It's not legal to do RDST all the time(!).) */ while (!araddr->arrdy) if (count && araddr->arexc) goto RDSTagain; /* Negate arreq, wait for arrdy to drop. */ araddr->arcatch = 0; /* Clear arrdyedge */ araddr->arcatch = 1; /* Catch edge */ araddr->arreq = 0; /* Now xfer a byte or six. */ do { /* Wait for edge of arrdy */ while (!araddr->arrdyedge) if (araddr->arexc) goto RDSTagain; *byteptr++ = araddr->ardata; araddr->arcatch = 0; /* Clear edge indicator */ araddr->arcatch = 1; /* Catch next one */ araddr->arreq = 1; /* Tell controller we have it */ /* Ready will fall within 250ns of our arreq, but we're supposed to keep it high for 20us */ DELAY(30); /* at least 20 usec */ araddr->arreq = 0; } while (byteptr < (char *)(&sc->sc_status) + sizeof (sc->sc_status)); /* * On exit from this loop, arcatch has been negated and * asserted, so it will correctly reflect the leading edge * of arrdy for the next command. */ sc->sc_oldstate = sc->sc_state; sc->sc_state = FINstate; /* Awaiting final interrupt */ /* Dump status bytes after a command AR_STATUS */ Dprintf("ar*RDST %x %d %d\n", *(unsigned short*)&sc->sc_status, sc->sc_status.SoftErrs, sc->sc_status.TapeStops); /* This code is obsolete since sc_qidle, and should be replaceable by a simple branch to RdWrFin. However, that doesn't work, so try this. JCGnu, 23Nov82 */ while (!araddr->arrdy) if (araddr->arexc) goto RDSTagain; /* We leave the edge of Ready caught in EdgeReady, but IdleState will disable interrupts on it. */ goto IdleState; case READcmd: case WRcmd: araddr->arreq = 0; goto next; case READinit: araddr->ardata = ARCMD_RDDATA; araddr->arreq = 1; goto next; case READburst: /* Read a block of data from the tape drive. */ Dprintf("ar*READ addr %x count %x\n", sc->sc_bufptr, sc->sc_size); sc->sc_status.BOT = 0; while (!araddr->arrdy) { if (araddr->arexc) return (0); /* Not yet done */ Dprintf("ar*Read no READY\n"); } araddr->arburst = 1; /* Begin block xfer */ count = min(sc->sc_size, AR_BSIZE); byteptr = sc->sc_bufptr; for (i=0; iardata; /* read data */ for (; iardata; /* read junk */ #ifdef lint x = x; /* use it */ #endif araddr->arburst = 0; sc->sc_oldstate = sc->sc_state; sc->sc_state = READfin; /* Like FINstate sorta */ break; case WRinit: araddr->ardata = ARCMD_WRDATA; araddr->arreq = 1; goto next; case WRburst: /* Write a block of data to the tape drive. */ Dprintf("ar*WRITE addr %x count %x\n", sc->sc_bufptr, sc->sc_size); while (!araddr->arrdy) { if (araddr->arexc) return (0); /* Not done yet */ Dprintf("ar*Write no READY\n"); } araddr->arburst = 1; /* Begin block xfer */ count = AR_BSIZE; byteptr = sc->sc_bufptr; while (count--) araddr->ardata = *byteptr++; araddr->arburst = 0; sc->sc_oldstate = sc->sc_state; sc->sc_state = WRfin; /* Like FINstate sorta */ Dprintf("ar*machine exiting done in state %x\n", sc->sc_state); /* * This code is a copy of the code at the end of this * switch statement, except that it returns 1 (operation * completed) instead of 0 (more needs doing) */ araddr->arrdyie = 1; araddr->arexcie = 1; return (1); case CMDstate: /* All commands that stop interacting once you say "do it" */ araddr->arreq = 0; /* Done with command */ goto FinState; /* Final interaction for this cmd */ IdleState: /* Drive is idle; set IDLEstate and disable */ sc->sc_oldstate = sc->sc_state; sc->sc_state = IDLEstate; goto DisAble; case WRfin: /* Entry after writing a block */ case READfin: /* Entry after reading a block */ case FINstate: /* Entry after any other command */ /* * Go to next sequential state - WRidle, READidle, IDLEstate. * Disable interrupts, and return. arstart_cmd() will later * put us into READ/WRburst or some commandinit state. */ sc->sc_oldstate = sc->sc_state; sc->sc_state = (enum ARstates)(1 +(int)sc->sc_state); DisAble: Dprintf("ar*machine idling\n"); araddr->arrdyie = 0; /* Negate arrdy interrupt */ return (1); /* Tell caller op is done */ case WRidle: /* Writing blocks, but none to write now */ case READidle: /* Reading blocks, but don't need one now */ case IDLEstate: /* Issuing commands, but don't have one now */ /* This can only happen if software triggers us. */ printf("ar: triggerred at idle %x\n", sc->sc_state); goto DisAble; /* Turn off interrupt enable again */ default: printf("ar: invalid state %d\n", sc->sc_state); goto FinState; /* Is this reasonable? */ next: /* Go to next sequential state */ sc->sc_oldstate = sc->sc_state; sc->sc_state = (enum ARstates)(1 +(int)sc->sc_state); break; FinState: sc->sc_oldstate = sc->sc_state; sc->sc_state = FINstate; break; CmdState: sc->sc_oldstate = sc->sc_state; sc->sc_state = CMDstate; break; } Dprintf("ar*machine exiting in state %x\n", sc->sc_state); /* Go to next state on the next leading edge of arrdy. */ araddr->arrdyie = 1; /* Interrupt on arrdy leading edge */ araddr->arexcie = 1; /* Interrupt on arexc too */ /* FIXME. Figure out where to set and unset, leave alone otherwise. */ return (0); }