diff options
Diffstat (limited to 'sys/i386/isa/ft.c')
-rw-r--r-- | sys/i386/isa/ft.c | 2580 |
1 files changed, 2580 insertions, 0 deletions
diff --git a/sys/i386/isa/ft.c b/sys/i386/isa/ft.c new file mode 100644 index 0000000000000..2b7fd5fa90e88 --- /dev/null +++ b/sys/i386/isa/ft.c @@ -0,0 +1,2580 @@ +/* + * Copyright (c) 1993, 1994 Steve Gerakines + * + * This is freely redistributable software. You may do anything you + * wish with it, so long as the above notice stays intact. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * ft.c - QIC-40/80 floppy tape driver + * $Id: ft.c,v 1.37 1998/04/17 22:36:32 des Exp $ + * + * 01/19/95 ++sg + * Cleaned up recalibrate/seek code at attach time for FreeBSD 2.x. + * + * 06/07/94 v0.9 ++sg + * Tape stuck on segment problem should be gone. Re-wrote buffering + * scheme. Added support for drives that do not automatically perform + * seek load point. Can handle more wakeup types now and should correctly + * report most manufacturer names. Fixed places where unit 0 was being + * sent to the fdc instead of the actual unit number. Added ioctl support + * for an in-core badmap. + * + * 01/26/94 v0.3b - Jim Babb + * Got rid of the hard coded device selection. Moved (some of) the + * static variables into a structure for support of multiple devices. + * ( still has a way to go for 2 controllers - but closer ) + * Changed the interface with fd.c so we no longer 'steal' its + * driver routine vectors. + * + * 10/30/93 v0.3 + * Fixed a couple more bugs. Reading was sometimes looping when an + * an error such as address-mark-missing was encountered. Both + * reading and writing was having more backup-and-retries than was + * necessary. Added support to get hardware info. Updated for use + * with FreeBSD. + * + * 09/15/93 v0.2 pl01 + * Fixed a bunch of bugs: extra isa_dmadone() in async_write() (shouldn't + * matter), fixed double buffering in async_req(), changed tape_end() in + * set_fdcmode() to reduce unexpected interrupts, changed end of track + * processing in async_req(), protected more of ftreq_rw() with an + * splbio(). Changed some of the ftreq_*() functions so that they wait + * for inactivity and then go, instead of aborting immediately. + * + * 08/07/93 v0.2 release + * Shifted from ftstrat to ioctl support for I/O. Streaming is now much + * more reliable. Added internal support for error correction, QIC-40, + * and variable length tapes. Random access of segments greatly + * improved. Formatting and verification support is close but still + * incomplete. + * + * 06/03/93 v0.1 Alpha release + * Hopefully the last re-write. Many bugs fixed, many remain. + */ + +#include "ft.h" +#if NFT > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/disklabel.h> /* temp. for dkunit() in fdc.h */ +#include <sys/malloc.h> +#include <sys/buf.h> +#include <sys/ftape.h> +#include <sys/kernel.h> + +#include <machine/clock.h> + +#include <i386/isa/isa_device.h> +#ifdef PC98 +#include <pc98/pc98/fdreg.h> +#else +#include <i386/isa/fdreg.h> +#endif +#include <i386/isa/fdc.h> +#include <i386/isa/ftreg.h> + +extern int ftintr __P((ftu_t ftu)); + +/* Enable or disable debugging messages. */ +#define FTDBGALL 0 /* 1 if you want everything */ +/*#define DPRT(a) printf a */ +#define DPRT(a) + +/* Constants private to the driver */ +#define FTPRI (PRIBIO) /* sleep priority */ +#define FTNBUFF 9 /* 8 for buffering, 1 for header */ + +/* The following items are needed from the fd driver. */ +extern int in_fdc(int); /* read fdc registers */ +extern int out_fdc(int, int); /* write fdc registers */ + +/* Flags in isadev struct */ +#define FT_PROBE 0x1 /* allow for "dangerous" tape probes */ + +/* Type of tape attached */ +/* use numbers that don't interfere with the possible floppy types */ +#define NO_TYPE 0 /* (same as NO_TYPE in fd.c) */ + +/* F_TAPE_TYPE must match value in fd.c */ +#define F_TAPE_TYPE 0x020 /* bit for ft->types to indicate tape */ +#define FT_NONE (F_TAPE_TYPE | 0) /* no method required */ +#define FT_MOUNTAIN (F_TAPE_TYPE | 1) /* mountain */ +#define FT_COLORADO (F_TAPE_TYPE | 2) /* colorado */ +#define FT_INSIGHT (F_TAPE_TYPE | 3) /* insight */ + +/* Mode FDC is currently in: tape or disk */ +enum { FDC_TAPE_MODE, FDC_DISK_MODE }; + +/* Command we are awaiting completion of */ +enum { FTCMD_NONE, FTCMD_RESET, FTCMD_RECAL, FTCMD_SEEK, FTCMD_READID }; + +/* Tape interrupt status of current request */ +enum { FTSTS_NONE, FTSTS_SNOOZE, FTSTS_INTERRUPT, FTSTS_TIMEOUT }; + +/* Tape I/O status */ +enum { + FTIO_READY, /* No I/O activity */ + FTIO_READING, /* Currently reading blocks */ + FTIO_RDAHEAD, /* Currently reading ahead */ + FTIO_WRITING /* Buffers are being written */ +}; + +/* Current tape mode */ +enum { + FTM_PRIMARY, /* Primary mode */ + FTM_VERIFY, /* Verify mode */ + FTM_FORMAT, /* Format mode */ + FTM_DIAG1, /* Diagnostic mode 1 */ + FTM_DIAG2 /* Diagnostic mode 2 */ +}; + +/* Tape geometries table */ +static QIC_Geom ftgtbl[] = { + { 0, 0, "Unformatted", "Unknown", 0, 0, 0, 0, 0 }, /* XXX */ + { 1, 1, "QIC-40", "205/550", 20, 68, 2176, 128, 21760 }, + { 1, 2, "QIC-40", "307.5/550", 20, 102, 3264, 128, 32640 }, + { 1, 3, "QIC-40", "295/900", 0, 0, 0, 0, 0 }, /* ??? */ + { 1, 4, "QIC-40", "1100/550", 20, 365, 11680, 128, 32512 }, + { 1, 5, "QIC-40", "1100/900", 0, 0, 0, 0, 0 }, /* ??? */ + { 2, 1, "QIC-80", "205/550", 28, 100, 3200, 128, 19200 }, + { 2, 2, "QIC-80", "307.5/550", 28, 150, 4800, 128, 19200 }, + { 2, 3, "QIC-80", "295/900", 0, 0, 0, 0, 0 }, /* ??? */ + { 2, 4, "QIC-80", "1100/550", 28, 537, 17184, 128, 32512 }, + { 2, 5, "QIC-80", "1100/900", 0, 0, 0, 0, 0 }, /* ??? */ + { 3, 1, "QIC-500", "205/550", 0, 0, 0, 0, 0 }, /* ??? */ + { 3, 2, "QIC-500", "307.5/550", 0, 0, 0, 0, 0 }, /* ??? */ + { 3, 3, "QIC-500", "295/900", 0, 0, 0, 0, 0 }, /* ??? */ + { 3, 4, "QIC-500", "1100/550", 0, 0, 0, 0, 0 }, /* ??? */ + { 3, 5, "QIC-500", "1100/900", 0, 0, 0, 0, 0 } /* ??? */ +}; +#define NGEOM (sizeof(ftgtbl) / sizeof(QIC_Geom)) + +static QIC_Geom *ftg = NULL; /* Current tape's geometry */ + +/* + * things relating to asynchronous commands + */ +static int awr_state; /* state of async write */ +static int ard_state; /* state of async read */ +static int arq_state; /* state of async request */ +static int async_retries; /* retries, one per invocation */ +static int async_func; /* function to perform */ +static int async_state; /* state current function is at */ +static int async_arg0; /* up to 3 arguments for async cmds */ +static int async_arg1; /**/ +static int async_arg2; /**/ +static int async_ret; /* return value */ +static struct _astk { + int over_func; + int over_state; + int over_retries; + int over_arg0; + int over_arg1; + int over_arg2; +} astk[10]; +static struct _astk *astk_ptr = &astk[0]; /* Pointer to stack position */ + +/* List of valid async (interrupt driven) tape support functions. */ +enum { + ACMD_NONE, /* no command */ + ACMD_SEEK, /* command seek */ + ACMD_STATUS, /* report status */ + ACMD_STATE, /* wait for state bits to be true */ + ACMD_SEEKSTS, /* perform command and wait for status */ + ACMD_READID, /* read id */ + ACMD_RUNBLK /* ready tape for I/O on the given block */ +}; + +/* Call another asyncronous command from within async_cmd(). */ +#define CALL_ACMD(r,f,a,b,c) \ + astk_ptr->over_retries = async_retries; \ + astk_ptr->over_func = async_func; \ + astk_ptr->over_state = (r); \ + astk_ptr->over_arg0 = async_arg0; \ + astk_ptr->over_arg1 = async_arg1; \ + astk_ptr->over_arg2 = async_arg2; \ + async_func = (f); async_state = 0; async_retries = 0; \ + async_arg0=(a); async_arg1=(b); async_arg2=(c); \ + astk_ptr++; \ + goto restate + +/* Perform an asyncronous command from outside async_cmd(). */ +#define ACMD_FUNC(r,f,a,b,c) over_async = (r); astk_ptr = &astk[0]; \ + async_func = (f); async_state = 0; async_retries = 0; \ + async_arg0=(a); async_arg1=(b); async_arg2=(c); \ + async_cmd(ftu); \ + return + +/* Various wait channels */ +static char *wc_buff_avail = "bavail"; +static char *wc_buff_done = "bdone"; +static char *wc_iosts_change = "iochg"; +static char *wc_long_delay = "ldelay"; +static char *wc_intr_wait = "intrw"; +#define ftsleep(wc,to) tsleep((caddr_t)(wc),FTPRI,(wc),(to)) + +/***********************************************************************\ +* Per controller structure. * +\***********************************************************************/ +extern struct fdc_data fdc_data[NFDC]; + +/***********************************************************************\ +* Per tape drive structure. * +\***********************************************************************/ +static struct ft_data { + struct fdc_data *fdc; /* pointer to controller structure */ + int ftsu; /* this units number on this controller */ + int type; /* Drive type (Mountain, Colorado) */ +/* QIC_Geom *ftg; */ /* pointer to Current tape's geometry */ + int flags; + int cmd_wait; /* Command we are awaiting completion of */ + int sts_wait; /* Tape interrupt status of current request */ + int io_sts; /* Tape I/O status */ + int mode; + int pcn; /* present cylinder number */ + int attaching; /* true when ft is attaching */ + unsigned char *xptr; /* pointer to buffer blk to xfer */ + int xcnt; /* transfer count */ + int xblk; /* block number to transfer */ + int xseg; /* segment being transferred */ + SegReq *segh; /* Current I/O request */ + SegReq *segt; /* Tail of queued I/O requests */ + SegReq *doneh; /* Completed I/O request queue */ + SegReq *donet; /* Completed I/O request tail */ + SegReq *segfree; /* Free segments */ + SegReq *hdr; /* Current tape header */ + int nsegq; /* Segments on request queue */ + int ndoneq; /* Segments on completed queue */ + int nfreelist; /* Segments on free list */ + + /* the next 3 should be defines in 'flags' */ + int active; /* TRUE if transfer is active */ + int rdonly; /* TRUE if tape is read-only */ + int newcart; /* TRUE if new cartridge detected */ + int laststs; /* last reported status code */ + int lastcfg; /* last reported QIC config */ + int lasterr; /* last QIC error code */ + int lastpos; /* last known segment number */ + int moving; /* TRUE if tape is moving */ + int rid[7]; /* read_id return values */ + struct callout_handle tohandle; +} *ft_data[NFT]; + +/***********************************************************************\ +* Throughout this file the following conventions will be used: * +* ft is a pointer to the ft_data struct for the drive in question * +* fdc is a pointer to the fdc_data struct for the controller * +* ftu is the tape drive unit number * +* fdcu is the floppy controller unit number * +* ftsu is the tape drive unit number on that controller. (sub-unit) * +\***********************************************************************/ + + + +#define id_physid id_scsiid /* this biotab field doubles as a field */ + /* for the physical unit number on the controller */ + +int ftopen(dev_t, int); +int ftclose(dev_t, int); +int ftioctl(dev_t, unsigned long, caddr_t, int, struct proc *); +int ftattach(struct isa_device *, struct isa_device *, int); +static timeout_t ft_timeout; +static void async_cmd(ftu_t); +static void async_req(ftu_t, int); +static void async_read(ftu_t, int); +static void async_write(ftu_t, int); +static void tape_start(ftu_t, int); +static void tape_end(ftu_t); +static void tape_inactive(ftu_t); +static int tape_cmd(ftu_t, int); +static int tape_status(ftu_t); +static int qic_status(ftu_t, int, int); +static int ftreq_rewind(ftu_t); +static int ftreq_hwinfo(ftu_t, QIC_HWInfo *); + +/*****************************************************************************/ + + +/* + * Allocate a segment I/O buffer from the free list. + */ +static SegReq * +segio_alloc(ft_p ft) +{ + SegReq *r; + + /* Grab first item from free list */ + if ((r = ft->segfree) != NULL) { + ft->segfree = ft->segfree->next; + ft->nfreelist--; + } + DPRT(("segio_alloc: nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq)); + return(r); +} + + +/* + * Queue a segment I/O request. + */ +static void +segio_queue(ft_p ft, SegReq *sp) +{ + /* Put request on in process queue. */ + if (ft->segt == NULL) + ft->segh = sp; + else + ft->segt->next = sp; + sp->next = NULL; + ft->segt = sp; + ft->nsegq++; + DPRT(("segio_queue: nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq)); +} + + +/* + * Segment I/O completed, place on correct queue. + */ +static void +segio_done(ft_p ft, SegReq *sp) +{ + /* First remove from current I/O queue */ + ft->segh = sp->next; + if (ft->segh == NULL) ft->segt = NULL; + ft->nsegq--; + + if (sp->reqtype == FTIO_WRITING) { + /* Place on free list */ + sp->next = ft->segfree; + ft->segfree = sp; + ft->nfreelist++; + wakeup((caddr_t)wc_buff_avail); + DPRT(("segio_done: (w) nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq)); + } else { + /* Put on completed I/O queue */ + if (ft->donet == NULL) + ft->doneh = sp; + else + ft->donet->next = sp; + sp->next = NULL; + ft->donet = sp; + ft->ndoneq++; + wakeup((caddr_t)wc_buff_done); + DPRT(("segio_done: (r) nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq)); + } +} + + +/* + * Take I/O request from finished queue to free queue. + */ +static void +segio_free(ft_p ft, SegReq *sp) +{ + /* First remove from done queue */ + ft->doneh = sp->next; + if (ft->doneh == NULL) ft->donet = NULL; + ft->ndoneq--; + + /* Place on free list */ + sp->next = ft->segfree; + ft->segfree = sp; + ft->nfreelist++; + wakeup((caddr_t)wc_buff_avail); + DPRT(("segio_free: nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq)); +} + +/* + * Probe/attach floppy tapes. + */ +int +ftattach(isadev, fdup, unithasfd) + struct isa_device *isadev, *fdup; + int unithasfd; +{ + fdcu_t fdcu = isadev->id_unit; /* fdc active unit */ + fdc_p fdc = fdc_data + fdcu; /* pointer to controller structure */ + ftu_t ftu = fdup->id_unit; + ft_p ft; + ftsu_t ftsu = fdup->id_physid; + QIC_HWInfo hw; + char *manu; + + if (ftu >= NFT) return 0; + ft = ft_data[ftu] = malloc(sizeof *ft, M_DEVBUF, M_NOWAIT); + bzero(ft, sizeof *ft); + callout_handle_init(&ft->tohandle); + + /* Probe for tape */ + ft->attaching = 1; + ft->type = NO_TYPE; + ft->fdc = fdc; + ft->ftsu = ftsu; + + /* + * FT_NONE - no method, just do it + */ + tape_start(ftu, 0); + if (tape_status(ftu) >= 0) { + ft->type = FT_NONE; + ftreq_hwinfo(ftu, &hw); + goto out; + } + + /* + * FT_COLORADO - colorado style + */ + tape_start(ftu, 0); + tape_cmd(ftu, QC_COL_ENABLE1); + tape_cmd(ftu, QC_COL_ENABLE2 + ftu); + if (tape_status(ftu) >= 0) { + ft->type = FT_COLORADO; + ftreq_hwinfo(ftu, &hw); + tape_cmd(ftu, QC_COL_DISABLE); + goto out; + } + + /* + * FT_MOUNTAIN - mountain style + */ + tape_start(ftu, 0); + tape_cmd(ftu, QC_MTN_ENABLE1); + tape_cmd(ftu, QC_MTN_ENABLE2); + if (tape_status(ftu) >= 0) { + ft->type = FT_MOUNTAIN; + ftreq_hwinfo(ftu, &hw); + tape_cmd(ftu, QC_MTN_DISABLE); + goto out; + } + + if(isadev->id_flags & FT_PROBE) { + /* + * Insight probe is dangerous, since it requires the motor being + * enabled and therefore risks attached floppy disk drives to jam. + * Probe only if explicitly requested by a flag 0x1 from config + */ + + /* + * FT_INSIGHT - insight style + * + * Since insight requires turning the drive motor on, we will not + * perform this probe if a floppy drive was already found with the + * the given unit and controller. + */ + if (unithasfd) goto out; + tape_start(ftu, 1); + if (tape_status(ftu) >= 0) { + ft->type = FT_INSIGHT; + ftreq_hwinfo(ftu, &hw); + goto out; + } + } + +out: + tape_end(ftu); + if (ft->type != NO_TYPE) { + fdc->flags |= FDC_HASFTAPE; + switch(hw.hw_make) { + case 0x0000: + if (ft->type == FT_COLORADO) { + manu = "Colorado"; + } else if (ft->type == FT_INSIGHT) { + manu = "Insight"; + } else if (ft->type == FT_MOUNTAIN && hw.hw_model == 0x05) { + manu = "Archive"; + } else if (ft->type == FT_MOUNTAIN) { + manu = "Mountain"; + } else { + manu = "Unknown"; + } + break; + case 0x0001: + manu = "Colorado"; + break; + case 0x0005: + if (hw.hw_model >= 0x09) { + manu = "Conner"; + } else { + manu = "Archive"; + } + break; + case 0x0006: + manu = "Mountain"; + break; + case 0x0007: + manu = "Wangtek"; + break; + case 0x0222: + manu = "IOMega"; + break; + default: + manu = "Unknown"; + break; + } + printf("ft%d: %s tape\n", fdup->id_unit, manu); + } + ft->attaching = 0; + return(ft->type); +} + + +/* + * Perform common commands asynchronously. + */ +static void +async_cmd(ftu_t ftu) { + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; + int cmd, i, st0, st3, pcn; + static int bitn, retval, retpos, nbits, newcn; + static int wanttrk, wantblk, wantdir; + static int curtrk, curblk, curdir, curdiff; + static int errcnt = 0; + +restate: +#if FTDBGALL + DPRT(("async_cmd state: func: %d state: %d\n", async_func, async_state)); +#endif + switch(async_func) { + case ACMD_SEEK: + /* + * Arguments: + * 0 - command to perform + */ + switch (async_state) { + case 0: + cmd = async_arg0; +#if FTDBGALL + DPRT(("===>async_seek cmd = %d\n", cmd)); +#endif + newcn = (cmd <= ft->pcn) ? ft->pcn - cmd : ft->pcn + cmd; + async_state = 1; + i = 0; + if (out_fdc(fdcu, NE7CMD_SEEK) < 0) i = 1; + if (!i && out_fdc(fdcu, ftu) < 0) i = 1; + if (!i && out_fdc(fdcu, newcn) < 0) i = 1; + if (i) { + if (++async_retries >= 10) { + DPRT(("ft%d: async_cmd command seek failed!!\n", ftu)); + goto complete; + } + DPRT(("ft%d: async_cmd command seek retry...\n",ftu)); + async_state = 0; + goto restate; + } + break; + case 1: + out_fdc(fdcu, NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + pcn = in_fdc(fdcu); + if (st0 < 0 || pcn < 0 || newcn != pcn) { + if (++async_retries >= 10) { + DPRT(("ft%d: async_cmd seek retries exceeded\n",ftu)); + goto complete; + } + DPRT(("ft%d: async_cmd command bad st0=$%02x pcn=$%02x\n", + ftu, st0, pcn)); + async_state = 0; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); + break; + } + if (st0 & 0x20) { /* seek done */ + ft->pcn = pcn; + } +#if FTDBGALL + else + DPRT(("ft%d: async_seek error st0 = $%02x pcn = %d\n", + ftu, st0, pcn)); +#endif + if (async_arg1) goto complete; + async_state = 2; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/50); + break; + case 2: + goto complete; + /* NOTREACHED */ + } + break; + + case ACMD_STATUS: + /* + * Arguments: + * 0 - command to issue report from + * 1 - number of bits + * modifies: bitn, retval, st3 + */ + switch (async_state) { + case 0: + bitn = 0; + retval = 0; + cmd = async_arg0; + nbits = async_arg1; + DPRT(("async_status got cmd = %d nbits = %d\n", cmd,nbits)); + CALL_ACMD(5, ACMD_SEEK, QC_NEXTBIT, 0, 0); + /* NOTREACHED */ + case 1: + out_fdc(fdcu, NE7CMD_SENSED); + out_fdc(fdcu, ftu); + st3 = in_fdc(fdcu); + if (st3 < 0) { + DPRT(("ft%d: async_status timed out on bit %d r=$%02x\n", + ftu,bitn,retval)); + async_ret = -1; + goto complete; + } + if ((st3 & 0x10) != 0) retval |= (1 << bitn); + bitn++; + if (bitn >= (nbits+2)) { + if ((retval & 1) && (retval & (1 << (nbits+1)))) { + async_ret = (retval & ~(1<<(nbits+1))) >> 1; + if (async_arg0 == QC_STATUS && async_arg2 == 0 && + (async_ret & (QS_ERROR|QS_NEWCART))) { + async_state = 2; + goto restate; + } + DPRT(("async status got $%04x ($%04x)\n", async_ret,retval)); + } else { + DPRT(("ft%d: async_status failed: retval=$%04x nbits=%d\n", + ftu, retval,nbits)); + async_ret = -2; + } + goto complete; + } + CALL_ACMD(1, ACMD_SEEK, QC_NEXTBIT, 0, 0); + /* NOTREACHED */ + case 2: + if (async_ret & QS_NEWCART) ft->newcart = 1; + CALL_ACMD(3, ACMD_STATUS, QC_ERRCODE, 16, 1); + case 3: + ft->lasterr = async_ret; + if ((ft->lasterr & QS_NEWCART) == 0 && ft->lasterr) { + DPRT(("ft%d: QIC error %d occurred on cmd %d\n", + ftu, ft->lasterr & 0xff, ft->lasterr >> 8)); + } + cmd = async_arg0; + nbits = async_arg1; + CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 1); + case 4: + goto complete; + case 5: + CALL_ACMD(6, ACMD_SEEK, QC_NEXTBIT, 0, 0); + case 6: + CALL_ACMD(7, ACMD_SEEK, QC_NEXTBIT, 0, 0); + case 7: + CALL_ACMD(8, ACMD_SEEK, QC_NEXTBIT, 0, 0); + case 8: + cmd = async_arg0; + CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0); + } + break; + + case ACMD_STATE: + /* + * Arguments: + * 0 - status bits to check + */ + switch(async_state) { + case 0: + CALL_ACMD(1, ACMD_STATUS, QC_STATUS, 8, 0); + case 1: + if ((async_ret & async_arg0) != 0) goto complete; + async_state = 0; + if (++async_retries == 360) { /* 90 secs. */ + DPRT(("ft%d: acmd_state exceeded retry count\n", ftu)); + goto complete; + } + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/4); + break; + } + break; + + case ACMD_SEEKSTS: + /* + * Arguments: + * 0 - command to perform + * 1 - status bits to check + * 2 - (optional) seconds to wait until completion + */ + switch(async_state) { + case 0: + cmd = async_arg0; + async_retries = (async_arg2) ? (async_arg2 * 4) : 10; + CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0); + case 1: + CALL_ACMD(2, ACMD_STATUS, QC_STATUS, 8, 0); + case 2: + if ((async_ret & async_arg1) != 0) goto complete; + if (--async_retries == 0) { + DPRT(("ft%d: acmd_seeksts retries exceeded\n", ftu)); + goto complete; + } + async_state = 1; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/4); + break; + } + break; + + case ACMD_READID: + /* + * Arguments: (none) + */ + switch(async_state) { + case 0: + if (!ft->moving) { + CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + /* NOTREACHED */ + } + async_state = 1; + out_fdc(fdcu, 0x4a); /* READ_ID */ + out_fdc(fdcu, ftu); + break; + case 1: + for (i = 0; i < 7; i++) ft->rid[i] = in_fdc(fdcu); + async_ret = (ft->rid[3]*ftg->g_fdtrk) + + (ft->rid[4]*ftg->g_fdside) + ft->rid[5] - 1; + DPRT(("readid st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d\n", + ft->rid[0], ft->rid[1], ft->rid[2], ft->rid[3], + ft->rid[4], ft->rid[5], async_ret)); + if ((ft->rid[0] & 0xc0) != 0 || async_ret < 0) { + /* + * Method for retry: + * errcnt == 1 regular retry + * 2 microstep head 1 + * 3 microstep head 2 + * 4 microstep head back to 0 + * 5 fail + */ + if (++errcnt >= 5) { + DPRT(("ft%d: acmd_readid errcnt exceeded\n", fdcu)); + async_ret = -2; + errcnt = 0; + goto complete; + } + if (errcnt == 1) { + ft->moving = 0; + CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + } else { + ft->moving = 0; + CALL_ACMD(4, ACMD_SEEKSTS, QC_STPAUSE, QS_READY, 0); + } + DPRT(("readid retry %d...\n", errcnt)); + async_state = 0; + goto restate; + } + if ((async_ret % ftg->g_blktrk) == (ftg->g_blktrk-1)) { + DPRT(("acmd_readid detected last block on track\n")); + retpos = async_ret; + CALL_ACMD(2, ACMD_STATE, QS_BOT|QS_EOT, 0, 0); + /* NOTREACHED */ + } + ft->lastpos = async_ret; + errcnt = 0; + goto complete; + /* NOTREACHED */ + case 2: + CALL_ACMD(3, ACMD_STATE, QS_READY, 0, 0); + case 3: + ft->moving = 0; + async_ret = retpos+1; + goto complete; + case 4: + CALL_ACMD(5, ACMD_SEEK, QC_FORWARD, 0, 0); + case 5: + ft->moving = 1; + async_state = 0; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */ + break; + } + break; + + case ACMD_RUNBLK: + /* + * Arguments: + * 0 - block number I/O will be performed on + * + * modifies: curpos + */ + switch (async_state) { + case 0: + wanttrk = async_arg0 / ftg->g_blktrk; + wantblk = async_arg0 % ftg->g_blktrk; + wantdir = wanttrk & 1; + ft->moving = 0; + CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + case 1: + curtrk = wanttrk; + curdir = curtrk & 1; + DPRT(("Changing to track %d\n", wanttrk)); + CALL_ACMD(2, ACMD_SEEK, QC_SEEKTRACK, 0, 0); + case 2: + cmd = wanttrk+2; + CALL_ACMD(3, ACMD_SEEKSTS, cmd, QS_READY, 0); + case 3: + CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 0); + case 4: + ft->laststs = async_ret; + if (wantblk == 0) { + curblk = 0; + cmd = (wantdir) ? QC_SEEKEND : QC_SEEKSTART; + CALL_ACMD(6, ACMD_SEEKSTS, cmd, QS_READY, 90); + } + if (ft->laststs & QS_BOT) { + DPRT(("Tape is at BOT\n")); + curblk = (wantdir) ? 4800 : 0; + async_state = 6; + goto restate; + } + if (ft->laststs & QS_EOT) { + DPRT(("Tape is at EOT\n")); + curblk = (wantdir) ? 0 : 4800; + async_state = 6; + goto restate; + } + CALL_ACMD(5, ACMD_READID, 0, 0, 0); + case 5: + if (async_ret < 0) { + ft->moving = 0; + ft->lastpos = -2; + if (async_ret == -2) { + CALL_ACMD(9, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + } + CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + } + curtrk = (async_ret+1) / ftg->g_blktrk; + curblk = (async_ret+1) % ftg->g_blktrk; + DPRT(("gotid: curtrk=%d wanttrk=%d curblk=%d wantblk=%d\n", + curtrk, wanttrk, curblk, wantblk)); + if (curtrk != wanttrk) { /* oops! */ + DPRT(("oops!! wrong track!\n")); + CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + } + async_state = 6; + goto restate; + case 6: + DPRT(("curtrk = %d nextblk = %d\n", curtrk, curblk)); + if (curblk == wantblk) { + ft->lastpos = curblk - 1; + async_ret = ft->lastpos; + if (ft->moving) goto complete; + CALL_ACMD(7, ACMD_STATE, QS_READY, 0, 0); + } + if (curblk > wantblk) { /* passed it */ + ft->moving = 0; + CALL_ACMD(10, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + } + if ((wantblk - curblk) <= 256) { /* approaching it */ + CALL_ACMD(5, ACMD_READID, 0, 0, 0); + } + /* way up ahead */ + ft->moving = 0; + CALL_ACMD(14, ACMD_SEEKSTS, QC_STOP, QS_READY, 0); + break; + case 7: + ft->moving = 1; + CALL_ACMD(8, ACMD_SEEK, QC_FORWARD, 0, 0); + break; + case 8: + async_state = 9; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */ + break; + case 9: + goto complete; + case 10: + curdiff = ((curblk - wantblk) / QCV_BLKSEG) + 2; + if (curdiff >= ftg->g_segtrk) curdiff = ftg->g_segtrk - 1; + DPRT(("pos %d past %d, reverse %d\n", curblk, wantblk, curdiff)); + CALL_ACMD(11, ACMD_SEEK, QC_SEEKREV, 0, 0); + case 11: + DPRT(("reverse 1 done\n")); + CALL_ACMD(12, ACMD_SEEK, (curdiff & 0xf)+2, 0, 0); + case 12: + DPRT(("reverse 2 done\n")); + CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff>>4)&0xf)+2, QS_READY, 90); + case 13: + CALL_ACMD(5, ACMD_READID, 0, 0, 0); + case 14: + curdiff = ((wantblk - curblk) / QCV_BLKSEG) - 2; + if (curdiff < 0) curdiff = 0; + DPRT(("pos %d before %d, forward %d\n", curblk, wantblk, curdiff)); + CALL_ACMD(15, ACMD_SEEK, QC_SEEKFWD, 0, 0); + case 15: + DPRT(("forward 1 done\n")); + CALL_ACMD(16, ACMD_SEEK, (curdiff & 0xf)+2, 0, 0); + case 16: + DPRT(("forward 2 done\n")); + CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff>>4)&0xf)+2, QS_READY, 90); + } + break; + } + + return; + +complete: + if (astk_ptr != &astk[0]) { + astk_ptr--; + async_retries = astk_ptr->over_retries; + async_func = astk_ptr->over_func; + async_state = astk_ptr->over_state; + async_arg0 = astk_ptr->over_arg0; + async_arg1 = astk_ptr->over_arg1; + async_arg2 = astk_ptr->over_arg2; + goto restate; + } + async_func = ACMD_NONE; + async_state = 0; + switch (ft->io_sts) { + case FTIO_READY: + async_req(ftu, 2); + break; + case FTIO_READING: + case FTIO_RDAHEAD: + async_read(ftu, 2); + break; + case FTIO_WRITING: + async_write(ftu, 2); + break; + default: + DPRT(("ft%d: bad async_cmd ending I/O state!\n", ftu)); + break; + } +} + + +/* + * Entry point for the async request processor. + */ +static void +async_req(ftu_t ftu, int from) +{ + ft_p ft = ft_data[ftu]; + SegReq *sp; + static int over_async, lastreq; + int cmd; + + if (from == 2) arq_state = over_async; + +restate: + switch (arq_state) { + case 0: /* Process segment */ + sp = ft->segh; + ft->io_sts = (sp == NULL) ? FTIO_READY : sp->reqtype; + + if (ft->io_sts == FTIO_WRITING) + async_write(ftu, from); + else + async_read(ftu, from); + if (ft->io_sts != FTIO_READY) return; + + /* Pull buffer from current I/O queue */ + if (sp != NULL) { + lastreq = sp->reqtype; + segio_done(ft, sp); + + /* If I/O cancelled, clear finished queue. */ + if (sp->reqcan) { + while (ft->doneh != NULL) + segio_free(ft, ft->doneh); + lastreq = FTIO_READY; + } + } else + lastreq = FTIO_READY; + + /* Detect end of track */ + if (((ft->xblk / QCV_BLKSEG) % ftg->g_segtrk) == 0) { + ACMD_FUNC(2, ACMD_STATE, QS_BOT|QS_EOT, 0, 0); + } + arq_state = 1; + goto restate; + + case 1: /* Next request */ + /* If we have another request queued, start it running. */ + if (ft->segh != NULL) { + sp = ft->segh; + sp->reqcrc = 0; + arq_state = ard_state = awr_state = 0; + ft->xblk = sp->reqblk; + ft->xseg = sp->reqseg; + ft->xcnt = 0; + ft->xptr = sp->buff; + DPRT(("I/O reqblk = %d\n", ft->xblk)); + goto restate; + } + + /* If the last request was reading, do read ahead. */ + if ((lastreq == FTIO_READING || lastreq == FTIO_RDAHEAD) && + (sp = segio_alloc(ft)) != NULL) { + sp->reqtype = FTIO_RDAHEAD; + sp->reqblk = ft->xblk; + sp->reqseg = ft->xseg+1; + sp->reqcrc = 0; + sp->reqcan = 0; + segio_queue(ft, sp); + bzero(sp->buff, QCV_SEGSIZE); + arq_state = ard_state = awr_state = 0; + ft->xblk = sp->reqblk; + ft->xseg = sp->reqseg; + ft->xcnt = 0; + ft->xptr = sp->buff; + DPRT(("Processing readahead reqblk = %d\n", ft->xblk)); + goto restate; + } + + if (ft->moving) { + DPRT(("No more I/O.. Stopping.\n")); + ft->moving = 0; + ACMD_FUNC(7, ACMD_SEEKSTS, QC_PAUSE, QS_READY, 0); + break; + } + arq_state = 7; + goto restate; + + case 2: /* End of track */ + ft->moving = 0; + ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0); + break; + + case 3: + DPRT(("async_req seek head to track %d\n", ft->xblk / ftg->g_blktrk)); + ACMD_FUNC(4, ACMD_SEEK, QC_SEEKTRACK, 0, 0); + break; + + case 4: + cmd = (ft->xblk / ftg->g_blktrk) + 2; + if (ft->segh != NULL) { + ACMD_FUNC(5, ACMD_SEEKSTS, cmd, QS_READY, 0); + } else { + ACMD_FUNC(7, ACMD_SEEKSTS, cmd, QS_READY, 0); + } + break; + + case 5: + ft->moving = 1; + ACMD_FUNC(6, ACMD_SEEK, QC_FORWARD, 0, 0); + break; + + case 6: + arq_state = 1; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */ + break; + + case 7: + /* Time to rest. */ + ft->active = 0; + ft->lastpos = -2; + + /* wakeup those who want an i/o chg */ + wakeup((caddr_t)wc_iosts_change); + break; + } +} + + +/* + * Entry for async read. + */ +static void +async_read(ftu_t ftu, int from) +{ + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + int i, rddta[7]; + int where; + static int over_async; + static int retries = 0; + + if (from == 2) ard_state = over_async; + +restate: +#if FTDBGALL + DPRT(("async_read: state: %d from = %d\n", ard_state, from)); +#endif + switch (ard_state) { + case 0: /* Start off */ + /* If tape is not at desired position, stop and locate */ + if (ft->lastpos != (ft->xblk-1)) { + DPRT(("ft%d: position unknown: lastpos:%d ft->xblk:%d\n", + ftu, ft->lastpos, ft->xblk)); + ACMD_FUNC(1, ACMD_RUNBLK, ft->xblk, 0, 0); + } + + /* Tape is in position but stopped. */ + if (!ft->moving) { + DPRT(("async_read ******STARTING TAPE\n")); + ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0); + } + ard_state = 1; + goto restate; + + case 1: /* Start DMA */ + /* Tape is now moving and in position-- start DMA now! */ + isa_dmastart(B_READ, ft->xptr, QCV_BLKSIZE, 2); + out_fdc(fdcu, 0x66); /* read */ + out_fdc(fdcu, ftu); /* unit */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdside) / ftg->g_fdtrk); /* cylinder */ + out_fdc(fdcu, ft->xblk / ftg->g_fdside); /* head */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* sector */ + out_fdc(fdcu, 0x03); /* 1K sectors */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* count */ + out_fdc(fdcu, 0x74); /* gap length */ + out_fdc(fdcu, 0xff); /* transfer size */ + ard_state = 2; + break; + + case 2: /* DMA completed */ + /* Transfer complete, get status */ + for (i = 0; i < 7; i++) rddta[i] = in_fdc(fdcu); + isa_dmadone(B_READ, ft->xptr, QCV_BLKSIZE, 2); + +#if FTDBGALL + /* Compute where the controller thinks we are */ + where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside) + + rddta[5]-1; + DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n", + rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5], + where, ft->xblk)); +#endif + + /* Check for errors */ + if ((rddta[0] & 0xc0) != 0x00) { +#if !FTDBGALL + where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside) + + rddta[5]-1; + DPRT(("xd: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n", + rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5], + where, ft->xblk)); +#endif + if ((rddta[1] & 0x04) == 0x04 && retries < 2) { + /* Probably wrong position */ + DPRT(("async_read: doing retry %d\n", retries)); + ft->lastpos = ft->xblk; + ard_state = 0; + retries++; + goto restate; + } else { + /* CRC/Address-mark/Data-mark, et. al. */ + DPRT(("ft%d: CRC error on block %d\n", fdcu, ft->xblk)); + ft->segh->reqcrc |= (1 << ft->xcnt); + } + } + + /* Otherwise, transfer completed okay. */ + retries = 0; + ft->lastpos = ft->xblk; + ft->xblk++; + ft->xcnt++; + ft->xptr += QCV_BLKSIZE; + if (ft->xcnt < QCV_BLKSEG && ft->segh->reqcan == 0) { + ard_state = 0; + goto restate; + } + DPRT(("Read done.. Cancel = %d\n", ft->segh->reqcan)); + ft->io_sts = FTIO_READY; + break; + + case 3: + ft->moving = 1; + ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0); + break; + + case 4: + ard_state = 1; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */ + break; + + default: + DPRT(("ft%d: bad async_read state %d!!\n", ftu, ard_state)); + break; + } +} + + +/* + * Entry for async write. If from is 0, this came from the interrupt + * routine, if it's 1 then it was a timeout, if it's 2, then an + * async_cmd completed. + */ +static void +async_write(ftu_t ftu, int from) +{ + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + int i, rddta[7]; + int where; + static int over_async; + static int retries = 0; + + if (from == 2) awr_state = over_async; + +restate: +#if FTDBGALL + DPRT(("async_write: state: %d from = %d\n", awr_state, from)); +#endif + switch (awr_state) { + case 0: /* Start off */ + /* If tape is not at desired position, stop and locate */ + if (ft->lastpos != (ft->xblk-1)) { + DPRT(("ft%d: position unknown: lastpos:%d ft->xblk:%d\n", + ftu, ft->lastpos, ft->xblk)); + ACMD_FUNC(1, ACMD_RUNBLK, ft->xblk, 0, 0); + } + + /* Tape is in position but stopped. */ + if (!ft->moving) { + DPRT(("async_write ******STARTING TAPE\n")); + ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0); + } + awr_state = 1; + goto restate; + + case 1: /* Start DMA */ + /* Tape is now moving and in position-- start DMA now! */ + isa_dmastart(B_WRITE, ft->xptr, QCV_BLKSIZE, 2); + out_fdc(fdcu, 0x45); /* write */ + out_fdc(fdcu, ftu); /* unit */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdside) / ftg->g_fdtrk); /* cyl */ + out_fdc(fdcu, ft->xblk / ftg->g_fdside); /* head */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* sector */ + out_fdc(fdcu, 0x03); /* 1K sectors */ + out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1); /* count */ + out_fdc(fdcu, 0x74); /* gap length */ + out_fdc(fdcu, 0xff); /* transfer size */ + awr_state = 2; + break; + + case 2: /* DMA completed */ + /* Transfer complete, get status */ + for (i = 0; i < 7; i++) rddta[i] = in_fdc(fdcu); + isa_dmadone(B_WRITE, ft->xptr, QCV_BLKSIZE, 2); + +#if FTDBGALL + /* Compute where the controller thinks we are */ + where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside) + rddta[5]-1; + DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n", + rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5], + where, ft->xblk)); +#endif + + /* Check for errors */ + if ((rddta[0] & 0xc0) != 0x00) { +#if !FTDBGALL + where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside) + + rddta[5]-1; + DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n", + rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5], + where, ft->xblk)); +#endif + if (retries < 3) { + /* Something happened -- try again */ + DPRT(("async_write: doing retry %d\n", retries)); + ft->lastpos = ft->xblk; + awr_state = 0; + retries++; + goto restate; + } else { + /* + * Retries failed. Note the unrecoverable error. + * Marking the block as bad is useless right now. + */ + printf("ft%d: unrecoverable write error on block %d\n", + ftu, ft->xblk); + ft->segh->reqcrc |= (1 << ft->xcnt); + } + } + + /* Otherwise, transfer completed okay. */ + retries = 0; + ft->lastpos = ft->xblk; + ft->xblk++; + ft->xcnt++; + ft->xptr += QCV_BLKSIZE; + if (ft->xcnt < QCV_BLKSEG) { + awr_state = 0; /* next block */ + goto restate; + } +#if FTDBGALL + DPRT(("Write done.\n")); +#endif + ft->io_sts = FTIO_READY; + break; + + case 3: + ft->moving = 1; + ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0); + break; + + case 4: + awr_state = 1; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */ + break; + + default: + DPRT(("ft%d: bad async_write state %d!!\n", ftu, awr_state)); + break; + } +} + + +/* + * Interrupt handler for active tape. Bounced off of fdintr(). + */ +int +ftintr(ftu_t ftu) +{ + int st0, pcn, i; + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + int s = splbio(); + + st0 = 0; + pcn = 0; + + /* I/O segment transfer completed */ + if (ft->active) { + if (async_func != ACMD_NONE) { + async_cmd(ftu); + splx(s); + return(1); + } +#if FTDBGALL + DPRT(("Got request interrupt\n")); +#endif + async_req(ftu, 0); + splx(s); + return(1); + } + + /* Get interrupt status */ + if (ft->cmd_wait != FTCMD_READID) { + out_fdc(fdcu, NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + pcn = in_fdc(fdcu); + } + + if (ft->cmd_wait == FTCMD_NONE || ft->sts_wait != FTSTS_SNOOZE) { +huh_what: + printf("ft%d: unexpected interrupt; st0 = $%02x pcn = %d\n", + ftu, st0, pcn); + splx(s); + return(1); + } + + switch (ft->cmd_wait) { + case FTCMD_RESET: + ft->sts_wait = FTSTS_INTERRUPT; + wakeup((caddr_t)wc_intr_wait); + break; + case FTCMD_RECAL: + case FTCMD_SEEK: + if (st0 & 0x20) { /* seek done */ + ft->sts_wait = FTSTS_INTERRUPT; + ft->pcn = pcn; + wakeup((caddr_t)wc_intr_wait); + } +#if FTDBGALL + else + DPRT(("ft%d: seek error st0 = $%02x pcn = %d\n", + ftu, st0, pcn)); +#endif + break; + case FTCMD_READID: + for (i = 0; i < 7; i++) ft->rid[i] = in_fdc(fdcu); + ft->sts_wait = FTSTS_INTERRUPT; + wakeup((caddr_t)wc_intr_wait); + break; + + default: + goto huh_what; + } + + splx(s); + return(1); +} + + +/* + * Interrupt timeout routine. + */ +static void +ft_timeout(void *arg1) +{ + int s; + ftu_t ftu = (ftu_t)arg1; + ft_p ft = ft_data[ftu]; + + s = splbio(); + if (ft->active) { + if (async_func != ACMD_NONE) { + async_cmd(ftu); + splx(s); + return; + } + async_req(ftu, 1); + } else { + ft->sts_wait = FTSTS_TIMEOUT; + wakeup((caddr_t)wc_intr_wait); + } + splx(s); +} + + +/* + * Wait for a particular interrupt to occur. ftintr() will wake us up + * if it sees what we want. Otherwise, time out and return error. + * Should always disable ints before trigger is sent and calling here. + */ +static int +ftintr_wait(ftu_t ftu, int cmd, int ticks) +{ + int retries, st0, pcn; + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + ft->cmd_wait = cmd; + ft->sts_wait = FTSTS_SNOOZE; + + /* At attach time, we can't rely on having interrupts serviced */ + if (ft->attaching) { + switch (cmd) { + case FTCMD_RESET: + DELAY(100); + ft->sts_wait = FTSTS_INTERRUPT; + goto intrdone; + case FTCMD_RECAL: + case FTCMD_SEEK: + for (retries = 0; retries < 10000; retries++) { + DELAY(150); + out_fdc(fdcu, NE7CMD_SENSEI); + st0 = in_fdc(fdcu); + if ((st0 & 0xc0) == 0x80) continue; + pcn = in_fdc(fdcu); + if (st0 & 0x20) { + ft->sts_wait = FTSTS_INTERRUPT; + ft->pcn = pcn; + goto intrdone; + } + } + break; + } + ft->sts_wait = FTSTS_TIMEOUT; + goto intrdone; + } + + ftsleep(wc_intr_wait, ticks); + +intrdone: + if (ft->sts_wait == FTSTS_TIMEOUT) { /* timeout */ +#if FTDBGALL + if (ft->cmd_wait != FTCMD_RESET) + DPRT(("ft%d: timeout on command %d\n", ftu, ft->cmd_wait)); +#endif + ft->cmd_wait = FTCMD_NONE; + ft->sts_wait = FTSTS_NONE; + return(1); + } + + /* got interrupt */ + if (ft->attaching == 0 && ticks) + untimeout(ft_timeout, (caddr_t)ftu, ft->tohandle); + ft->cmd_wait = FTCMD_NONE; + ft->sts_wait = FTSTS_NONE; + return(0); +} + + +/* + * Recalibrate tape drive. Parameter totape is true, if we should + * recalibrate to tape drive settings. + */ +static int +tape_recal(ftu_t ftu, int totape) +{ + int s; + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + DPRT(("tape_recal start\n")); + +#ifdef PC98 + outb(0xbe, FDP_FDDEXC | FDP_PORTEXC); +#endif + out_fdc(fdcu, NE7CMD_SPECIFY); +#ifdef PC98 + out_fdc(fdcu, (totape) ? 0xEF : 0xCF); + out_fdc(fdcu, 0x02); +#else + out_fdc(fdcu, (totape) ? 0xAD : 0xDF); + out_fdc(fdcu, 0x02); +#endif + + s = splbio(); + out_fdc(fdcu, NE7CMD_RECAL); + out_fdc(fdcu, ftu); + + if (ftintr_wait(ftu, FTCMD_RECAL, hz)) { + splx(s); + DPRT(("ft%d: recalibrate timeout\n", ftu)); + return(1); + } + splx(s); + + out_fdc(fdcu, NE7CMD_SPECIFY); +#ifdef PC98 + out_fdc(fdcu, (totape) ? 0xEF : 0xCF); + out_fdc(fdcu, 0x02); +#else + out_fdc(fdcu, (totape) ? 0xFD : 0xDF); + out_fdc(fdcu, 0x02); +#endif + + DPRT(("tape_recal end\n")); + return(0); +} + +/* + * Wait for a particular tape status to be met. If all is TRUE, then + * all states must be met, otherwise any state can be met. + */ +static int +tape_state(ftu_t ftu, int all, int mask, int seconds) +{ + int r, tries, maxtries; + + maxtries = (seconds) ? (4 * seconds) : 1; + for (tries = 0; tries < maxtries; tries++) { + r = tape_status(ftu); + if (r >= 0) { + if (all && (r & mask) == mask) return(r); + if ((r & mask) != 0) return(r); + } + if (seconds) ftsleep(wc_long_delay, hz/4); + } + DPRT(("ft%d: tape_state failed on mask=$%02x maxtries=%d\n", + ftu, mask, maxtries)); + return(-1); +} + + +/* + * Send a QIC command to tape drive, wait for completion. + */ +static int +tape_cmd(ftu_t ftu, int cmd) +{ + int newcn; + int retries = 0; + int s; + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + DPRT(("===> tape_cmd: %d\n",cmd)); + newcn = (cmd <= ft->pcn) ? ft->pcn - cmd : ft->pcn + cmd; + +retry: + + /* Perform seek */ + s = splbio(); + out_fdc(fdcu, NE7CMD_SEEK); + out_fdc(fdcu, ftu); + out_fdc(fdcu, newcn); + + if (ftintr_wait(ftu, FTCMD_SEEK, hz)) { + DPRT(("ft%d: tape_cmd seek timeout\n", ftu)); +redo: + splx(s); + if (++retries < 5) goto retry; + DPRT(("ft%d: tape_cmd seek failed!\n", ftu)); + return(1); + } + splx(s); + + if (ft->pcn != newcn) { + DPRT(("ft%d: bad seek in tape_cmd; pcn = %d newcn = %d\n", + ftu, ft->pcn, newcn)); + goto redo; + } + DELAY(2500); + return(0); +} + + +/* + * Return status of tape drive + */ +static int +tape_status(ftu_t ftu) +{ + int r, err, tries; + ft_p ft = ft_data[ftu]; + int max = (ft->attaching) ? 2 : 3; + + for (r = -1, tries = 0; r < 0 && tries < max; tries++) + r = qic_status(ftu, QC_STATUS, 8); + if (tries == max) return(-1); + +recheck: + DPRT(("tape_status got $%04x\n",r)); + ft->laststs = r; + + if (r & (QS_ERROR|QS_NEWCART)) { + err = qic_status(ftu, QC_ERRCODE, 16); + ft->lasterr = err; + if (r & QS_NEWCART) { + ft->newcart = 1; + /* If tape not referenced, do a seek load point. */ + if ((r & QS_FMTOK) == 0 && !ft->attaching) { + tape_cmd(ftu, QC_SEEKLP); + do { + ftsleep(wc_long_delay, hz); + } while ((r = qic_status(ftu, QC_STATUS, 8)) < 0 || + (r & (QS_READY|QS_CART)) == QS_CART); + goto recheck; + } + } else if (err && !ft->attaching) { + DPRT(("ft%d: QIC error %d occurred on cmd %d\n", + ftu, err & 0xff, err >> 8)); + } + r = qic_status(ftu, QC_STATUS, 8); + ft->laststs = r; + DPRT(("tape_status got error code $%04x new sts = $%02x\n",err,r)); + } + + ft->rdonly = (r & QS_RDONLY); + return(r); +} + + +/* + * Transfer control to tape drive. + */ +static void +tape_start(ftu_t ftu, int motor) +{ + ft_p ft = ft_data[ftu]; + fdc_p fdc = ft->fdc; + int s, mbits; +#ifndef PC98 + static int mbmotor[] = { FDO_MOEN0, FDO_MOEN1, FDO_MOEN2, FDO_MOEN3 }; +#endif + + s = splbio(); + DPRT(("tape_start start\n")); + + /* reset, dma disable */ +#ifdef PC98 + outb(fdc->baseport+FDOUT, FDO_RST | FDO_FRY | FDO_AIE | FDO_MTON); +#else + outb(fdc->baseport+FDOUT, 0x00); +#endif + (void)ftintr_wait(ftu, FTCMD_RESET, hz/10); + + /* raise reset, enable DMA, motor on if needed */ +#ifdef PC98 + outb(fdc->baseport+FDOUT, FDO_DMAE | FDO_MTON); +#else + mbits = ftu & 3; + if (motor && ftu < 4) + mbits |= mbmotor[ftu]; + + outb(fdc->baseport+FDOUT, FDO_FRST | FDO_FDMAEN | mbits); +#endif + (void)ftintr_wait(ftu, FTCMD_RESET, hz/10); + + splx(s); + + tape_recal(ftu, 1); + + /* set transfer speed */ +#ifndef PC98 + outb(fdc->baseport+FDCTL, FDC_500KBPS); + DELAY(10); +#endif + + DPRT(("tape_start end\n")); +} + + +/* + * Transfer control back to floppy disks. + */ +static void +tape_end(ftu_t ftu) +{ + ft_p ft = ft_data[ftu]; + fdc_p fdc = ft->fdc; + int s; + + DPRT(("tape_end start\n")); + tape_recal(ftu, 0); + + s = splbio(); + + /* reset, dma disable */ +#ifdef PC98 + outb(fdc->baseport+FDOUT, FDO_RST | FDO_FRY | FDO_AIE | FDO_MTON); +#else + outb(fdc->baseport+FDOUT, 0x00); +#endif + (void)ftintr_wait(ftu, FTCMD_RESET, hz/10); + + /* raise reset, enable DMA */ +#ifdef PC98 + outb(fdc->baseport+FDOUT, FDO_DMAE | FDO_MTON); +#else + outb(fdc->baseport+FDOUT, FDO_FRST | FDO_FDMAEN); +#endif + (void)ftintr_wait(ftu, FTCMD_RESET, hz/10); + + splx(s); + + /* set transfer speed */ +#ifndef PC98 + outb(fdc->baseport+FDCTL, FDC_500KBPS); + DELAY(10); +#endif + fdc->flags &= ~FDC_TAPE_BUSY; + + DPRT(("tape_end end\n")); +} + + +/* + * Wait for the driver to go inactive, cancel readahead if necessary. + */ +static void +tape_inactive(ftu_t ftu) +{ + ft_p ft = ft_data[ftu]; + int s = splbio(); + + if (ft->segh != NULL) { + if (ft->segh->reqtype == FTIO_RDAHEAD) { + /* cancel read-ahead */ + ft->segh->reqcan = 1; + } else if (ft->segh->reqtype == FTIO_WRITING && !ft->active) { + /* flush out any remaining writes */ + DPRT(("Flushing write I/O chain\n")); + arq_state = ard_state = awr_state = 0; + ft->xblk = ft->segh->reqblk; + ft->xseg = ft->segh->reqseg; + ft->xcnt = 0; + ft->xptr = ft->segh->buff; + ft->active = 1; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, 1); + } + } + while (ft->active) ftsleep(wc_iosts_change, 0); + splx(s); +} + + +/* + * Get the geometry of the tape currently in the drive. + */ +static int +ftgetgeom(ftu_t ftu) +{ + int r, i, tries; + int cfg, qic80, ext; + int sts, fmt, len; + ft_p ft = ft_data[ftu]; + + r = tape_status(ftu); + + /* XXX fix me when format mode is finished */ + if (r < 0 || (r & QS_CART) == 0 || (r & QS_FMTOK) == 0) { + DPRT(("ftgetgeom: no cart or not formatted 0x%04x\n",r)); + ftg = NULL; + ft->newcart = 1; + return(0); + } + + /* Report drive configuration */ + for (cfg = -1, tries = 0; cfg < 0 && tries < 3; tries++) + cfg = qic_status(ftu, QC_CONFIG, 8); + if (tries == 3) { + DPRT(("ftgetgeom report config failed\n")); + ftg = NULL; + return(-1); + } + DPRT(("ftgetgeom report config got $%04x\n", cfg)); + ft->lastcfg = cfg; + + qic80 = cfg & QCF_QIC80; + ext = cfg & QCF_EXTRA; + +/* + * XXX - This doesn't seem to work on my Colorado Jumbo 250... + * if it works on your drive, I'd sure like to hear about it. + */ +#if 0 + /* Report drive status */ + for (sts = -1, tries = 0; sts < 0 && tries < 3; tries++) + sts = qic_status(ftu, QC_TSTATUS, 8); + if (tries == 3) { + DPRT(("ftgetgeom report tape status failed\n")); + ftg = NULL; + return(-1); + } + DPRT(("ftgetgeom report tape status got $%04x\n", sts)); +#else + /* + * XXX - Forge a fake tape status based upon the returned + * configuration, since the above command or code is broken + * for my drive and probably other older drives. + */ + sts = 0; + sts = (qic80) ? QTS_QIC80 : QTS_QIC40; + sts |= (ext) ? QTS_LEN2 : QTS_LEN1; +#endif + + fmt = sts & QTS_FMMASK; + len = (sts & QTS_LNMASK) >> 4; + + if (fmt > QCV_NFMT) { + ftg = NULL; + printf("ft%d: unsupported tape format\n", ftu); + return(-1); + } + if (len > QCV_NLEN) { + ftg = NULL; + printf("ft%d: unsupported tape length\n", ftu); + return(-1); + } + + /* Look up geometry in the table */ + for (i = 1; i < NGEOM; i++) + if (ftgtbl[i].g_fmtno == fmt && ftgtbl[i].g_lenno == len) break; + if (i == NGEOM) { + printf("ft%d: unknown tape geometry\n", ftu); + ftg = NULL; + return(-1); + } + ftg = &ftgtbl[i]; + if (!ftg->g_trktape) { + printf("ft%d: unsupported format %s w/len %s\n", + ftu, ftg->g_fmtdesc, ftg->g_lendesc); + ftg = NULL; + return(-1); + } + DPRT(("Tape format is %s, length is %s\n", ftg->g_fmtdesc, ftg->g_lendesc)); + ft->newcart = 0; + return(0); +} + + +/* + * Switch between tape/floppy. This will send the tape enable/disable + * codes for this drive's manufacturer. + */ +static int +set_fdcmode(dev_t dev, int newmode) +{ + ftu_t ftu = FDUNIT(minor(dev)); + ft_p ft = ft_data[ftu]; + fdc_p fdc = ft->fdc; + static int havebufs = 0; + int i; + SegReq *sp, *rsp; + + if (newmode == FDC_TAPE_MODE) { + /* Wake up the tape drive */ + switch (ft->type) { + case NO_TYPE: + fdc->flags &= ~FDC_TAPE_BUSY; + return(ENXIO); + case FT_NONE: + tape_start(ftu, 0); + break; + case FT_COLORADO: + tape_start(ftu, 0); + if (tape_cmd(ftu, QC_COL_ENABLE1)) { + tape_end(ftu); + return(EIO); + } + if (tape_cmd(ftu, QC_COL_ENABLE2 + ftu)) { + tape_end(ftu); + return(EIO); + } + break; + case FT_MOUNTAIN: + tape_start(ftu, 0); + if (tape_cmd(ftu, QC_MTN_ENABLE1)) { + tape_end(ftu); + return(EIO); + } + if (tape_cmd(ftu, QC_MTN_ENABLE2)) { + tape_end(ftu); + return(EIO); + } + break; + case FT_INSIGHT: + tape_start(ftu, 1); + break; + default: + DPRT(("ft%d: bad tape type\n", ftu)); + return(ENXIO); + } + if (tape_status(ftu) < 0) { + if (ft->type == FT_COLORADO) + tape_cmd(ftu, QC_COL_DISABLE); + else if (ft->type == FT_MOUNTAIN) + tape_cmd(ftu, QC_MTN_DISABLE); + tape_end(ftu); + return(EIO); + } + + /* Grab buffers from memory. */ + if (!havebufs) { + ft->segh = ft->segt = NULL; + ft->doneh = ft->donet = NULL; + ft->segfree = NULL; + ft->hdr = NULL; + ft->nsegq = ft->ndoneq = ft->nfreelist = 0; + for (i = 0; i < FTNBUFF; i++) { + sp = malloc(sizeof(SegReq), M_DEVBUF, M_WAITOK); + if (sp == NULL) { + printf("ft%d: not enough memory for buffers\n", ftu); + for (sp=ft->segfree; sp != NULL; sp=sp->next) + free(sp, M_DEVBUF); + if (ft->type == FT_COLORADO) + tape_cmd(ftu, QC_COL_DISABLE); + else if (ft->type == FT_MOUNTAIN) + tape_cmd(ftu, QC_MTN_DISABLE); + tape_end(ftu); + return(ENOMEM); + } + sp->reqtype = FTIO_READY; + sp->next = ft->segfree; + ft->segfree = sp; + ft->nfreelist++; + } + /* take one buffer for header */ + ft->hdr = ft->segfree; + ft->segfree = ft->segfree->next; + ft->nfreelist--; + havebufs = 1; + } + ft->io_sts = FTIO_READY; /* tape drive is ready */ + ft->active = 0; /* interrupt driver not active */ + ft->moving = 0; /* tape not moving */ + ft->rdonly = 0; /* tape read only */ + ft->newcart = 0; /* new cartridge flag */ + ft->lastpos = -1; /* tape is rewound */ + async_func = ACMD_NONE; /* No async function */ + tape_state(ftu, 0, QS_READY, 60); + tape_cmd(ftu, QC_RATE); + tape_cmd(ftu, QCF_RT500+2); /* 500K bps */ + tape_state(ftu, 0, QS_READY, 60); + ft->mode = FTM_PRIMARY; + tape_cmd(ftu, QC_PRIMARY); /* Make sure we're in primary mode */ + tape_state(ftu, 0, QS_READY, 60); + ftg = NULL; /* No geometry yet */ + ftgetgeom(ftu); /* Get tape geometry */ + ftreq_rewind(ftu); /* Make sure tape is rewound */ + } else { + if (ft->type == FT_COLORADO) + tape_cmd(ftu, QC_COL_DISABLE); + else if (ft->type == FT_MOUNTAIN) + tape_cmd(ftu, QC_MTN_DISABLE); + tape_end(ftu); + ft->newcart = 0; /* clear new cartridge */ + if (ft->hdr != NULL) free(ft->hdr, M_DEVBUF); + if (havebufs) { + for (sp = ft->segfree; sp != NULL;) { + rsp = sp; sp = sp->next; + free(rsp, M_DEVBUF); + } + for (sp = ft->segh; sp != NULL;) { + rsp = sp; sp = sp->next; + free(rsp, M_DEVBUF); + } + for (sp = ft->doneh; sp != NULL;) { + rsp = sp; sp = sp->next; + free(rsp, M_DEVBUF); + } + } + havebufs = 0; + } + return(0); +} + + +/* + * Perform a QIC status function. + */ +static int +qic_status(ftu_t ftu, int cmd, int nbits) +{ + int st3, r, i; + ft_p ft = ft_data[ftu]; + fdcu_t fdcu = ft->fdc->fdcu; /* fdc active unit */ + + if (tape_cmd(ftu, cmd)) { + DPRT(("ft%d: QIC status timeout\n", ftu)); + return(-1); + } + + /* Sense drive status */ + out_fdc(fdcu, NE7CMD_SENSED); + out_fdc(fdcu, ftu); + st3 = in_fdc(fdcu); + + if ((st3 & 0x10) == 0) { /* track 0 */ + DPRT(("qic_status has dead drive... st3 = $%02x\n", st3)); + return(-1); + } + + for (i = r = 0; i <= nbits; i++) { + if (tape_cmd(ftu, QC_NEXTBIT)) { + DPRT(("ft%d: QIC status bit timed out on %d\n", ftu, i)); + return(-1); + } + + out_fdc(fdcu, NE7CMD_SENSED); + out_fdc(fdcu, ftu); + st3 = in_fdc(fdcu); + if (st3 < 0) { + DPRT(("ft%d: controller timed out on bit %d r=$%02x\n", + ftu, i, r)); + return(-1); + } + + r >>= 1; + if (i < nbits) + r |= ((st3 & 0x10) ? 1 : 0) << nbits; + else if ((st3 & 0x10) == 0) { + DPRT(("ft%d: qic status stop bit missing at %d, st3=$%02x r=$%04x\n", + ftu,i,st3,r)); + return(-1); + } + } + + DPRT(("qic_status returned $%02x\n", r)); + return(r); +} + + +/* + * Open tape drive for use. Bounced off of Fdopen if tape minor is + * detected. + */ +int +ftopen(dev_t dev, int arg2) { + ftu_t ftu = FDUNIT(minor(dev)); + fdc_p fdc; + + /* check bounds */ + if (ftu >= NFT) + return(ENXIO); + if (!ft_data[ftu]) + return(ENXIO); + fdc = ft_data[ftu]->fdc; + if ((fdc == NULL) || (ft_data[ftu]->type == NO_TYPE)) + return(ENXIO); + /* check for controller already busy with tape */ + if (fdc->flags & FDC_TAPE_BUSY) + return(EBUSY); + /* make sure we found a tape when probed */ + if (!(fdc->flags & FDC_HASFTAPE)) + return(ENODEV); + fdc->fdu = ftu; + fdc->flags |= FDC_TAPE_BUSY; + return(set_fdcmode(dev, FDC_TAPE_MODE)); /* try to switch to tape */ +} + + +/* + * Close tape and return floppy controller to disk mode. + */ +int +ftclose(dev_t dev, int flags) +{ + ftu_t ftu = FDUNIT(minor(dev)); + ft_p ft = ft_data[ftu]; + + + /* Wait for any remaining I/O activity to complete. */ + tape_inactive(ftu); + + ft->mode = FTM_PRIMARY; + tape_cmd(ftu, QC_PRIMARY); + tape_state(ftu, 0, QS_READY, 60); + ftreq_rewind(ftu); + return(set_fdcmode(dev, FDC_DISK_MODE)); /* Otherwise, close tape */ +} + +/* + * Read or write a segment. + */ +static int +ftreq_rw(ftu_t ftu, unsigned long cmd, QIC_Segment *sr, struct proc *p) +{ + int r, i; + SegReq *sp; + int s; + long blk, bad, seg; + unsigned char *cp, *cp2; + ft_p ft = ft_data[ftu]; + + if (!ft->active && ft->segh == NULL) { + r = tape_status(ftu); + if ((r & QS_CART) == 0) + return(ENXIO); /* No cartridge */ + if ((r & QS_FMTOK) == 0) + return(ENXIO); /* Not formatted */ + tape_state(ftu, 0, QS_READY, 90); + } + + if (ftg == NULL || ft->newcart) { + tape_inactive(ftu); + tape_state(ftu, 0, QS_READY, 90); + if (ftgetgeom(ftu) < 0) + return(ENXIO); + } + + /* Write not allowed on a read-only tape. */ + if (cmd == QIOWRITE && ft->rdonly) + return(EROFS); + + /* Quick check of request and buffer. */ + if (sr == NULL || sr->sg_data == NULL) + return(EINVAL); + + /* Make sure requested track and segment is in range. */ + if (sr->sg_trk >= ftg->g_trktape || sr->sg_seg >= ftg->g_segtrk) + return(EINVAL); + + blk = sr->sg_trk * ftg->g_blktrk + sr->sg_seg * QCV_BLKSEG; + seg = sr->sg_trk * ftg->g_segtrk + sr->sg_seg; + + s = splbio(); + if (cmd == QIOREAD) { + /* + * See if the driver is reading ahead. + */ + if (ft->doneh != NULL || + (ft->segh != NULL && ft->segh->reqtype == FTIO_RDAHEAD)) { + /* + * Eat the completion queue and see if the request + * is already there. + */ + while (ft->doneh != NULL) { + if (blk == ft->doneh->reqblk) { + sp = ft->doneh; + sp->reqtype = FTIO_READING; + sp->reqbad = sr->sg_badmap; + goto rddone; + } + segio_free(ft, ft->doneh); + } + + /* + * Not on the completed queue, in progress maybe? + */ + if (ft->segh != NULL && ft->segh->reqtype == FTIO_RDAHEAD && + blk == ft->segh->reqblk) { + sp = ft->segh; + sp->reqtype = FTIO_READING; + sp->reqbad = sr->sg_badmap; + goto rdwait; + } + } + + /* Wait until we're ready. */ + tape_inactive(ftu); + + /* Set up a new read request. */ + sp = segio_alloc(ft); + sp->reqcrc = 0; + sp->reqbad = sr->sg_badmap; + sp->reqblk = blk; + sp->reqseg = seg; + sp->reqcan = 0; + sp->reqtype = FTIO_READING; + segio_queue(ft, sp); + + /* Start the read request off. */ + DPRT(("Starting read I/O chain\n")); + arq_state = ard_state = awr_state = 0; + ft->xblk = sp->reqblk; + ft->xseg = sp->reqseg; + ft->xcnt = 0; + ft->xptr = sp->buff; + ft->active = 1; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, 1); + +rdwait: + ftsleep(wc_buff_done, 0); + +rddone: + bad = sp->reqbad; + sr->sg_crcmap = sp->reqcrc & ~bad; + + /* Copy out segment and discard bad mapped blocks. */ + cp = sp->buff; cp2 = sr->sg_data; + for (i = 0; i < QCV_BLKSEG; cp += QCV_BLKSIZE, i++) { + if (bad & (1 << i)) continue; + copyout(cp, cp2, QCV_BLKSIZE); + cp2 += QCV_BLKSIZE; + } + segio_free(ft, sp); + } else { + if (ft->segh != NULL && ft->segh->reqtype != FTIO_WRITING) + tape_inactive(ftu); + + /* Allocate a buffer and start tape if we're running low. */ + sp = segio_alloc(ft); + if (!ft->active && (sp == NULL || ft->nfreelist <= 1)) { + DPRT(("Starting write I/O chain\n")); + arq_state = ard_state = awr_state = 0; + ft->xblk = ft->segh->reqblk; + ft->xseg = ft->segh->reqseg; + ft->xcnt = 0; + ft->xptr = ft->segh->buff; + ft->active = 1; + ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, 1); + } + + /* Sleep until a buffer becomes available. */ + while (sp == NULL) { + ftsleep(wc_buff_avail, 0); + sp = segio_alloc(ft); + } + + /* Copy in segment and expand bad blocks. */ + bad = sr->sg_badmap; + cp = sr->sg_data; cp2 = sp->buff; + for (i = 0; i < QCV_BLKSEG; cp2 += QCV_BLKSIZE, i++) { + if (bad & (1 << i)) continue; + copyin(cp, cp2, QCV_BLKSIZE); + cp += QCV_BLKSIZE; + } + sp->reqblk = blk; + sp->reqseg = seg; + sp->reqcan = 0; + sp->reqtype = FTIO_WRITING; + segio_queue(ft, sp); + } + splx(s); + return(0); +} + + +/* + * Rewind to beginning of tape + */ +static int +ftreq_rewind(ftu_t ftu) +{ + ft_p ft = ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + tape_cmd(ftu, QC_SEEKSTART); + tape_state(ftu, 0, QS_READY, 90); + tape_cmd(ftu, QC_SEEKTRACK); + tape_cmd(ftu, 2); + tape_state(ftu, 0, QS_READY, 90); + ft->lastpos = -1; + ft->moving = 0; + return(0); +} + + +/* + * Move to logical beginning or end of track + */ +static int +ftreq_trkpos(ftu_t ftu, int req) +{ + int curtrk, r, cmd; + ft_p ft = ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + + r = tape_status(ftu); + if ((r & QS_CART) == 0) return(ENXIO); /* No cartridge */ + if ((r & QS_FMTOK) == 0) return(ENXIO); /* Not formatted */ + + if (ftg == NULL || ft->newcart) { + if (ftgetgeom(ftu) < 0) return(ENXIO); + } + + curtrk = (ft->lastpos < 0) ? 0 : ft->lastpos / ftg->g_blktrk; + if (req == QIOBOT) + cmd = (curtrk & 1) ? QC_SEEKEND : QC_SEEKSTART; + else + cmd = (curtrk & 1) ? QC_SEEKSTART : QC_SEEKEND; + tape_cmd(ftu, cmd); + tape_state(ftu, 0, QS_READY, 90); + return(0); +} + + +/* + * Seek tape head to a particular track. + */ +static int +ftreq_trkset(ftu_t ftu, int *trk) +{ + int r; + ft_p ft = ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + + r = tape_status(ftu); + if ((r & QS_CART) == 0) return(ENXIO); /* No cartridge */ + if ((r & QS_FMTOK) == 0) return(ENXIO); /* Not formatted */ + if (ftg == NULL || ft->newcart) { + if (ftgetgeom(ftu) < 0) return(ENXIO); + } + + tape_cmd(ftu, QC_SEEKTRACK); + tape_cmd(ftu, *trk + 2); + tape_state(ftu, 0, QS_READY, 90); + return(0); +} + + +/* + * Start tape moving forward. + */ +static int +ftreq_lfwd(ftu_t ftu) +{ + ft_p ft = ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + tape_cmd(ftu, QC_FORWARD); + ft->moving = 1; + return(0); +} + + +/* + * Stop the tape + */ +static int +ftreq_stop(ftu_t ftu) +{ + ft_p ft = ft_data[ftu]; + + tape_inactive(ftu); + tape_cmd(ftu, QC_STOP); + tape_state(ftu, 0, QS_READY, 90); + ft->moving = 0; + return(0); +} + + +/* + * Set the particular mode the drive should be in. + */ +static int +ftreq_setmode(ftu_t ftu, int cmd) +{ + int r; + ft_p ft = ft_data[ftu]; + + tape_inactive(ftu); + r = tape_status(ftu); + + switch(cmd) { + case QIOPRIMARY: + ft->mode = FTM_PRIMARY; + tape_cmd(ftu, QC_PRIMARY); + break; + case QIOFORMAT: + if (r & QS_RDONLY) return(ENXIO); + if ((r & QS_BOT) == 0) return(ENXIO); + tape_cmd(ftu, QC_FORMAT); + break; + case QIOVERIFY: + if ((r & QS_FMTOK) == 0) return(ENXIO); /* Not formatted */ + tape_cmd(ftu, QC_VERIFY); + break; + } + tape_state(ftu, 0, QS_READY, 60); + return(0); +} + + +/* + * Return drive status bits + */ +static int +ftreq_status(ftu_t ftu, unsigned long cmd, int *sts, struct proc *p) +{ + ft_p ft = ft_data[ftu]; + + if (ft->active) + *sts = ft->laststs & ~QS_READY; + else + *sts = tape_status(ftu); + return(0); +} + + +/* + * Return drive configuration bits + */ +static int +ftreq_config(ftu_t ftu, unsigned long cmd, int *cfg, struct proc *p) +{ + int r, tries; + ft_p ft = ft_data[ftu]; + + if (ft->active) + r = ft->lastcfg; + else { + for (r = -1, tries = 0; r < 0 && tries < 3; tries++) + r = qic_status(ftu, QC_CONFIG, 8); + if (tries == 3) return(ENXIO); + } + *cfg = r; + return(0); +} + + +/* + * Return current tape's geometry. + */ +static int +ftreq_geom(ftu_t ftu, QIC_Geom *g) +{ + tape_inactive(ftu); + if (ftg == NULL && ftgetgeom(ftu) < 0) return(ENXIO); + bcopy(ftg, g, sizeof(QIC_Geom)); + return(0); +} + + +/* + * Return drive hardware information + */ +static int +ftreq_hwinfo(ftu_t ftu, QIC_HWInfo *hwp) +{ + int tries; + int rom, vend; + + tape_inactive(ftu); + bzero(hwp, sizeof(QIC_HWInfo)); + + for (rom = -1, tries = 0; rom < 0 && tries < 3; tries++) + rom = qic_status(ftu, QC_VERSION, 8); + if (rom > 0) { + hwp->hw_rombeta = (rom >> 7) & 0x01; + hwp->hw_romid = rom & 0x7f; + } + + for (vend = -1, tries = 0; vend < 0 && tries < 3; tries++) + vend = qic_status(ftu, QC_VENDORID, 16); + if (vend > 0) { + hwp->hw_make = (vend >> 6) & 0x3ff; + hwp->hw_model = vend & 0x3f; + } + + return(0); +} + + +/* + * Receive or Send the in-core header segment. + */ +static int +ftreq_hdr(ftu_t ftu, unsigned long cmd, QIC_Segment *sp) +{ + ft_p ft = ft_data[ftu]; + QIC_Header *h = (QIC_Header *)ft->hdr->buff; + + if (sp == NULL || sp->sg_data == NULL) return(EINVAL); + if (cmd == QIOSENDHDR) { + copyin(sp->sg_data, ft->hdr->buff, QCV_SEGSIZE); + } else { + if (h->qh_sig != QCV_HDRMAGIC) return(EIO); + copyout(ft->hdr->buff, sp->sg_data, QCV_SEGSIZE); + } + return(0); +} + +/* + * I/O functions. + */ +int +ftioctl(dev_t dev, unsigned long cmd, caddr_t data, int flag, struct proc *p) +{ + ftu_t ftu = FDUNIT(minor(dev)); + + switch(cmd) { + case QIOREAD: /* Request reading a segment from tape. */ + case QIOWRITE: /* Request writing a segment to tape. */ + return(ftreq_rw(ftu, cmd, (QIC_Segment *)data, p)); + + case QIOREWIND: /* Rewind tape. */ + return(ftreq_rewind(ftu)); + + case QIOBOT: /* Seek to logical beginning of track. */ + case QIOEOT: /* Seek to logical end of track. */ + return(ftreq_trkpos(ftu, cmd)); + + case QIOTRACK: /* Seek tape head to specified track. */ + return(ftreq_trkset(ftu, (int *)data)); + + case QIOSEEKLP: /* Seek load point. */ + goto badreq; + + case QIOFORWARD: /* Move tape in logical forward direction. */ + return(ftreq_lfwd(ftu)); + + case QIOSTOP: /* Causes tape to stop. */ + return(ftreq_stop(ftu)); + + case QIOPRIMARY: /* Enter primary mode. */ + case QIOFORMAT: /* Enter format mode. */ + case QIOVERIFY: /* Enter verify mode. */ + return(ftreq_setmode(ftu, cmd)); + + case QIOWRREF: /* Write reference burst. */ + goto badreq; + + case QIOSTATUS: /* Get drive status. */ + return(ftreq_status(ftu, cmd, (int *)data, p)); + + case QIOCONFIG: /* Get tape configuration. */ + return(ftreq_config(ftu, cmd, (int *)data, p)); + + case QIOGEOM: + return(ftreq_geom(ftu, (QIC_Geom *)data)); + + case QIOHWINFO: + return(ftreq_hwinfo(ftu, (QIC_HWInfo *)data)); + + case QIOSENDHDR: + case QIORECVHDR: + return(ftreq_hdr(ftu, cmd, (QIC_Segment *)data)); + } +badreq: + DPRT(("ft%d: unknown ioctl(%#lx) request\n", ftu, cmd)); + return(ENXIO); +} + +#endif |