#define TMSDEBUG 1 /* @(#)tmscp.c 1.12 (2.11BSD) 1999/2/25 */ #if !defined(lint) && defined(DOSCCS) static char *sccsid = "@(#)tmscp.c 1.24 (ULTRIX) 1/21/86"; #endif /************************************************************************ * Licensed from Digital Equipment Corporation * * Copyright (c) * * Digital Equipment Corporation * * Maynard, Massachusetts * * 1985, 1986 * * All rights reserved. * * * * The Information in this software is subject to change * * without notice and should not be construed as a commitment * * by Digital Equipment Corporation. Digital makes no * * representations about the suitability of this software for * * any purpose. It is supplied "As Is" without expressed or * * implied warranty. * * * * If the Regents of the University of California or its * * licensees modify the software in a manner creating * * diriviative copyright rights, appropriate copyright * * legends may be placed on the drivative work in addition * * to that set forth above. * ************************************************************************ * * tmscp.c - TMSCP (TK50/TU81) tape device driver * * Modification History: * 25-Feb-99 - sms * Fix density selection to preserve the high byte of m_format. This * is required devices (such as the TK70) which have a nonzero value * in this field. * * 22-Feb-99 - sms * Add timeout logic to tmscpcommand to catch hardware going catatonic. * When tmscpcommand() was first created the only use was to issue a * nonblocking rewind upon close. Tmscpcommand is now called for many * other functions some of which can leave a process (and the drive) hung * unless a timeout is done. * * Remove special treatment of OFFLINE and AVLBL status codes in * tms_iodone(). The code is suspected of having been a bug even in * its old location but is definitely causing problems now - if a read * completes with a 'AVLBL' status the drive is hung because an iodone() * is never performed. * * Minor cleanup done thruout (reduce D space consumption ,etc). * * 24-Apr-98 - sms * An incorrect pointer was being passed to tms_iodone() from tmscprsp() * when dealing with the 'invalid endcode' case. While that value should * never be generated by normal hardware the kernel should not panic. * * 07-Mar-98 - sms * Fix a bug that caused EOM to be 'sticky'. Once EndOfMedia was detected * the only way to clear it was to unload and reload the tape. Remove * unused flag definitions. Only use a single 'written' flag instead of * two (one in tms_flags and another in Tflags). * * 01-Feb-98 - sms * Initially the thought was the driver was broken sometime around June * 1996. After adding additional logging and the 'sysctl' interface it * was discovered (eventually after several long nights) that both my * TK50 and another person's TU81+ had developed hardware problems. The * better logging and sysctl changes were retained for future use ;) * * Failure to 'online' a drive was changed to return EIO instead of ENXIO * because 'device not configured' means the drive is not present. * * A 'sysctl' interface was added so that 'tmscpprintf' could be changed * without rebooting/recompiling the system. Also sysctl can be used to * set the cache on/off status even if a tape is not at the load point - * the setting takes effect on the next open(). * * 14-May-96 - sms * Missing parens caused mtflush,mtcache,mtnocache to be always skipped. * The use cache bit was being cleared in tmscpopen(). The use cache bit * is sticky in that once set via MTCACHE it stays set until cleared by * MTNOCACHE. * * 14-Dec-95 - sms@wlv... * Success! But the size of the driver is even more of a problem now. * If the crash dump option is defined the driver could exceed the max * size permitted for an overlay. The 'tmsdump' routine was moved to * a separate file. This driver almost always must be in an overlay * by itself now. * * 08-Dec-95 - sms@wlv... * Begin serious surgery on this driver. The goals are: 1) to correctly * handle TU81/81+ drives (do not set density unless at BOT), 2) support * use of the cache on drives which implement it (TU81+), 3) improve * error detection and reporting, 4) improve readability by replacing * multipage 'switch' statements with calls to functions, 5) make it * easier to add new capabilities and fix problems without wholescale * changes again. * * 30-Nov-95 - sms@wlv... * Experiment to see if M_MD_IMMED being set for the rewind on close * obviates the need for skipping the iowait(). Success - only 2 ticks * elapse doing the iowait!. * * 02-Jan-93 - sms@wlv. [2.11BSD] * Remove unibus map ifdefs and rely on run time testing of 'ubmap' which * does the right thing and makes kernels easier to move between machines. * * 20-Nov-92 - sms@wlv.iipo.gtegsc.com [2.11BSD] * Add tmsVec() for autoconfig to use in passing the vector from /etc/dtab * to this driver. The previous scheme of having a fixed vector with * all TMSCP vectors contiguous has been removed. * * 23-May-91 - sms@wlv.iipo.gtegsc.com [2.11BSD] * Minor typo in the multicontroller support fixed. Major overhaul * of the density selection, apparently some TMSCP controllers * do not treat 0 in the mscp_format word as meaning the highest * density, instead leaving the drive in the current/last-used * density. * * 29-Mar-91 - sms@wlv.iipo.gtegsc.com [2.11BSD] * Major changes to 1) support multiple drives per controller * (the maximum number of drives per controller is 4 for now in order * to maximize compatibility with existing minor device numbers - the * norewind and density bit stay in the same place) and 2) more * importantly reduce the bloat of this driver. The command * packet area is now allocated from an external heap setup at boot * time (the MSCP, ra.c driver also does this). Allocating from an * external arena save approximately 2kb of kernel D space PER CONTROLLER * and costs little in speed because the amount of remapping involved is * quite small. * * The 'errinfo' routine was removed in the interest of space savings, * the error code was already being printed out in hex and it's not * worth eating up another 250 or so bytes of kernel D space to pretty * print messages for which tmscp.h provides the cross reference (I space * is also saved by not printing the messages). Besides, the ra.c (MSCP) * driver doesn't do it and it is worth a degree of non 4.3BSD verisimility * to save a significant amount of space. * * The tms_type field should have been a 'long' (mediatype). Since the * drives are not probed at autoconfigure time a GTUNT (TMS_SENSE) command * is done at open() time to fetch the format/density menu and unit flags. * iodone() proccessing was missing for the GTUNT function in tmscprsp() * causing hangs in both open and ioctl functions. * * Multiple controller support made to work, the top 2 bits of the minor * device number are used to designate the controller, thus there is * a maximum of 4 TMSCP controllers per system. * * 17-Jun-90,14Aug90 - sms@wlv.iipo.gtegsc.com * Began porting to 2.10.1BSD/2.11BSD. Multiple drives per controller * NOT supported, although multiple controllers are (maybe). Programmable * vectors don't work very well with the autoconfigure scheme in use. * the define TMSCP_VEC will have to be adjusted in tms.h (see * conf/config and the sample config files). For patching purposes * the global "TMSvec" is declared and initialized with TMSCP_VEC. * * 06-Jan-86 - afd * Changed the probe routine to use DELAY (not TODR). This now * works for MicroVAXen as well. This eliminates the busy-wait * for MicroVAXen so a dead TK50 controller will not hang autoconf. * * 06-Dec-85 - afd * Fixed a bug in density selection. The "set unit characteristics" * command to select density, was clearing the "unit flags" field * where the CACHE bit was for TU81-E. Now the unit's "format" and * "unitflgs" are saved in tms_info struct. And are used on STUNT * commands. * * 19-Oct-85 - afd * Added support to the open routine to allow drives to be opened * for low density (800 or 1600 bpi) use. When the slave routine * initiates a "get-unit-char" cmd, the format menu for the unit * is saved in the tms_info structure. The format menu is used in the * start routine to select the proper low density. * * 02-Oct-85 - afd * When a tmscp-type controller is initializing, it is possible for * the sa reg to become 0 between states. Thus the init code in * the interrupt routine had to be modified to reflect this. * * 21-Sep-85 - afd * The TK50 declares a serious exception when a tape mark is encountered. * This causes problems to dd (& other UN*X utilities). So a flag * is set in the rsp() routine when a tape mark is encountered. If * this flag is set, the start() routine appends the Clear Serious * Exception modifier to the next command. * * 03-Sep-85 -- jaw * messed up previous edit.. * * 29-Aug-85 - jaw * fixed bugs in 8200 and 750 buffered datapath handling. * * 06-Aug-85 - afd * 1. When repositioning records or files, the count of items skipped * does NOT HAVE to be returned by controllers (& the TU81 doesn't). * So tmscprsp() had to be modified to stop reporting * residual count errors on reposition commands. * * 2. Fixed bug in the open routine which allowed multiple opens. * * 18-Jul-85 - afd * 1. Need to return status when mt status (or corresponding ioctl) is done. * Save resid, flags, endcode & status in tmscprsp() routine (except on * clear serious exception no-op). Return these fields when status * ioctl is done (in tmscpcommand()). How they are returned: * mt_resid = resid * mt_dsreg = flags|endcode * mt_erreg = status * * 2. Added latent support for enabling/disabling caching. This is * handled along with all other ioctl commands. * * 3. Need to issue a no-op on unrecognized ioctl in tmsstart(), since * we have already commited to issuing a command at that point. * * 4. In tmscprsp() routine if encode is 0200 (invalid command issued); * We need to: Unlink the buffer from the I/O wait queue, * and signal iodone, so the higher level command can exit! * Just as if it were a valid command. * * 11-jul-85 -- jaw * fix bua/bda map registers. * * 19-Jun-85 -- jaw * VAX8200 name change. * * 06-Jun-85 - jaw * fixes for 8200. * * 9-Apr-85 - afd * Added timeout code to the probe routine, so if the controller * fails to init in 10 seconds we return failed status. * * 13-Mar-85 -jaw * Changes for support of the VAX8200 were merged in. * * 27-Feb-85 -tresvik * Changes for support of the VAX8600 were merged in. * */ #include "tms.h" #if NTMSCP > 0 #include "param.h" #include "systm.h" #include "buf.h" #include "conf.h" #include "user.h" #include "file.h" #include "map.h" #include "ioctl.h" #include "syslog.h" #include "mtio.h" #include "uio.h" #include "tty.h" #include "uba.h" #include "kernel.h" #include "tmscpreg.h" #include "../pdp/tmscp.h" #include "../machine/seg.h" /* * The density array is indexed by the density bits (bits 3 and 4) of the * minor device number AND the format menu returned for the drive. Since * only 3 densities are apparently supported by TMSCP (no DPE/3200bpi), the * second MEDium table is a copy of the first MEDium entry. */ char Dmatrix[4][8] = { /* LOW */ {0,M_TF_800,M_TF_PE,M_TF_800,M_TF_GCR,M_TF_800,M_TF_PE,M_TF_800}, /* MED1 */ {0,M_TF_800,M_TF_PE,M_TF_800,M_TF_GCR,M_TF_800,M_TF_PE,M_TF_PE }, /* MED2 */ {0,M_TF_800,M_TF_PE,M_TF_800,M_TF_GCR,M_TF_800,M_TF_PE,M_TF_PE }, /* HI */ {0,M_TF_800,M_TF_PE,M_TF_PE,M_TF_GCR,M_TF_GCR,M_TF_GCR,M_TF_GCR} }; struct tmscp_softc tmscp_softc[NTMSCP]; /* Controller info */ struct tms_info tms_info[NTMS]; /* Drive info */ memaddr tmscp[NTMSCP]; /* click addresses of ctrl comm area */ /* * Tflags definitions. These take the place of several * individual structure members in tms_info. */ #define _SEREX 0x0001 /* Serious Exception exists */ #define _CLSEREX 0x0002 /* Do Clear Serious Exception */ #define _LOST 0x0004 /* Position lost error happened */ #define _BUFMARK 0x0008 /* Encountered a tape mark */ #define _HASCACHE 0x0010 /* Drive has cache capability */ #define _CACHE_ON 0x0020 /* Cache enabled */ #define _CACHE_LOST 0x0040 /* Cache data loss has happened */ #define _CACHE_WRITTEN 0x0080 /* Cache has been written */ #define _INUSE 0x0100 /* Drive is in use */ #define _ONLINE 0x0200 /* Drive is online */ /* * Internal (ioctl) command codes (these must also be declared in the * tmscpioctl routine). These correspond to ioctls in mtio.h */ #define TMS_WRITM 0 /* write tape mark */ #define TMS_FSF 1 /* forward space file */ #define TMS_BSF 2 /* backward space file */ #define TMS_FSR 3 /* forward space record */ #define TMS_BSR 4 /* backward space record */ #define TMS_REW 5 /* rewind tape */ #define TMS_OFFL 6 /* rewind tape & mark unit offline */ #define TMS_SENSE 7 /* noop - do a get unit status */ #define TMS_CACHE 8 /* enable cache */ #define TMS_NOCACHE 9 /* disable cache */ #define TMS_FLUSH 10 /* flush cache */ /* These go last: after all real mt cmds, just bump the numbers up */ #define TMS_CSE 11 /* clear serious exception */ #define TMS_SETDENSITY 12 /* set unit density */ /* * Controller states */ #define S_IDLE 0 /* hasn't been initialized */ #define S_STEP1 1 /* doing step 1 init */ #define S_STEP2 2 /* doing step 2 init */ #define S_STEP3 3 /* doing step 3 init */ #define S_SCHAR 4 /* doing "set controller characteristics" */ #define S_RUN 5 /* running */ static char *tmscpstepfailed = "step%d init failed: sa %x\n"; char *tmscpfatalerr = "tms%d,%d: fatal error %x\n"; int tmscp_cp_wait = 0; /* Something to wait on for command */ /* packets and or credits. */ int wakeup(); int tmswatchdog(); extern int hz; /* Should find the right include */ extern long _iomap(); extern u_int tmscp_cache; /* See pdp/kern_pdp.c */ /* * Bit 0 = print all non-successful response packets _except_ hitting a * tapemark (which really isn't an error). * Bit 1 = print datagram arrival message. * Bit 2 = print status of all response packets except datagrams. * Bit 3 = enable debugging print and log statements not covered above */ int tmscpprintf = 1; /* * This is settable via "sysctl -w machdep.tmscp.cache=0xXXXX". There is one * bit per drive. Bit 0 is the first drive on the first controller, bit 4 is * the first drive on the second controller, and so on. * */ int tmscpcache = 0; struct mscp *tmscpgetcp(); #define b_qsize b_resid /* queue size per drive, in tmsutab */ tmsVec(ctlr, vector) int ctlr, vector; { register struct tmscp_softc *sc = &tmscp_softc[ctlr]; if (ctlr >= NTMSCP || sc->sc_ivec) return(-1); sc->sc_ivec = vector; return(0); } /* * Open routine will issue the online command, later. * * This routine attaches controllers, not drives - sc_unit and 'unit' are * the controller number not a drive unit number. sc_com is initialized * to SEG5 because all communication areas are mapped to the same virtual * address now. */ tmsattach(addr, unit) struct tmscpdevice *addr; register int unit; { register struct tmscp_softc *sc = &tmscp_softc[unit]; if (unit >= NTMSCP) return(0); if (sc->sc_addr == NULL && addr != NULL) { tmscp[unit] = (memaddr)_ioget(sizeof (struct tmscp)); if (tmscp[unit] == NULL) return(0); sc->sc_addr = addr; sc->sc_unit = unit; sc->sc_com = (struct tmscp *)SEG5; return (1); } return(0); } struct tms_info * getdd() { register struct tms_info *p; for (p = tms_info; p < &tms_info[NTMS]; p++) { if (p->tms_type == 0) { /* * Set the type with a placeholder value - we may have to sleep waiting for * the drive to come on line and we don't want this slot to be grabbed again. * The online response will load the real drive type. */ p->tms_type = 1; /* XXX */ return(p); } } log(LOG_INFO, "tms: !drives\n"); return(NULL); } static int wait_step(mask, good, csr, sc) u_short mask, good; register struct tmscpdevice *csr; register struct tmscp_softc *sc; { register int i; for (i = 0; i < 150; i++) { if ((csr->tmscpsa & mask) == good) return(0); delay(10000L); /* still in step - wait 1/100 sec */ } sc->sc_state = S_IDLE; sc->sc_ctab.b_active = 0; log(LOG_INFO, tmscpstepfailed, sc->sc_state, csr->tmscpsa); wakeup((caddr_t)&sc->sc_ctab); return(-1); } /* * TMSCP interrupt routine. */ tmsintr(dev) dev_t dev; { register struct tmscpdevice *tmscpaddr; struct buf *bp; int i; register struct tmscp_softc *sc = &tmscp_softc[dev]; register struct tmscp *tm = sc->sc_com; struct mscp *mp; segm seg5; tmscpaddr = sc->sc_addr; /* * How the interrupt is handled depends on the state of the controller. */ switch (sc->sc_state) { case S_IDLE: log(LOG_INFO, "tms%d: rand intr\n", dev); return; /* Controller was in step 1 last, see if its gone to step 2 */ case S_STEP1: # define STEP1MASK 0174377 # define STEP1GOOD (TMSCP_STEP2|TMSCP_IE|(NCMDL2<<3)|NRSPL2) if (wait_step(STEP1MASK, STEP1GOOD, tmscpaddr, sc) < 0) return; tmscpaddr->tmscpsa = (short)sc->sc_ctab.b_un.b_addr; sc->sc_state = S_STEP2; return; /* Controller was in step 2 last, see if its gone to step 3 */ case S_STEP2: # define STEP2MASK 0174377 # define STEP2GOOD (TMSCP_STEP3|TMSCP_IE|(sc->sc_ivec/4)) if (wait_step(STEP2MASK, STEP2GOOD, tmscpaddr, sc) < 0) return; tmscpaddr->tmscpsa = sc->sc_ctab.b_xmem; sc->sc_state = S_STEP3; return; /* Controller was in step 3 last, see if its gone to step 4 */ case S_STEP3: # define STEP3MASK 0174000 # define STEP3GOOD TMSCP_STEP4 if (wait_step(STEP3MASK, STEP3GOOD, tmscpaddr, sc) < 0) return; /* * Get microcode version and model number of controller; * Signal initialization complete (_GO) (to the controller); * Set state to "set controller characteristics". */ i = tmscpaddr->tmscpsa; tmscpaddr->tmscpsa = TMSCP_GO | TMSCP_LF; sc->sc_state = S_SCHAR; log(LOG_INFO, "tms%d Ver %d Mod %d\n", dev, i & 0xf, (i >> 4) & 0xf); /* * Initialize the data structures (response and command queues). */ saveseg5(seg5); mapseg5(tmscp[sc->sc_unit], MAPBUFDESC); tmsginit(sc, sc->sc_com->tmscp_ca.ca_rspdsc, sc->sc_com->tmscp_rsp, 0, NRSP, TMSCP_OWN|TMSCP_INT); tmsginit(sc, sc->sc_com->tmscp_ca.ca_cmddsc, sc->sc_com->tmscp_cmd, NRSP, NCMD, TMSCP_INT); bp = &sc->sc_wtab; bp->av_forw = bp->av_back = bp; sc->sc_lastcmd = 1; sc->sc_lastrsp = 0; mp = sc->sc_com->tmscp_cmd; mp->mscp_unit = mp->mscp_modifier = 0; mp->mscp_flags = 0; mp->mscp_version = 0; mp->mscp_cntflgs = M_CF_ATTN|M_CF_MISC|M_CF_THIS; /* * A host time out value of 0 means that the controller will not * time out. This is ok for the TK50. */ mp->mscp_hsttmo = 0; bzero(&mp->mscp_time, sizeof (mp->mscp_time)); mp->mscp_cntdep = 0; mp->mscp_opcode = M_OP_STCON; ((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN|TMSCP_INT); i = tmscpaddr->tmscpip; /* initiate polling */ restorseg5(seg5); return; case S_SCHAR: case S_RUN: break; default: log(LOG_INFO, "tms%d: ST %d\n", dev, sc->sc_state); return; } /* end switch */ /* * The controller state is S_SCHAR or S_RUN */ /* * If the error bit is set in the SA register then print an error * message and reinitialize the controller. */ if (tmscpaddr->tmscpsa&TMSCP_ERR) { log(LOG_INFO, tmscpfatalerr, dev, sc->sc_unit, tmscpaddr->tmscpsa); tmscpaddr->tmscpip = 0; sc->sc_state = S_IDLE; sc->sc_ctab.b_active = 0; wakeup((caddr_t)&sc->sc_ctab); } /* * Check for a buffer purge request. (Won't happen w/ TK50 on Q22 bus) */ saveseg5(seg5); mapseg5(tmscp[sc->sc_unit], MAPBUFDESC); if (tm->tmscp_ca.ca_bdp) { tm->tmscp_ca.ca_bdp = 0; tmscpaddr->tmscpsa = 0; /* signal purge complete */ } /* * Check for response ring transition. */ if (tm->tmscp_ca.ca_rspint) { tm->tmscp_ca.ca_rspint = 0; for (i = sc->sc_lastrsp;; i++) { i %= NRSP; if (tm->tmscp_ca.ca_rspdsc[i].hsh&TMSCP_OWN) break; tmscprsp(sc, i); tm->tmscp_ca.ca_rspdsc[i].hsh |= TMSCP_OWN; } sc->sc_lastrsp = i; } /* * Check for command ring transition. (Should never happen!) */ if (tm->tmscp_ca.ca_cmdint) tm->tmscp_ca.ca_cmdint = 0; restorseg5(seg5); if (tmscp_cp_wait) wakeup((caddr_t)&tmscp_cp_wait); (void) tmsstart(sc); } /* * Open a tmscp device and set the unit online. If the controller is not * in the run state, call init to initialize the tmscp controller first. */ /* ARGSUSED */ tmscpopen(dev, flag) dev_t dev; int flag; { register int unit = TMSUNIT(dev); int ctlr = TMSCTLR(dev); register struct tmscp_softc *sc; register struct tms_info *tms; register struct mscp *mp; struct tmscpdevice *tmscpaddr; u_short cmdref; int s,i; if (ctlr >= NTMSCP) return (ENXIO); sc = &tmscp_softc[ctlr]; if (sc->sc_addr == NULL) return (ENXIO); if ((tms = sc->sc_drives[unit]) == NULL) { tms = getdd(); if (!tms) return(ENXIO); tms->Tflags = 0; sc->sc_drives[unit] = tms; } else if (tms->Tflags & _INUSE) return(EBUSY); tms->Tflags |= _INUSE; s = spl5(); if (sc->sc_state != S_RUN) { if (sc->sc_state == S_IDLE) if (!tkini(sc)) { log(LOG_INFO, "tms%d init\n", ctlr); sc->sc_drives[unit] = NULL; tms->Tflags = 0; tms->tms_type = 0; (void) splx(s); return(ENXIO); } /* * Wait for initialization to complete */ timeout(wakeup,(caddr_t)&sc->sc_ctab,11*hz); /* to be sure*/ sleep((caddr_t)&sc->sc_ctab, PSWP+1); if (sc->sc_state != S_RUN) { sc->sc_drives[unit] = NULL; tms->Tflags = 0; tms->tms_type = 0; (void) splx(s); return(EIO); } } (void) splx(s); /* * If drive is not online get a command packet, sleeping if no * controller credits are available at the moment. Then start * the ONLINE process. */ tmscpaddr = (struct tmscpdevice *) sc->sc_addr; if (!(tms->Tflags & _ONLINE)) { s = spl5(); while ((mp = tmscpgetcp(sc)) == 0) { tmscp_cp_wait++; sleep((caddr_t)&tmscp_cp_wait,PSWP+1); tmscp_cp_wait--; } (void) splx(s); mapseg5(tmscp[sc->sc_unit], MAPBUFDESC); mp->mscp_opcode = M_OP_ONLIN; mp->mscp_unit = unit; /* unit? */ tms_clrerr(tms, mp); /* calculate this once instead of 4 times */ cmdref = (u_short)&tms->tms_type; mp->mscp_cmdref = cmdref; /* need to sleep on something */ ((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN | TMSCP_INT); normalseg5(); i = tmscpaddr->tmscpip; /* * To make sure we wake up, timeout in 240 seconds. * Wakeup in tmscprsp routine. * 240 seconds (4 minutes) is necessary since a rewind * can take a few minutes. */ timeout(wakeup,(caddr_t) cmdref,240 * hz); sleep((caddr_t)cmdref,PSWP+1); untimeout(wakeup, cmdref); } if (!(tms->Tflags & _ONLINE)) { oops: tms->Tflags = 0; tms->tms_type = 0; sc->sc_drives[unit] = NULL; return(EIO); /* Didn't go online */ } /* * Get the unit characteristics (GTUNT). This 1) Verifies the drive * is really still online and 2) Retrieves information about the drive * such as density choices, cache presence, etc. */ tms->tms_flags = 0; if (tmscpcache & ((1 << unit) << (4 * ctlr))) tms->Tflags |= _CACHE_ON; i = tms->Tflags & _CACHE_ON; tms->Tflags = _ONLINE | _INUSE | i; /* Clear all other flags */ tmscpcommand(dev, TMS_SENSE, 1); if (!(tms->Tflags & _ONLINE)) goto oops; /* * Next issue a reposition NOP by spacing forward 0 records. This returns * the current position (so we can tell if we're at BOT or not) and also * clears any serious exceptions outstanding. */ tmscpcommand(dev, TMS_CSE, 1); /* * Now go set the density - the lower level routines set the density if * the drive is at BOT. */ tmscpcommand(dev, TMS_SETDENSITY, 1); return(0); } /* * 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. */ tmscpclose(dev, flag) register dev_t dev; register flag; { struct tmscp_softc *sc; register struct tms_info *tms; int unit = TMSUNIT(dev); sc = &tmscp_softc[TMSCTLR(dev)]; tms = sc->sc_drives[unit]; if ((tms->Tflags & _CACHE_ON) && (tms->Tflags & _CACHE_WRITTEN)) { /* * A misbehaving application has closed the device without flushing * the enabled cache by issuing a MTFLUSH - we'll do it for it. */ tmscpcommand(dev, TMS_FLUSH, 1); if (tms->tms_status != M_ST_SUCC) { /* * This is BAD news - the flush didn't work. The drive is in a serious * exception state, leave that alone for the non-rewind case so that further * operations fail. About all that can be done now is log the error. */ log(LOG_INFO, "tms%d,%d flsh\n", TMSCTLR(dev), unit); tms->Tflags &= ~_CACHE_WRITTEN; } } if (tms->tms_flags & MTF_WRITTEN) tms_wrteof(dev, tms); tms->Tflags &= ~(_BUFMARK | _INUSE); if ((dev & T_NOREWIND) == 0) { tmscpcommand(dev, TMS_REW, 0); tms->Tflags &= ~(_LOST | _CLSEREX | _SEREX); } return(0); } /* * Execute a command on the tape drive a specified number of times. * This routine sets up a buffer and calls the strategy routine which * links the buffer onto the drive's buffer queue. * The start routine will take care of creating a tmscp command packet * with the command. The start routine is called by the strategy or the * interrupt routine. */ tmscpcommand(dev, com, count) register dev_t dev; int com, count; { register struct buf *bp; register int s; int unit = TMSUNIT(dev); bp = &tmscp_softc[TMSCTLR(dev)].sc_cmdbuf; s = spl5(); while (bp->b_flags&B_BUSY) { bp->b_flags |= B_WANTED; sleep((caddr_t)bp, PRIBIO); } bp->b_flags = B_BUSY|B_READ; splx(s); /* * Load the buffer. The b_count field gets used to hold the command * count. the b_resid field gets used to hold the command mneumonic. * These 2 fields are "known" to be "safe" to use for this purpose. * (Most other drivers also use these fields in this way.) */ bp->b_dev = dev; bp->b_bcount = count; bp->b_resid = com; bp->b_blkno = 0; /* * Start the timer before entering the strategy routine. If it declares * an immediate error it will also perform an iodone which will cause us * to fall thru and cancel the timer. */ timeout(tmswatchdog, bp, 240 * hz); tmscpstrategy(bp); iowait(bp); untimeout(tmswatchdog, bp); if (bp->b_flags & B_WANTED) /* Anyone waiting above? */ wakeup(bp); bp->b_flags &= B_ERROR; /* Clears B_BUSY */ } /* * If this routine is called then (after 4 minutes) something is hung. * Set the I/O done flag, set error to be ETIMEDOUT, and issue the wakeup. */ tmswatchdog(bp) struct buf *bp; { bp->b_error = ETIMEDOUT; biodone(bp); } /* * Init mscp communications area */ tmsginit(sc, com, msgs, offset, length, flags) register struct tmscp_softc *sc; register Trl *com; register struct mscp *msgs; int offset; int length; int flags; { long vaddr; /* * Figure out virtual address of message * skip comm area and mscp messages header and previous messages * * N.B. Assumes SEG5 has been remapped to the comm area for this * controller. */ vaddr = _iomap(tmscp[sc->sc_unit]); vaddr += sizeof(struct tmscpca) /* skip comm area */ +sizeof(struct mscp_header); /* m_cmdref disp */ vaddr += offset * sizeof(struct mscp); /* skip previous */ while (length--) { com->lsh = loint(vaddr); com->hsh = flags | hiint(vaddr); msgs->mscp_dscptr = (long *)com; msgs->mscp_header.mscp_msglen = sizeof(struct mscp); msgs->mscp_header.mscp_vcid = 1; /* tape VCID = 1 */ ++com; ++msgs; vaddr += sizeof(struct mscp); } } /* * Find an unused command packet */ struct mscp * tmscpgetcp(sc) register struct tmscp_softc *sc; { register struct mscp *mp = NULL; register struct tmscpca *cp; int i; int s; segm seg5; s = spl5(); saveseg5(seg5); mapseg5(tmscp[sc->sc_unit], MAPBUFDESC); cp = &sc->sc_com->tmscp_ca; /* * If no credits, can't issue any commands * until some outstanding commands complete. */ i = sc->sc_lastcmd; if (((cp->ca_cmddsc[i].hsh&(TMSCP_OWN|TMSCP_INT))==TMSCP_INT) && (sc->sc_credits >= 2)) { sc->sc_credits--; /* This commits to issuing a command */ cp->ca_cmddsc[i].hsh &= ~TMSCP_INT; mp = &sc->sc_com->tmscp_cmd[i]; mp->mscp_cmdref = 0; mp->mscp_modifier = 0; mp->mscp_flags = 0; bzero(&mp->un, sizeof (mp->un)); sc->sc_lastcmd = (i + 1) % NCMD; } restorseg5(seg5); (void) splx(s); return(mp); } /* * Initialize a TMSCP device. Set up UBA mapping registers, * initialize data structures, and start hardware * initialization sequence. */ tkini(sc) register struct tmscp_softc *sc; { register struct tmscpdevice *tmscpaddr; long adr; sc->sc_ctab.b_active++; adr = _iomap(tmscp[sc->sc_unit]) + (u_int)RINGBASE; sc->sc_ctab.b_un.b_addr = (caddr_t)loint(adr); sc->sc_ctab.b_xmem = hiint(adr); tmscpaddr = sc->sc_addr; /* * Start the hardware initialization sequence. */ tmscpaddr->tmscpip = 0; /* start initialization */ while((tmscpaddr->tmscpsa & TMSCP_STEP1) == 0) { if (tmscpaddr->tmscpsa & TMSCP_ERR) return(0); /* CHECK */ } tmscpaddr->tmscpsa=TMSCP_ERR|(NCMDL2<<11)|(NRSPL2<<8)|TMSCP_IE|(sc->sc_ivec/4); /* * Initialization continues in the interrupt routine. */ sc->sc_state = S_STEP1; sc->sc_credits = 0; return(1); } /* * Start I/O operation * This code is convoluted. The majority of it was copied from the uda driver. */ tmsstart(sc) register struct tmscp_softc *sc; { register struct mscp *mp; register struct buf *bp, *dp; register struct tms_info *tms; struct tmscpdevice *tmscpaddr; int i, unit; segm seg5; saveseg5(seg5); /* save just once at top */ for(;;) { if ((dp = sc->sc_ctab.b_actf) == NULL) { /* (drive was inactive) */ sc->sc_ctab.b_active = 0; break; } if ((bp = dp->b_actf) == NULL) { /* * No more requests for this drive, remove * from controller queue and look at next drive. * We know we're at the head of the controller queue. */ dp->b_active = 0; sc->sc_ctab.b_actf = dp->b_forw; continue; } sc->sc_ctab.b_active++; unit = TMSUNIT(bp->b_dev); tmscpaddr = (struct tmscpdevice *)sc->sc_addr; tms = sc->sc_drives[unit]; if ((tmscpaddr->tmscpsa&TMSCP_ERR) || sc->sc_state != S_RUN) { log(LOG_INFO, "tms%d,%d: sa %x st %d\n", sc->sc_unit, unit, tmscpaddr->tmscpsa, sc->sc_state); (void)tkini(sc); /* SHOULD REQUEUE OUTSTANDING REQUESTS, LIKE TMSCPRESET */ break; } if (!(tms->Tflags & _ONLINE)) { if ((mp = tmscpgetcp(sc)) == NULL) break; mapseg5(tmscp[sc->sc_unit], MAPBUFDESC); mp->mscp_opcode = M_OP_ONLIN; mp->mscp_unit = unit; tms_clrerr(tms, mp); dp->b_active = 2; sc->sc_ctab.b_actf = dp->b_forw; /* remove from controller q */ ((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN|TMSCP_INT); if (tmscpaddr->tmscpsa&TMSCP_ERR) log(LOG_INFO, tmscpfatalerr, sc->sc_unit, TMSUNIT(bp->b_dev), tmscpaddr->tmscpsa); restorseg5(seg5); i = tmscpaddr->tmscpip; continue; } if ((mp = tmscpgetcp(sc)) == NULL) break; mapseg5(tmscp[sc->sc_unit], MAPBUFDESC); mp->mscp_cmdref = (u_short)bp; /* pointer to get back */ mp->mscp_unit = unit; /* * If its an ioctl-type command then set up the appropriate * tmscp command; by doing a switch on the "b_resid" field where * the command mneumonic is stored. */ if (bp == &sc->sc_cmdbuf) { /* * The reccnt and tmkcnt fields are set to zero by the getcp * routine (as bytecnt and buffer fields). Thus reccnt and * tmkcnt are only modified here if they need to be set to * a non-zero value. */ switch ((int)bp->b_resid) { case TMS_WRITM: tms_wtm_st(mp, sc, 0); break; case TMS_FSF: tms_repos_st(mp, sc, 0); break; case TMS_BSF: tms_repos_st(mp, sc, M_MD_REVRS); break; case TMS_FSR: tms_reposrec_st(mp, sc, M_MD_OBJCT); break; case TMS_BSR: tms_reposrec_st(mp, sc, M_MD_OBJCT | M_MD_REVRS); break; case TMS_REW: if (bp->b_bcount) i = M_MD_REWND; else i = M_MD_REWND | M_MD_IMMED; bp->b_bcount = 0; /* XXX */ tms_repos_st(mp, sc, i); break; case TMS_OFFL: tms_avail_st(mp, sc, M_MD_UNLOD); break; case TMS_SENSE: mp->mscp_opcode = M_OP_GTUNT; break; case TMS_FLUSH: mp->mscp_opcode = M_OP_FLUSH; break; case TMS_CACHE: tms->tms_unitflgs |= M_UF_WBKNV; tms->tms_unitflgs &= ~M_UF_SCCHH; tms->Tflags |= _CACHE_ON; tms_stunt_st(mp, sc, 0); break; case TMS_NOCACHE: tms->tms_unitflgs &= ~M_UF_WBKNV; tms->tms_unitflgs |= M_UF_SCCHH; tms->Tflags &= ~_CACHE_ON; tms_stunt_st(mp, sc, 0); break; case TMS_CSE: bp->b_bcount = 0; /* 0 objects to skip */ tms_repos_st(mp, sc, M_MD_OBJCT); break; case TMS_SETDENSITY: tms->tms_format &= ~M_TF_MASK; tms->tms_format |= Dmatrix[TMSDENS(bp->b_dev)][tms->tms_fmtmenu & FMTMASK]; tms_stunt_st(mp, sc, 0); break; default: log(LOG_INFO, "ioctl %x\n", bp->b_resid); /* Need a no-op. Reposition no amount */ mp->mscp_opcode = M_OP_REPOS; break; } /* end switch (bp->b_resid) */ } else /* Its a read/write command (not an ioctl) */ { mp->mscp_opcode = bp->b_flags&B_READ ? M_OP_READ : M_OP_WRITE; mp->mscp_bytecnt = bp->b_bcount; mp->mscp_buffer_l = (u_short) bp->b_un.b_addr; mp->mscp_buffer_h = bp->b_xmem; } if ((tms->Tflags & _CACHE_ON) && (mp->mscp_opcode == M_OP_WRITE)) tms->Tflags |= _CACHE_WRITTEN; if ((tms->Tflags & _BUFMARK) && (mp->mscp_opcode == M_OP_READ) && (tms->Tflags & _CLSEREX)) { tms->Tflags &= ~(_BUFMARK | _CLSEREX); mp->mscp_modifier |= M_MD_CLSEX; } if (tmscpprintf & 0x8) { log(LOG_INFO, "tms%d,%d -> op %x fl %x mod %x\n", sc->sc_unit, mp->mscp_unit, mp->mscp_opcode, mp->mscp_flags, mp->mscp_modifier); } ((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN|TMSCP_INT); i = tmscpaddr->tmscpip; /* initiate polling */ dp->b_qsize++; /* * Move drive to the end of the controller queue */ if (dp->b_forw != NULL) { sc->sc_ctab.b_actf = dp->b_forw; sc->sc_ctab.b_actl->b_forw = dp; sc->sc_ctab.b_actl = dp; dp->b_forw = NULL; } /* * Move buffer to I/O wait queue */ dp->b_actf = bp->av_forw; dp = &sc->sc_wtab; bp->av_forw = dp; bp->av_back = dp->av_back; dp->av_back->av_forw = bp; dp->av_back = bp; if (tmscpaddr->tmscpsa&TMSCP_ERR) { log(LOG_INFO, tmscpfatalerr,sc->sc_unit, mp->mscp_unit, tmscpaddr->tmscpsa); (void)tkini(sc); break; } } /* end for */ /* * Check for response ring transitions lost in the * Race condition. Map SEG5 in case we escaped early from the for(). */ mapseg5(tmscp[sc->sc_unit], MAPBUFDESC); for (i = sc->sc_lastrsp;; i++) { i %= NRSP; if (sc->sc_com->tmscp_ca.ca_rspdsc[i].hsh&TMSCP_OWN) break; tmscprsp(sc, i); sc->sc_com->tmscp_ca.ca_rspdsc[i].hsh |= TMSCP_OWN; } sc->sc_lastrsp = i; restorseg5(seg5); } /* * Process a response packet. N.B. Assumes SEG5 maps comm area for controller */ tmscprsp(sc, i) register struct tmscp_softc *sc; int i; { register struct mscp *mp; register struct tms_info *tms; struct buf *dp, *bp; int em_status, em_endcode; mp = &sc->sc_com->tmscp_rsp[i]; mp->mscp_header.mscp_msglen = sizeof (struct mscp); sc->sc_credits += mp->mscp_header.mscp_credits & 0xf; /* low 4 bits */ if ((mp->mscp_header.mscp_credits & 0xf0) > 0x10) /* Check */ return; /* * If it's an error log message (datagram), * pass it on for more extensive processing. */ if ((mp->mscp_header.mscp_credits & 0xf0) == 0x10) { tmserror(sc->sc_unit, (struct mslg *)mp); return; } if (tmscpprintf & 0x4) log(LOG_INFO, "tms%d,%d: op %x st %x\n", sc->sc_unit, mp->mscp_unit,mp->mscp_opcode, mp->mscp_status & M_ST_MASK); em_status = mp->mscp_status&M_ST_MASK; em_endcode = mp->mscp_endcode & ~M_OP_END; /* * The controller interrupts as any drive. * This means that you must check for controller interrupts * before drive responses. */ if (em_endcode == M_OP_STCON) { if (em_status == M_ST_SUCC) sc->sc_state = S_RUN; else sc->sc_state = S_IDLE; sc->sc_ctab.b_active = 0; wakeup((caddr_t)&sc->sc_ctab); return; } if (mp->mscp_unit >= 4) return; tms = sc->sc_drives[mp->mscp_unit]; if (!tms) /* unopened unit coming online - ignore it */ return; dp = &tms->tms_dtab; switch (em_endcode) { case M_OP_ONLIN: if (em_status == M_ST_SUCC || em_status == M_ST_WRTPR) { tms->Tflags |= _ONLINE; tms->Tflags &= ~_CACHE_WRITTEN; /* * Normally this is done in the 'tms_check_ret' routine. The ONLIN case is * special (well, ok - weird) in that it was never put on the I/O wait queue. * Thus we need to do this here. */ tms->tms_endcode = em_endcode; tms->tms_status = em_status; tms->tms_type = mp->mscp_mediaid; tms->tms_unitflgs = mp->mscp_unitflgs; tms->tms_format = mp->mscp_format; /* * Link the drive onto the controller queue */ dp->b_forw = NULL; if (sc->sc_ctab.b_actf == NULL) sc->sc_ctab.b_actf = dp; else sc->sc_ctab.b_actl->b_forw = dp; sc->sc_ctab.b_actl = dp; dp->b_active = 1; } else { while (bp = dp->b_actf) { dp->b_actf = bp->av_forw; bp->b_flags |= B_ERROR; iodone(bp); } } if (mp->mscp_cmdref) wakeup((caddr_t)mp->mscp_cmdref); return; /* * The AVAILABLE ATTENTION message occurs when the * unit becomes available after loading. * Marking the unit offline will force an * online command prior to using the unit. */ case M_OP_AVATN: tms->Tflags &= ~_ONLINE; return; /* * An endcode with no opcode (0x80) is an invalid command. This is supposed * to indicate a protocol error (illegal opcode, parameter error, etc) but * without the real opcode we don't know which command (reposition, write, * read, ...) failed. So, just declare I/O done and hope for the best. */ case 0: if (tmscpprintf & 0x8) log(LOG_INFO, "tms%d,%d: inv end=%x st=%x\n", sc->sc_unit,mp->mscp_unit,em_endcode,em_status); tms_iodone(mp, sc); return; case M_OP_WRITE: case M_OP_READ: tms_rw_em(mp, sc); return; case M_OP_WRITM: tms_wtm_em(mp, sc); return; case M_OP_REPOS: tms_repos_em(mp, sc); return; case M_OP_STUNT: tms_stunt_em(mp, sc); return; case M_OP_AVAIL: tms_avail_em(mp, sc); return; case M_OP_GTUNT: tms_gtunt_em(mp, sc); return; case M_OP_FLUSH: tms_flush_em(mp, sc); return; default: if (tmscpprintf & 0x8) log(LOG_INFO, "tms%d,%d rsp %x\n", sc->sc_unit, mp->mscp_unit, em_endcode); return; } /* end switch mp->mscp_opcode */ } /* * Manage buffers and perform block mode read and write operations. */ tmscpstrategy (bp) register struct buf *bp; { register struct buf *dp; register struct tmscp_softc *sc; struct tms_info *tms; int ctlr = TMSCTLR(bp->b_dev); int s; sc = &tmscp_softc[ctlr]; tms = sc->sc_drives[TMSUNIT(bp->b_dev)]; if (!tms || !(tms->Tflags & _ONLINE)) { bp->b_flags |= B_ERROR; iodone(bp); return; } /* * If we're at the end of the tape and this is not an 'ioctl' command * then return an error rather than reading or writing off the end of the tape. * Ioctl commands are allowed to proceed so that tapemarks can be written and * repositioning (rewind, etc) can be done. */ if ((tms->tms_flags & MTF_EOM) && bp != &sc->sc_cmdbuf) { bp->b_resid = bp->b_bcount; bp->b_error = ENOSPC; bp->b_flags |= B_ERROR; iodone(bp); return; } mapalloc(bp); s = spl5(); /* * Link the buffer onto the drive queue */ dp = &tms->tms_dtab; if (dp->b_actf == 0) dp->b_actf = bp; else dp->b_actl->av_forw = bp; dp->b_actl = bp; bp->av_forw = 0; /* * Link the drive onto the controller queue */ if (dp->b_active == 0) { dp->b_forw = NULL; if (sc->sc_ctab.b_actf == NULL) sc->sc_ctab.b_actf = dp; else sc->sc_ctab.b_actl->b_forw = dp; sc->sc_ctab.b_actl = dp; dp->b_active = 1; } /* * If the controller is not active, start it. */ if (sc->sc_ctab.b_active == 0) (void) tmsstart(sc); splx(s); return; } /* ARGSUSED */ tmscpioctl(dev, cmd, data, flag) dev_t dev; int cmd; caddr_t data; int flag; { struct tmscp_softc *sc = &tmscp_softc[TMSCTLR(dev)]; struct buf *bp = &sc->sc_cmdbuf; register struct tms_info *tms = sc->sc_drives[TMSUNIT(dev)]; int fcount; /* number of files (or records) to space */ register struct mtop *mtop; /* mag tape cmd op to perform */ register struct mtget *mtget; /* mag tape struct to get info in */ /* we depend of the values and order of the TMS ioctl codes here */ static char tmsops[] = {TMS_WRITM,TMS_FSF,TMS_BSF,TMS_FSR,TMS_BSR,TMS_REW,TMS_OFFL,TMS_SENSE, TMS_CACHE,TMS_NOCACHE,TMS_FLUSH}; if (!tms) return(ENXIO); if (cmd != MTIOCGET) tms->tms_status = ~M_ST_SUCC; if (tms->tms_position == 0) tms->tms_flags |= MTF_BOM; else tms->tms_flags &= ~MTF_BOM; switch (cmd) { case MTIOCTOP: mtop = (struct mtop *)data; switch (mtop->mt_op) { case MTWEOF: return(tms_wrteof(dev, tms)); case MTFSF: case MTBSF: case MTFSR: case MTBSR: fcount = mtop->mt_count; break; case MTFLUSH: case MTCACHE: case MTNOCACHE: if ((tms->Tflags & (_HASCACHE|_ONLINE)) != (_HASCACHE|_ONLINE)) return(0); case MTREW: case MTOFFL: case MTNOP: fcount = 1; break; default: return(ENXIO); } /* end switch mtop->mt_op */ if (fcount <= 0) return(EINVAL); tmscpcommand(dev, tmsops[mtop->mt_op], fcount); if (tms->tms_status != M_ST_SUCC) { if (tms->Tflags & _CLSEREX) tmscpcommand(dev, TMS_CSE, 1); return(EIO); } return(geterror(bp)); case MTIOCGET: /* * Return status info associated with the particular UNIT. */ mtget = (struct mtget *)data; mtget->mt_type = MT_ISTMSCP; mtget->mt_dsreg = tms->tms_flags << 8; mtget->mt_dsreg |= tms->tms_endcode; mtget->mt_erreg = tms->tms_status; mtget->mt_resid = tms->tms_resid; break; default: return(ENXIO); } return (0); } tms_wrteof(dev, tms) dev_t dev; register struct tms_info *tms; { tmscpcommand(dev, TMS_WRITM, 1); if (tms->tms_status != M_ST_SUCC) return(EIO); tms->tms_status = ~M_ST_SUCC; tmscpcommand(dev, TMS_WRITM, 1); if (tms->tms_status != M_ST_SUCC) return(EIO); tms->Tflags &= ~_CACHE_WRITTEN; tms->tms_status = ~M_ST_SUCC; tmscpcommand(dev, TMS_BSR, 1); if (tms->tms_status != M_ST_SUCC) return(EIO); return(0); } /* * Initialize the mscp packet for a STUNT command. If the drive is in a * 'cache lost' but 'written' state then clear it and log the error. */ tms_stunt_st(mp, sc, flgs) register struct mscp *mp; register struct tmscp_softc *sc; int flgs; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; mp->mscp_opcode = M_OP_STUNT; mp->mscp_modifier = flgs | M_MD_EXCAC; tms_cache_cmn(sc, tms, mp); /* * Set the unit flags - this includes the cache enable/disable flags. */ mp->mscp_unitflgs = tms->tms_unitflgs; /* * Only initialize the density if the position is not at BOT. On some * drives (notably the TU81) selecting a density other than the 'current' * (0) causes an illegal command error. */ if (tms->tms_position != 0) mp->mscp_format = 0; else mp->mscp_format = tms->tms_format; } /* * Initialize the mscp packet for a REPOS command. The 'flgs' parameter * specifies if the movement if forwards or backwards and if records or * tape marks (objects) are to be skipped. */ tms_repos_st(mp, sc, flgs) register struct mscp *mp; register struct tmscp_softc *sc; int flgs; { struct buf *bp = (struct buf *)mp->mscp_cmdref; register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; mp->mscp_opcode = M_OP_REPOS; mp->mscp_modifier = flgs | M_MD_CLSEX; tms->Tflags &= ~_CLSEREX; tms_cache_cmn(sc, tms, mp); /* shared code with tms_reposrec_st */ if (mp->mscp_modifier & M_MD_OBJCT) mp->mscp_reccnt = bp->b_bcount; else mp->mscp_tmkcnt = bp->b_bcount; } /* * Initialize the mscp packet for a REPOS 'record' command. Tape records do * not include tapemarks (which is why this is a separate routine from * tms_repos_st above). */ tms_reposrec_st(mp, sc, flgs) register struct mscp *mp; register struct tmscp_softc *sc; int flgs; { struct buf *bp = (struct buf *)mp->mscp_cmdref; register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; mp->mscp_opcode = M_OP_REPOS; mp->mscp_modifier = flgs; tms_cache_cmn(sc, tms, mp); mp->mscp_reccnt = bp->b_bcount; } /* * Initialize the mscp packet for an AVAIL command. The only time this is * done is on a 'rewoffl' command. */ tms_avail_st(mp, sc, flgs) register struct mscp *mp; struct tmscp_softc *sc; int flgs; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; mp->mscp_opcode = M_OP_AVAIL; mp->mscp_modifier = flgs; tms_clrerr(tms, mp); } tms_clrerr(tms, mp) register struct tms_info *tms; register struct mscp *mp; { mp->mscp_modifier |= M_MD_CLSEX; tms->Tflags &= ~_CLSEREX; if (tms->Tflags & _CACHE_LOST) { tms->Tflags &= ~(_CACHE_LOST | _CACHE_WRITTEN); mp->mscp_modifier |= M_MD_CDATL; } } /* * Initialize the mscp packet for a WTM (write tapemark) command. */ tms_wtm_st(mp, sc, flgs) register struct mscp *mp; register struct tmscp_softc *sc; int flgs; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; mp->mscp_opcode = M_OP_WRITM; mp->mscp_modifier = flgs | M_MD_CLSEX; tms->Tflags &= ~_CLSEREX; tms_cache_cmn(sc, tms, mp); } tms_cache_cmn(sc, tms, mp) register struct tmscp_softc *sc; register struct tms_info *tms; register struct mscp *mp; { if (tms->Tflags & _CACHE_LOST) { if (!(tms->Tflags & _CACHE_WRITTEN)) { tms->Tflags &= ~(_CACHE_LOST | _CACHE_WRITTEN); mp->mscp_modifier |= M_MD_CDATL; } else log(LOG_INFO, "tms%d,%d clost\n", sc->sc_unit, mp->mscp_unit); } } /* * Process the end message from a REPOS command. Called from tmscprsp(). */ tms_repos_em(mp, sc) register struct mscp *mp; register struct tmscp_softc *sc; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; u_short em_status = mp->mscp_status & M_ST_MASK; (void)tms_check_ret(mp, sc); if (em_status == M_ST_SUCC) { if (tms->tms_position != mp->mscp_position) tms->tms_flags &= ~MTF_WRITTEN; if (tms->tms_position = mp->mscp_position) tms->tms_flags &= ~MTF_BOM; else { tms->tms_flags |= MTF_BOM; tms->Tflags &= ~_LOST; } tms->Tflags &= ~_CACHE_WRITTEN; } tms->tms_resid = 0; tms_iodone(mp, sc); } /* * Process the end message from a STUNT command. */ tms_stunt_em(mp, sc) register struct mscp *mp; register struct tmscp_softc *sc; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; u_short em_status = mp->mscp_status & M_ST_MASK; (void)tms_check_ret(mp, sc); tms->Tflags &= ~_CACHE_WRITTEN; if (em_status == M_ST_SUCC) { tms->tms_unitflgs = mp->mscp_unitflgs; tms->tms_format = mp->mscp_format; tms->tms_type = mp->mscp_mediaid; tms->Tflags |= _ONLINE; } tms->tms_resid = 0; tms_iodone(mp, sc); } /* * Process the end message from a WRITM (Write Tape Mark) command. */ tms_wtm_em(mp, sc) register struct mscp *mp; register struct tmscp_softc *sc; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; u_short em_status = mp->mscp_status & M_ST_MASK; if (em_status == M_ST_SUCC) { tms->tms_flags &= ~MTF_WRITTEN; tms->tms_position = mp->mscp_position; } (void)tms_check_ret(mp, sc); tms->tms_resid = 0; tms_iodone(mp, sc); } tms_avail_em(mp, sc) register struct mscp *mp; register struct tmscp_softc *sc; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; (void)tms_check_ret(mp, sc); tms->Tflags &= ~(_INUSE | _ONLINE); tms->tms_position = 0; tms->tms_flags |= MTF_BOM; if (tms->tms_status == M_ST_SUCC) tms->Tflags &= ~_CACHE_WRITTEN; tms->tms_resid = 0; tms_iodone(mp, sc); } tms_flush_em(mp, sc) register struct mscp *mp; register struct tmscp_softc *sc; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; u_short em_status = mp->mscp_status & M_ST_MASK; if (em_status == M_ST_SUCC) tms->Tflags &= ~_CACHE_WRITTEN; else log(LOG_INFO, "tms%d,%d Flush\n",sc->sc_unit,mp->mscp_unit); tms->tms_position = mp->mscp_position; (void)tms_check_ret(mp, sc); tms->tms_resid = 0; tms_iodone(mp, sc); } tms_gtunt_em(mp, sc) register struct mscp *mp; register struct tmscp_softc *sc; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; u_short em_status = mp->mscp_status & M_ST_MASK; u_short em_flags = mp->mscp_unitflgs; (void)tms_check_ret(mp, sc); if (em_status == M_ST_SUCC) { tms->tms_format = mp->mscp_format; tms->tms_type = mp->mscp_mediaid; tms->tms_fmtmenu = mp->mscp_fmtmenu; tms->tms_unitflgs = mp->mscp_unitflgs; tms->Tflags |= _ONLINE; if (em_flags & (M_UF_WRTPH | M_UF_WRTPS | M_UF_WRTPD)) tms->tms_flags |= MTF_WRTLCK; else tms->tms_flags &= ~MTF_WRTLCK; if (em_flags & M_UF_CACH) { tms->Tflags |= _HASCACHE; if (tms->Tflags & _CACHE_ON) { tms->tms_unitflgs |= M_UF_WBKNV; tms->tms_unitflgs &= ~M_UF_SCCHH; } else { tms->tms_unitflgs &= ~M_UF_WBKNV; tms->tms_unitflgs |= M_UF_SCCHH; } } if (tms->tms_position == 0) tms->tms_flags |= MTF_BOM; else tms->tms_flags &= ~MTF_BOM; } tms->tms_resid = 0; tms_iodone(mp, sc); } tms_rw_em(mp, sc) register struct mscp *mp; register struct tmscp_softc *sc; { struct buf *bp = (struct buf *)mp->mscp_cmdref; register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; u_short em_status = mp->mscp_status & M_ST_MASK; tms->tms_flags &= ~MTF_WRITTEN; (void)tms_check_ret(mp, sc); if (em_status == M_ST_SUCC) { if ((tms->tms_endcode & ~M_OP_END) == M_OP_WRITE) tms->tms_flags |= MTF_WRITTEN; else tms->Tflags &= ~_CACHE_WRITTEN; } tms->tms_resid = bp->b_bcount - mp->mscp_bytecnt; if (tms->tms_position = mp->mscp_position) tms->tms_flags &= ~MTF_BOM; else tms->tms_flags |= MTF_BOM; tms_iodone(mp, sc); } tms_iodone(mp, sc) struct mscp *mp; struct tmscp_softc *sc; { struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; register struct buf *bp = (struct buf *)mp->mscp_cmdref; register struct buf *dp = &tms->tms_dtab; /* * Remove the buffer from the I/O wait queue. Set the residual count and * declare the I/O done. */ bp->av_back->av_forw = bp->av_forw; bp->av_forw->av_back = bp->av_back; dp->b_qsize--; bp->b_resid = tms->tms_resid; iodone(bp); } /* * Check the return status of an mscp packet and adjust the drive's flags * appropriately. On error conditions set the error flag and code in the * buffer header. * * Many of the 'case' statements below are finer grained than strictly * necessary - this is done to provide easy access for logging of specific * conditions during debugging. */ tms_check_ret(mp, sc) struct mscp *mp; struct tmscp_softc *sc; { register struct tms_info *tms = sc->sc_drives[mp->mscp_unit]; register struct buf *bp = (struct buf *)mp->mscp_cmdref; u_short em_status = mp->mscp_status & M_ST_MASK; u_short em_subcode = mp->mscp_status >> M_ST_SBBIT; u_short em_flags = mp->mscp_flags; u_short em_endcode = mp->mscp_endcode & ~M_OP_END; char berr, unkerr; if (em_status != M_ST_SUCC && em_status != M_ST_TAPEM && (tmscpprintf & 0x1)) log(LOG_INFO, "tms%d,%d st=%x sb=%x fl=%x en=%x\n", sc->sc_unit, mp->mscp_unit, em_status, em_subcode, em_flags, em_endcode); tms->tms_endcode = mp->mscp_endcode; if (em_flags & M_EF_EOT) tms->tms_flags |= MTF_EOM; else tms->tms_flags &= ~MTF_EOM; if (em_flags & M_EF_DLS) tms->Tflags |= _CACHE_LOST; if (em_flags & M_EF_PLS) tms->Tflags |= _LOST; tms->tms_status = mp->mscp_status; berr = 0; unkerr = 0; switch (em_status) { case M_ST_SUCC: { switch (em_subcode) { case M_SC_DUPUN: /* Duplicate unit */ berr = ENXIO; break; case M_SC_ALONL: /* Already online */ case M_SC_STONL: /* Still online */ case M_SC_UNIGN: /* Unload ignored */ case M_SC_ROVOL: /* Readonly unit */ tms->Tflags |= _ONLINE; break; case M_SC_EOT: /* End of Tape */ tms->tms_flags |= MTF_EOM; break; } } break; case M_ST_OFFLN: tms->Tflags &= ~_ONLINE; if ((tms->Tflags & _CACHE_ON) && (tms->Tflags & _CACHE_WRITTEN)) log(LOG_INFO, "tms%d,%d closs2\n", sc->sc_unit, mp->mscp_unit); berr = ENXIO; break; case M_ST_WRTPR: /* Write Protected */ tms->tms_flags |= MTF_WRTLCK; berr = EACCES; /* Really an error ? */ break; case M_ST_ICMD: /* Byte count too big */ case M_ST_ABRTD: /* Command aborted */ case M_ST_COMP: /* Data Compare error */ case M_ST_DATA: /* Data error */ case M_ST_HSTBF: /* Host buffer error */ case M_ST_CNTLR: /* Controller error */ case M_ST_FMTER: /* Formatter error */ berr = EIO; break; case M_ST_AVLBL: /* Unit available */ tms->Tflags &= ~(_LOST | _CLSEREX | _SEREX | _ONLINE); break; case M_ST_BOT: /* Beginning of tape */ tms->tms_flags |= MTF_BOM; tms->tms_position = 0; break; case M_ST_TAPEM: /* Tape Mark */ tms->Tflags |= (_BUFMARK | _CLSEREX); break; case M_ST_SEX: /* Serious Exception */ if (em_flags & M_EF_EOT) { tms->tms_flags |= MTF_EOM; berr = ENOSPC; } else { tms->Tflags &= ~_CLSEREX; log(LOG_INFO, "tms%d,%d serex sb %x\n", sc->sc_unit, mp->mscp_unit, em_subcode); berr = EIO; } break; case M_ST_PLOST: /* Position Lost */ log(LOG_INFO, "tms%d,%d plost\n", sc->sc_unit, mp->mscp_unit); tms->Tflags |= (_LOST | _SEREX); tms->Tflags &= ~_CLSEREX; berr = EIO; break; case M_ST_LED: /* Logical end of dev */ tms->Tflags |= _CLSEREX; break; case M_ST_MFMTE: /* Media format err */ case M_ST_DRIVE: /* Drive error */ case M_ST_RDTRN: /* Record truncated */ tms->Tflags |= _SEREX; tms->Tflags &= ~_CLSEREX; case M_ST_LOADR: case M_ST_DIAG: /* Intern diag err */ case M_ST_IPARM: /* Invalid parameter */ berr = EIO; break; default: unkerr = 1; berr = EIO; break; } if (unkerr) log(LOG_INFO, "tms%d,%d: st/sb =%x/%x\n", sc->sc_unit, mp->mscp_unit, em_status, em_subcode); if (berr && bp) { bp->b_flags |= B_ERROR; bp->b_error = berr; } return; } /* * Process an error log datagram. * * No decoding is done - it wasn't worth the D space. If bit 1 is set * in the print flags then a simple message is generated out of (hopefully) * useful fields. * * Eventually a error log daemon will be written and the datagram sent to * it for capture and detailed analysis. Until then the logging in * 'tms_check_ret' and a few other strategic spots will have to suffice. */ u_short tms_datagrams[NTMSCP]; tmserror(ctlr, mp) int ctlr; register struct mslg *mp; { tms_datagrams[ctlr]++; if (tmscpprintf & 0x2) log(LOG_INFO, "tms%d,%d dgram fmt=%x evt=%x grp=%x flg=%x pos=%D\n", ctlr, mp->mslg_unit, mp->mslg_format, mp->mslg_event, mp->mslg_group, mp->mslg_flags, mp->mslg_position); } #endif NTMSCP