* 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.4 1994/02/14 22:24:28 nate Exp $
* 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
* 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' it's
* driver routine vectors.
* 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
* 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.
* 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
* 06/03/93 v0.1 Alpha release
* Hopefully the last re-write. Many bugs fixed, many remain.
#include "i386/isa/isa_device.h"
#include "i386/isa/fdreg.h"
#include "i386/isa/fdc.h"
#include "i386/isa/icu.h"
#include "i386/isa/rtc.h"
/* Enable or disable debugging messages. */
#define FTDBGALL 0 /* 1 if you want everything */
/*#define DPRT(a) printf 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 */
extern int hz
; /* system clock rate */
/* 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
};
FTIO_READY
, /* No I/O activity */
FTIO_READING
, /* Currently reading blocks */
FTIO_RDAHEAD
, /* Currently reading ahead */
FTIO_WRITING
/* Buffers are being written */
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 */
{ 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))
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
*astk_ptr
= &astk
[0]; /* Pointer to stack position */
/* List of valid async (interrupt driven) tape support functions. */
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); \
/* 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); \
/* 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. *
\***********************************************************************/
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 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 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 */
/***********************************************************************\
* 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) *
\***********************************************************************/
typedef struct ft_data
*ft_p
;
#define id_physid id_scsiid /* this biotab field doubles as a field */
/* for the physical unit number on the controller */
void ftstrategy(struct buf
*);
int ftioctl(dev_t
, int, caddr_t
, int, struct proc
*);
static void ft_timeout(caddr_t
, int);
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.
/* Grab first item from free list */
if ((r
= ft
->segfree
) != NULL
) {
ft
->segfree
= ft
->segfree
->next
;
DPRT(("segio_alloc: nfree=%d ndone=%d nreq=%d\n", ft
->nfreelist
, ft
->ndoneq
, ft
->nsegq
));
* Queue a segment I/O request.
segio_queue(ft_p ft
, SegReq
*sp
)
/* Put request on in process queue. */
DPRT(("segio_queue: nfree=%d ndone=%d nreq=%d\n", ft
->nfreelist
, ft
->ndoneq
, ft
->nsegq
));
* Segment I/O completed, place on correct queue.
segio_done(ft_p ft
, SegReq
*sp
)
/* First remove from current I/O queue */
if (ft
->segh
== NULL
) ft
->segt
= NULL
;
if (sp
->reqtype
== FTIO_WRITING
) {
wakeup((caddr_t
)wc_buff_avail
);
DPRT(("segio_done: (w) nfree=%d ndone=%d nreq=%d\n", ft
->nfreelist
, ft
->ndoneq
, ft
->nsegq
));
/* Put on completed I/O queue */
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.
segio_free(ft_p ft
, SegReq
*sp
)
/* First remove from done queue */
if (ft
->doneh
== NULL
) ft
->donet
= NULL
;
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.
struct isa_device
*isadev
, *fdup
;
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
;
ftsu_t ftsu
= fdup
->id_physid
;
if (ftu
>= NFT
) return 0;
* FT_NONE - no method, just do it
if (tape_status(ftu
) >= 0) {
* FT_COLORADO - colorado style
tape_cmd(ftu
, QC_COL_ENABLE1
);
tape_cmd(ftu
, QC_COL_ENABLE2
+ ftu
);
if (tape_status(ftu
) >= 0) {
tape_cmd(ftu
, QC_COL_DISABLE
);
* FT_MOUNTAIN - mountain style
tape_cmd(ftu
, QC_MTN_ENABLE1
);
tape_cmd(ftu
, QC_MTN_ENABLE2
);
if (tape_status(ftu
) >= 0) {
tape_cmd(ftu
, QC_MTN_DISABLE
);
* FT_INSIGHT - insight style
if (tape_status(ftu
) >= 0) {
if (ft
->type
!= NO_TYPE
) {
fdc
->flags
|= FDC_HASFTAPE
;
if (ft
->type
== FT_COLORADO
)
else if (ft
->type
== FT_INSIGHT
)
else if (ft
->type
== FT_MOUNTAIN
&& hw
.hw_model
== 0x05)
else if (ft
->type
== FT_MOUNTAIN
)
printf(" [%d: ft%d: %s tape]", fdup
->id_physid
, fdup
->id_unit
, manu
);
* Perform common commands asynchronously.
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 curpos
, curtrk
, curblk
, curdir
, curdiff
;
DPRT(("async_cmd state: func: %d state: %d\n", async_func
, async_state
));
DPRT(("===>async_seek cmd = %d\n", cmd
));
newcn
= (cmd
<= ft
->pcn
) ? ft
->pcn
- cmd
: ft
->pcn
+ cmd
;
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 (++async_retries
>= 10) {
DPRT(("ft%d: async_cmd command seek failed!!\n", ftu
));
DPRT(("ft%d: async_cmd command seek retry...\n",ftu
));
out_fdc(fdcu
, NE7CMD_SENSEI
);
if (st0
< 0 || pcn
< 0 || newcn
!= pcn
) {
if (++async_retries
>= 10) {
DPRT(("ft%d: async_cmd seek retries exceeded\n",ftu
));
DPRT(("ft%d: async_cmd command bad st0=$%02x pcn=$%02x\n",
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/10);
if (st0
& 0x20) { /* seek done */
DPRT(("ft%d: async_seek error st0 = $%02x pcn = %d\n",
if (async_arg1
) goto complete
;
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/50);
* 0 - command to issue report from
* modifies: bitn, retval, st3
DPRT(("async_status got cmd = %d nbits = %d\n", cmd
,nbits
));
CALL_ACMD(5, ACMD_SEEK
, QC_NEXTBIT
, 0, 0);
out_fdc(fdcu
, NE7CMD_SENSED
);
DPRT(("ft%d: async_status timed out on bit %d r=$%02x\n",
if ((st3
& 0x10) != 0) retval
|= (1 << bitn
);
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
))) {
DPRT(("async status got $%04x ($%04x)\n", async_ret
,retval
));
DPRT(("ft%d: async_status failed: retval=$%04x nbits=%d\n",
CALL_ACMD(1, ACMD_SEEK
, QC_NEXTBIT
, 0, 0);
if (async_ret
& QS_NEWCART
) ft
->newcart
= 1;
CALL_ACMD(3, ACMD_STATUS
, QC_ERRCODE
, 16, 1);
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));
CALL_ACMD(4, ACMD_STATUS
, QC_STATUS
, 8, 1);
CALL_ACMD(6, ACMD_SEEK
, QC_NEXTBIT
, 0, 0);
CALL_ACMD(7, ACMD_SEEK
, QC_NEXTBIT
, 0, 0);
CALL_ACMD(8, ACMD_SEEK
, QC_NEXTBIT
, 0, 0);
CALL_ACMD(1, ACMD_SEEK
, cmd
, 0, 0);
* 0 - status bits to check
CALL_ACMD(1, ACMD_STATUS
, QC_STATUS
, 8, 0);
if ((async_ret
& async_arg0
) != 0) goto complete
;
if (++async_retries
== 360) { /* 90 secs. */
DPRT(("ft%d: acmd_state exceeded retry count\n", ftu
));
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/4);
* 1 - status bits to check
* 2 - (optional) seconds to wait until completion
async_retries
= (async_arg2
) ? (async_arg2
* 4) : 10;
CALL_ACMD(1, ACMD_SEEK
, cmd
, 0, 0);
CALL_ACMD(2, ACMD_STATUS
, QC_STATUS
, 8, 0);
if ((async_ret
& async_arg1
) != 0) goto complete
;
if (--async_retries
== 0) {
DPRT(("ft%d: acmd_seeksts retries exceeded\n", ftu
));
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/4);
CALL_ACMD(4, ACMD_SEEKSTS
, QC_STOP
, QS_READY
, 0);
out_fdc(fdcu
, 0x4a); /* READ_ID */
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) {
* errcnt == 1 regular retry
* 4 microstep head back to 0
DPRT(("ft%d: acmd_readid errcnt exceeded\n", fdcu
));
CALL_ACMD(4, ACMD_SEEKSTS
, QC_STOP
, QS_READY
, 0);
CALL_ACMD(4, ACMD_SEEKSTS
, QC_STPAUSE
, QS_READY
, 0);
DPRT(("readid retry %d...\n", errcnt
));
if ((async_ret
% ftg
->g_blktrk
) == (ftg
->g_blktrk
-1)) {
DPRT(("acmd_readid detected last block on track\n"));
CALL_ACMD(2, ACMD_STATE
, QS_BOT
|QS_EOT
, 0, 0);
CALL_ACMD(3, ACMD_STATE
, QS_READY
, 0, 0);
CALL_ACMD(5, ACMD_SEEK
, QC_FORWARD
, 0, 0);
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/10); /* XXX */
* 0 - block number I/O will be performed on
wanttrk
= async_arg0
/ ftg
->g_blktrk
;
wantblk
= async_arg0
% ftg
->g_blktrk
;
CALL_ACMD(1, ACMD_SEEKSTS
, QC_STOP
, QS_READY
, 0);
DPRT(("Changing to track %d\n", wanttrk
));
CALL_ACMD(2, ACMD_SEEK
, QC_SEEKTRACK
, 0, 0);
CALL_ACMD(3, ACMD_SEEKSTS
, cmd
, QS_READY
, 0);
CALL_ACMD(4, ACMD_STATUS
, QC_STATUS
, 8, 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;
if (ft
->laststs
& QS_EOT
) {
DPRT(("Tape is at EOT\n"));
curblk
= (wantdir
) ? 0 : 4800;
CALL_ACMD(5, ACMD_READID
, 0, 0, 0);
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);
DPRT(("curtrk = %d nextblk = %d\n", curtrk
, curblk
));
ft
->lastpos
= curblk
- 1;
if (ft
->moving
) goto complete
;
CALL_ACMD(7, ACMD_STATE
, QS_READY
, 0, 0);
if (curblk
> wantblk
) { /* passed it */
CALL_ACMD(10, ACMD_SEEKSTS
, QC_STOP
, QS_READY
, 0);
if ((wantblk
- curblk
) <= 256) { /* approaching it */
CALL_ACMD(5, ACMD_READID
, 0, 0, 0);
CALL_ACMD(14, ACMD_SEEKSTS
, QC_STOP
, QS_READY
, 0);
CALL_ACMD(8, ACMD_SEEK
, QC_FORWARD
, 0, 0);
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/10); /* XXX */
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);
DPRT(("reverse 1 done\n"));
CALL_ACMD(12, ACMD_SEEK
, (curdiff
& 0xf)+2, 0, 0);
DPRT(("reverse 2 done\n"));
CALL_ACMD(13, ACMD_SEEKSTS
, ((curdiff
>>4)&0xf)+2, QS_READY
, 90);
CALL_ACMD(5, ACMD_READID
, 0, 0, 0);
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);
DPRT(("forward 1 done\n"));
CALL_ACMD(16, ACMD_SEEK
, (curdiff
& 0xf)+2, 0, 0);
DPRT(("forward 2 done\n"));
CALL_ACMD(13, ACMD_SEEKSTS
, ((curdiff
>>4)&0xf)+2, QS_READY
, 90);
if (astk_ptr
!= &astk
[0]) {
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
;
DPRT(("ft%d: bad async_cmd ending I/O state!\n", ftu
));
* Entry point for the async request processor.
async_req(ftu_t ftu
, int from
)
static int over_async
, lastreq
, domore
;
if (from
== 2) arq_state
= over_async
;
case 0: /* Process segment */
ft
->io_sts
= (sp
== NULL
) ? FTIO_READY
: sp
->reqtype
;
if (ft
->io_sts
== FTIO_WRITING
)
if (ft
->io_sts
!= FTIO_READY
) return;
/* Pull buffer from current I/O queue */
/* If I/O cancelled, clear finished queue. */
while (ft
->doneh
!= NULL
)
segio_free(ft
, ft
->doneh
);
/* Detect end of track */
if (((ft
->xblk
/ QCV_BLKSEG
) % ftg
->g_segtrk
) == 0) {
ACMD_FUNC(2, ACMD_STATE
, QS_BOT
|QS_EOT
, 0, 0);
case 1: /* Next request */
/* If we have another request queued, start it running. */
arq_state
= ard_state
= awr_state
= 0;
DPRT(("I/O reqblk = %d\n", ft
->xblk
));
/* 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
;
bzero(sp
->buff
, QCV_SEGSIZE
);
arq_state
= ard_state
= awr_state
= 0;
DPRT(("Processing readahead reqblk = %d\n", ft
->xblk
));
DPRT(("No more I/O.. Stopping.\n"));
ACMD_FUNC(7, ACMD_SEEKSTS
, QC_PAUSE
, QS_READY
, 0);
case 2: /* End of track */
ACMD_FUNC(3, ACMD_STATE
, QS_READY
, 0, 0);
DPRT(("async_req seek head to track %d\n", ft
->xblk
/ ftg
->g_blktrk
));
ACMD_FUNC(4, ACMD_SEEK
, QC_SEEKTRACK
, 0, 0);
cmd
= (ft
->xblk
/ ftg
->g_blktrk
) + 2;
ACMD_FUNC(5, ACMD_SEEKSTS
, cmd
, QS_READY
, 0);
ACMD_FUNC(7, ACMD_SEEKSTS
, cmd
, QS_READY
, 0);
ACMD_FUNC(6, ACMD_SEEK
, QC_FORWARD
, 0, 0);
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/10); /* XXX */
/* wakeup those who want an i/o chg */
wakeup((caddr_t
)wc_iosts_change
);
async_read(ftu_t ftu
, int from
)
fdcu_t fdcu
= ft
->fdc
->fdcu
; /* fdc active unit */
int i
, cmd
, newcn
, rddta
[7];
if (from
== 2) ard_state
= over_async
;
DPRT(("async_read: state: %d from = %d\n", ard_state
, from
));
/* 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. */
DPRT(("async_read ******STARTING TAPE\n"));
ACMD_FUNC(3, ACMD_STATE
, QS_READY
, 0, 0);
/* 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 */
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);
/* Compute where the controller thinks we are */
where
= (rddta
[3]*ftg
->g_fdtrk
) + (rddta
[4]*ftg
->g_fdside
)
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],
if ((rddta
[0] & 0xc0) != 0x00) {
where
= (rddta
[3]*ftg
->g_fdtrk
) + (rddta
[4]*ftg
->g_fdside
)
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],
if ((rddta
[1] & 0x04) == 0x04 && retries
< 2) {
/* Probably wrong position */
DPRT(("async_read: doing retry %d\n", retries
));
/* 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. */
if (ft
->xcnt
< QCV_BLKSEG
&& ft
->segh
->reqcan
== 0) {
DPRT(("Read done.. Cancel = %d\n", ft
->segh
->reqcan
));
ACMD_FUNC(4, ACMD_SEEK
, QC_FORWARD
, 0, 0);
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/10); /* XXX */
DPRT(("ft%d: bad async_read state %d!!\n", ftu
, ard_state
));
* 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_write(ftu_t ftu
, int from
)
fdcu_t fdcu
= ft
->fdc
->fdcu
; /* fdc active unit */
int i
, cmd
, newcn
, rddta
[7];
if (from
== 2) awr_state
= over_async
;
DPRT(("async_write: state: %d from = %d\n", awr_state
, from
));
/* 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. */
DPRT(("async_write ******STARTING TAPE\n"));
ACMD_FUNC(3, ACMD_STATE
, QS_READY
, 0, 0);
/* 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 */
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);
/* 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],
if ((rddta
[0] & 0xc0) != 0x00) {
where
= (rddta
[3]*ftg
->g_fdtrk
) + (rddta
[4]*ftg
->g_fdside
)
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],
/* Something happened -- try again */
DPRT(("async_write: doing retry %d\n", retries
));
* 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",
ft
->segh
->reqcrc
|= (1 << ft
->xcnt
);
/* Otherwise, transfer completed okay. */
if (ft
->xcnt
< QCV_BLKSEG
) {
awr_state
= 0; /* next block */
ACMD_FUNC(4, ACMD_SEEK
, QC_FORWARD
, 0, 0);
timeout(ft_timeout
, (caddr_t
)ftu
, hz
/10); /* XXX */
DPRT(("ft%d: bad async_write state %d!!\n", ftu
, awr_state
));
* Interrupt handler for active tape. Bounced off of fdintr().
fdcu_t fdcu
= ft
->fdc
->fdcu
; /* fdc active unit */
/* I/O segment transfer completed */
if (async_func
!= ACMD_NONE
) {
DPRT(("Got request interrupt\n"));
/* Get interrupt status */
if (ft
->cmd_wait
!= FTCMD_READID
) {
out_fdc(fdcu
, NE7CMD_SENSEI
);
if (ft
->cmd_wait
== FTCMD_NONE
|| ft
->sts_wait
!= FTSTS_SNOOZE
) {
printf("ft%d: unexpected interrupt; st0 = $%02x pcn = %d\n",
ft
->sts_wait
= FTSTS_INTERRUPT
;
wakeup((caddr_t
)wc_intr_wait
);
if (st0
& 0x20) { /* seek done */
ft
->sts_wait
= FTSTS_INTERRUPT
;
wakeup((caddr_t
)wc_intr_wait
);
DPRT(("ft%d: seek error st0 = $%02x pcn = %d\n",
for (i
= 0; i
< 7; i
++) ft
->rid
[i
] = in_fdc(fdcu
);
ft
->sts_wait
= FTSTS_INTERRUPT
;
wakeup((caddr_t
)wc_intr_wait
);
* Interrupt timeout routine.
ft_timeout(caddr_t arg1
, int arg2
)
if (async_func
!= ACMD_NONE
) {
ft
->sts_wait
= FTSTS_TIMEOUT
;
wakeup((caddr_t
)wc_intr_wait
);
* 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.
ftintr_wait(ftu_t ftu
, int cmd
, int ticks
)
fdcu_t fdcu
= ft
->fdc
->fdcu
; /* fdc active unit */
ft
->sts_wait
= FTSTS_SNOOZE
;
/* At attach time, we can't rely on having interrupts serviced */
ft
->sts_wait
= FTSTS_INTERRUPT
;
for (retries
= 0; retries
< 10000; retries
++) {
out_fdc(fdcu
, NE7CMD_SENSEI
);
ft
->sts_wait
= FTSTS_INTERRUPT
;
ft
->sts_wait
= FTSTS_TIMEOUT
;
ftsleep(wc_intr_wait
, ticks
);
if (ft
->sts_wait
== FTSTS_TIMEOUT
) { /* timeout */
if (ft
->cmd_wait
!= FTCMD_RESET
)
DPRT(("ft%d: timeout on command %d\n", ftu
, ft
->cmd_wait
));
ft
->cmd_wait
= FTCMD_NONE
;
ft
->sts_wait
= FTSTS_NONE
;
if (ft
->attaching
== 0 && ticks
) untimeout(ft_timeout
, (caddr_t
)ftu
);
ft
->cmd_wait
= FTCMD_NONE
;
ft
->sts_wait
= FTSTS_NONE
;
* Recalibrate tape drive. Parameter totape is true, if we should
* recalibrate to tape drive settings.
tape_recal(ftu_t ftu
, int totape
)
fdcu_t fdcu
= ft
->fdc
->fdcu
; /* fdc active unit */
DPRT(("tape_recal start\n"));
out_fdc(fdcu
, NE7CMD_SPECIFY
);
out_fdc(fdcu
, (totape
) ? 0xAD : 0xDF);
out_fdc(fdcu
, NE7CMD_RECAL
);
if (ftintr_wait(ftu
, FTCMD_RECAL
, hz
)) {
DPRT(("ft%d: recalibrate timeout\n", ftu
));
out_fdc(fdcu
, NE7CMD_SPECIFY
);
out_fdc(fdcu
, (totape
) ? 0xFD : 0xDF);
DPRT(("tape_recal end\n"));
* Timeout for long delays.
state_timeout(caddr_t arg1
, int arg2
)
wakeup((caddr_t
)wc_long_delay
);
* 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.
tape_state(ftu_t ftu
, int all
, int mask
, int seconds
)
maxtries
= (seconds
) ? (4 * seconds
) : 1;
for (tries
= 0; tries
< maxtries
; tries
++) {
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",
* Send a QIC command to tape drive, wait for completion.
tape_cmd(ftu_t ftu
, int cmd
)
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
;
out_fdc(fdcu
, NE7CMD_SEEK
);
if (ftintr_wait(ftu
, FTCMD_SEEK
, hz
)) {
DPRT(("ft%d: tape_cmd seek timeout\n", ftu
));
if (++retries
< 5) goto retry
;
DPRT(("ft%d: tape_cmd seek failed!\n", ftu
));
DPRT(("ft%d: bad seek in tape_cmd; pcn = %d newcn = %d\n",
* Return status of tape drive
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);
DPRT(("tape_status got $%04x\n",r
));
if (r
& (QS_ERROR
|QS_NEWCART
)) {
err
= qic_status(ftu
, QC_ERRCODE
, 16);
/* If tape not referenced, do a seek load point. */
if ((r
& QS_FMTOK
) == 0 && !ft
->attaching
) {
tape_cmd(ftu
, QC_SEEKLP
);
ftsleep(wc_long_delay
, hz
);
} while ((r
= qic_status(ftu
, QC_STATUS
, 8)) < 0 ||
(r
& (QS_READY
|QS_CART
)) == QS_CART
);
} 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);
DPRT(("tape_status got error code $%04x new sts = $%02x\n",err
,r
));
ft
->rdonly
= (r
& QS_RDONLY
);
* Transfer control to tape drive.
tape_start(ftu_t ftu
, int motor
)
DPRT(("tape_start start\n"));
outb(fdc
->baseport
+FDOUT
, 0x00);
(void)ftintr_wait(ftu
, FTCMD_RESET
, hz
/10);
/* raise reset, enable DMA, motor on if needed */
mbits
= (!ftu
) ? FDO_MOEN0
: FDO_MOEN1
;
outb(fdc
->baseport
+FDOUT
, FDO_FRST
| FDO_FDMAEN
| mbits
);
(void)ftintr_wait(ftu
, FTCMD_RESET
, hz
/10);
outb(fdc
->baseport
+FDCTL
, FDC_500KBPS
);
DPRT(("tape_start end\n"));
* Transfer control back to floppy disks.
DPRT(("tape_end start\n"));
outb(fdc
->baseport
+FDOUT
, 0x00);
(void)ftintr_wait(ftu
, FTCMD_RESET
, hz
/10);
/* raise reset, enable DMA */
outb(fdc
->baseport
+FDOUT
, FDO_FRST
| FDO_FDMAEN
);
(void)ftintr_wait(ftu
, FTCMD_RESET
, hz
/10);
outb(fdc
->baseport
+FDCTL
, FDC_500KBPS
);
fdc
->flags
&= ~FDC_TAPE_BUSY
;
DPRT(("tape_end end\n"));
* Wait for the driver to go inactive, cancel readahead if necessary.
if (ft
->segh
->reqtype
== FTIO_RDAHEAD
) {
} 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
->xptr
= ft
->segh
->buff
;
timeout(ft_timeout
, (caddr_t
)ftu
, 1);
while (ft
->active
) ftsleep(wc_iosts_change
, 0);
* Get the geometry of the tape currently in the drive.
/* 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
));
/* Report drive configuration */
for (cfg
= -1, tries
= 0; cfg
< 0 && tries
< 3; tries
++)
cfg
= qic_status(ftu
, QC_CONFIG
, 8);
DPRT(("ftgetgeom report config failed\n"));
DPRT(("ftgetgeom report config got $%04x\n", cfg
));
* 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.
/* Report drive status */
for (sts
= -1, tries
= 0; sts
< 0 && tries
< 3; tries
++)
sts
= qic_status(ftu
, QC_TSTATUS
, 8);
DPRT(("ftgetgeom report tape status failed\n"));
DPRT(("ftgetgeom report tape status got $%04x\n", sts
));
* 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
= (qic80
) ? QTS_QIC80
: QTS_QIC40
;
sts
|= (ext
) ? QTS_LEN2
: QTS_LEN1
;
len
= (sts
& QTS_LNMASK
) >> 4;
printf("ft%d: unsupported tape format\n", ftu
);
printf("ft%d: unsupported tape length\n", ftu
);
/* Look up geometry in the table */
for (i
= 1; i
< NGEOM
; i
++)
if (ftgtbl
[i
].g_fmtno
== fmt
&& ftgtbl
[i
].g_lenno
== len
) break;
printf("ft%d: unknown tape geometry\n", ftu
);
printf("ft%d: unsupported format %s w/len %s\n",
ftu
, ftg
->g_fmtdesc
, ftg
->g_lendesc
);
DPRT(("Tape format is %s, length is %s\n", ftg
->g_fmtdesc
, ftg
->g_lendesc
));
* Switch between tape/floppy. This will send the tape enable/disable
* codes for this drive's manufacturer.
set_fdcmode(dev_t dev
, int newmode
)
ftu_t ftu
= FDUNIT(minor(dev
));
if (newmode
== FDC_TAPE_MODE
) {
/* Wake up the tape drive */
fdc
->flags
&= ~FDC_TAPE_BUSY
;
if (tape_cmd(ftu
, QC_COL_ENABLE1
)) {
if (tape_cmd(ftu
, QC_COL_ENABLE2
+ ftu
)) {
if (tape_cmd(ftu
, QC_MTN_ENABLE1
)) {
if (tape_cmd(ftu
, QC_MTN_ENABLE2
)) {
DPRT(("ft%d: bad tape type\n", ftu
));
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
);
/* Grab buffers from memory. */
ft
->segh
= ft
->segt
= NULL
;
ft
->doneh
= ft
->donet
= NULL
;
ft
->nsegq
= ft
->ndoneq
= ft
->nfreelist
= 0;
for (i
= 0; i
< FTNBUFF
; i
++) {
sp
= malloc(sizeof(SegReq
), M_DEVBUF
, M_WAITOK
);
printf("ft%d: not enough memory for buffers\n", ftu
);
for (sp
=ft
->segfree
; sp
!= NULL
; sp
=sp
->next
)
if (ft
->type
== FT_COLORADO
)
tape_cmd(ftu
, QC_COL_DISABLE
);
else if (ft
->type
== FT_MOUNTAIN
)
tape_cmd(ftu
, QC_MTN_DISABLE
);
sp
->reqtype
= FTIO_READY
;
/* take one buffer for header */
ft
->segfree
= ft
->segfree
->next
;
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
, QCF_RT500
+2); /* 500K bps */
tape_state(ftu
, 0, QS_READY
, 60);
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 */
if (ft
->type
== FT_COLORADO
)
tape_cmd(ftu
, QC_COL_DISABLE
);
else if (ft
->type
== FT_MOUNTAIN
)
tape_cmd(ftu
, QC_MTN_DISABLE
);
ft
->newcart
= 0; /* clear new cartridge */
if (ft
->hdr
!= NULL
) free(ft
->hdr
, M_DEVBUF
);
for (sp
= ft
->segfree
; sp
!= NULL
;) {
for (sp
= ft
->segh
; sp
!= NULL
;) {
for (sp
= ft
->doneh
; sp
!= NULL
;) {
* Perform a QIC status function.
qic_status(ftu_t ftu
, int cmd
, int nbits
)
fdcu_t fdcu
= ft
->fdc
->fdcu
; /* fdc active unit */
if (tape_cmd(ftu
, cmd
)) {
DPRT(("ft%d: QIC status timeout\n", ftu
));
out_fdc(fdcu
, NE7CMD_SENSED
);
if ((st3
& 0x10) == 0) { /* track 0 */
DPRT(("qic_status has dead drive... st3 = $%02x\n", st3
));
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
));
out_fdc(fdcu
, NE7CMD_SENSED
);
DPRT(("ft%d: controller timed out on bit %d r=$%02x\n",
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",
DPRT(("qic_status returned $%02x\n", r
));
* Open tape drive for use. Bounced off of Fdopen if tape minor is
ftopen(dev_t dev
, int arg2
) {
ftu_t ftu
= FDUNIT(minor(dev
));
int type
= FDTYPE(minor(dev
));
/* check for controller already busy with tape */
if (fdc
->flags
& FDC_TAPE_BUSY
)
/* make sure we found a tape when probed */
if (!(fdc
->flags
& FDC_HASFTAPE
))
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.
ftclose(dev_t dev
, int flags
)
ftu_t ftu
= FDUNIT(minor(dev
));
/* Wait for any remaining I/O activity to complete. */
tape_cmd(ftu
, QC_PRIMARY
);
tape_state(ftu
, 0, QS_READY
, 60);
return(set_fdcmode(dev
, FDC_DISK_MODE
)); /* Otherwise, close tape */
* Perform strategy on a given buffer (not!). Changed so that the
* driver will at least return 'Operation not supported'.
ftstrategy(struct buf
*bp
)
* Read or write a segment.
ftreq_rw(ftu_t ftu
, int cmd
, QIC_Segment
*sr
, struct proc
*p
)
if (!ft
->active
&& ft
->segh
== NULL
) {
return(ENXIO
); /* No cartridge */
return(ENXIO
); /* Not formatted */
tape_state(ftu
, 0, QS_READY
, 90);
if (ftg
== NULL
|| ft
->newcart
) {
tape_state(ftu
, 0, QS_READY
, 90);
/* Write not allowed on a read-only tape. */
if (cmd
== QIOWRITE
&& ft
->rdonly
)
/* Quick check of request and buffer. */
if (sr
== NULL
|| sr
->sg_data
== NULL
)
/* Make sure requested track and segment is in range. */
if (sr
->sg_trk
>= ftg
->g_trktape
|| sr
->sg_seg
>= ftg
->g_segtrk
)
blk
= sr
->sg_trk
* ftg
->g_blktrk
+ sr
->sg_seg
* QCV_BLKSEG
;
seg
= sr
->sg_trk
* ftg
->g_segtrk
+ sr
->sg_seg
;
* See if the driver is reading ahead.
(ft
->segh
!= NULL
&& ft
->segh
->reqtype
== FTIO_RDAHEAD
)) {
* Eat the completion queue and see if the request
while (ft
->doneh
!= NULL
) {
if (blk
== ft
->doneh
->reqblk
) {
sp
->reqtype
= FTIO_READING
;
sp
->reqbad
= sr
->sg_badmap
;
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
->reqtype
= FTIO_READING
;
sp
->reqbad
= sr
->sg_badmap
;
/* Wait until we're ready. */
/* Set up a new read request. */
sp
->reqbad
= sr
->sg_badmap
;
sp
->reqtype
= FTIO_READING
;
/* Start the read request off. */
DPRT(("Starting read I/O chain\n"));
arq_state
= ard_state
= awr_state
= 0;
timeout(ft_timeout
, (caddr_t
)ftu
, 1);
ftsleep(wc_buff_done
, 0);
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
);
if (ft
->segh
!= NULL
&& ft
->segh
->reqtype
!= FTIO_WRITING
)
/* Allocate a buffer and start tape if we're running low. */
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
->xptr
= ft
->segh
->buff
;
timeout(ft_timeout
, (caddr_t
)ftu
, 1);
/* Sleep until a buffer becomes available. */
ftsleep(wc_buff_avail
, 0);
/* Copy in segment and expand bad blocks. */
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
);
sp
->reqtype
= FTIO_WRITING
;
* Rewind to beginning of tape
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_state(ftu
, 0, QS_READY
, 90);
* Move to logical beginning or end of track
ftreq_trkpos(ftu_t ftu
, int req
)
tape_state(ftu
, 0, QS_READY
, 90);
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
;
cmd
= (curtrk
& 1) ? QC_SEEKEND
: QC_SEEKSTART
;
cmd
= (curtrk
& 1) ? QC_SEEKSTART
: QC_SEEKEND
;
tape_state(ftu
, 0, QS_READY
, 90);
* Seek tape head to a particular track.
ftreq_trkset(ftu_t ftu
, int *trk
)
tape_state(ftu
, 0, QS_READY
, 90);
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_state(ftu
, 0, QS_READY
, 90);
* Start tape moving forward.
tape_state(ftu
, 0, QS_READY
, 90);
tape_cmd(ftu
, QC_FORWARD
);
tape_state(ftu
, 0, QS_READY
, 90);
* Set the particular mode the drive should be in.
ftreq_setmode(ftu_t ftu
, int cmd
)
tape_cmd(ftu
, QC_PRIMARY
);
if (r
& QS_RDONLY
) return(ENXIO
);
if ((r
& QS_BOT
) == 0) return(ENXIO
);
tape_cmd(ftu
, QC_FORMAT
);
if ((r
& QS_FMTOK
) == 0) return(ENXIO
); /* Not formatted */
tape_cmd(ftu
, QC_VERIFY
);
tape_state(ftu
, 0, QS_READY
, 60);
* Return drive status bits
ftreq_status(ftu_t ftu
, int cmd
, int *sts
, struct proc
*p
)
*sts
= ft
->laststs
& ~QS_READY
;
* Return drive configuration bits
ftreq_config(ftu_t ftu
, int cmd
, int *cfg
, struct proc
*p
)
for (r
= -1, tries
= 0; r
< 0 && tries
< 3; tries
++)
r
= qic_status(ftu
, QC_CONFIG
, 8);
if (tries
== 3) return(ENXIO
);
* Return current tape's geometry.
ftreq_geom(ftu_t ftu
, QIC_Geom
*g
)
if (ftg
== NULL
&& ftgetgeom(ftu
) < 0) return(ENXIO
);
bcopy(ftg
, g
, sizeof(QIC_Geom
));
* Return drive hardware information
ftreq_hwinfo(ftu_t ftu
, QIC_HWInfo
*hwp
)
bzero(hwp
, sizeof(QIC_HWInfo
));
for (rom
= -1, tries
= 0; rom
< 0 && tries
< 3; tries
++)
rom
= qic_status(ftu
, QC_VERSION
, 8);
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);
hwp
->hw_make
= (vend
>> 6) & 0x3ff;
hwp
->hw_model
= vend
& 0x3f;
* Receive or Send the in-core header segment.
ftreq_hdr(ftu_t ftu
, int cmd
, QIC_Segment
*sp
)
QIC_Header
*h
= (QIC_Header
*)ft
->hdr
->buff
;
if (sp
== NULL
|| sp
->sg_data
== NULL
) return(EINVAL
);
copyin(sp
->sg_data
, ft
->hdr
->buff
, QCV_SEGSIZE
);
if (h
->qh_sig
!= QCV_HDRMAGIC
) return(EIO
);
copyout(ft
->hdr
->buff
, sp
->sg_data
, QCV_SEGSIZE
);
ftioctl(dev_t dev
, int cmd
, caddr_t data
, int flag
, struct proc
*p
)
ftu_t ftu
= FDUNIT(minor(dev
));
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. */
case QIOFORWARD
: /* Move tape in logical forward direction. */
case QIOSTOP
: /* Causes tape to stop. */
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. */
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
));
return(ftreq_geom(ftu
, (QIC_Geom
*)data
));
return(ftreq_hwinfo(ftu
, (QIC_HWInfo
*)data
));
return(ftreq_hdr(ftu
, cmd
, (QIC_Segment
*)data
));
DPRT(("ft%d: unknown ioctl(%d) request\n", ftu
, cmd
));