Coherent4.2.10/conf/ft/src/ftx.c

/*
 * ftx.c
 *
 * Manage i/o requests from tape driver to FDC.
 */

/*
 * ----------------------------------------------------------------------
 * Includes.
 */
#include <sys/coherent.h>
#include <sys/cmn_err.h>
#include <sys/dmac.h>
#include <sys/fdc765.h>

#include <coh/misc.h>

#include <sys/ft.h>
#include <sys/ftx.h>
#include <sys/ftxreq.h>

#if FT_TRACE
extern long __bolt;
#endif

/* Globals. */

extern int fdcTblk;	/* For FDC error messages - tape block in last cmd. */

/* Data Structures and State Variables. */

__VOLATILE__ struct Ftx ftx;

/* Buffer area for read/write command that will be sent to FDC. */

unsigned char fdcRWCmd[] = {
	0x46,		/* FDC read, skip deleted data, MFM.	*/
	0,		/* Head 0, unit 0.			*/
	0,		/* C = FDC cylinder			*/
	0,		/* H = FDC head				*/
	1,		/* R = FDC block number (1-based)	*/
	3,		/* N = block length (3 = 1024 bytes)	*/
	128,		/* EOT = last block number on cylinder	*/
	109,		/* GPL = number of bytes in gap 3	*/
	255		/* DTL, use 0xFF when N != 0		*/
};

/* Read/write command bytes - first element of fdcRWCmd. */
enum {
	FDC_CMD_RD = 0x46,
	FDC_CMD_WR = 0x45
};

/* Offsets into rwCmd. */
enum {
	RW_CMD_VERB	= 0,
	RW_CMD_UNIT	= 1,
	RW_CMD_CYL	= 2,
	RW_CMD_HEAD	= 3,
	RW_CMD_SCTR	= 4,
	RW_CMD_SCSZ	= 5,	/* encoded block length		*/
	RW_CMD_SCHI	= 6,	/* high block number		*/
	RW_CMD_GPL	= 7,
	RW_CMD_DTL	= 8,
	RW_CMD_LEN	= 9
};

/************************************************************************
 * ftxSetDrive
 *
 * Enter FDC unit number into FDC write command.
 ***********************************************************************/
#if	__USE_PROTO__
void ftxSetDrive (unsigned char drive)
#else
void
ftxSetDrive (drive)
unsigned char drive;
#endif
{
	fdcRWCmd[RW_CMD_UNIT] = drive;
}

/************************************************************************
 * ftxC2chk
 *
 * Called by busyWait to check whether IRQ routine has finished.
 * Return nonzero when there is no IRQ pending for block transfer.
 ***********************************************************************/
#if	__USE_PROTO__
static int ftxC2chk (void)
#else
static int
ftxC2chk ()
#endif
{
	if (ftx.ftx_IRQpending == 0)
		return 1;
	else
		return 0;
}

/************************************************************************
 * ftxSendCmd
 *
 * Take request records off queue, processing skips, until a transfer
 * request is encountered, then send that to the FDC.
 * Called from interrupt level *and* from normal priority.
 ***********************************************************************/
#if	__USE_PROTO__
static void ftxSendCmd (void)
#else
static void
ftxSendCmd ()
#endif
{
	int		s;
	struct FtxReq	* reqp;

	s = sphi ();

	for (;;) {

		/* Try to fetch a request from the queue. */
		reqp = ftxReqGet ();

		/* Queue was empty. */
		if (reqp == NULL)
			break;

		/* If block was mapped out by BBT, skip it. */
		if (reqp->ftr_type == FTXR_SKIP) {
			ftx.ftx_bytesXferred += FT_BLK_SIZE;

#if defined(FAST_CPU) && defined(FT_TRACE)
cmn_err (CE_CONT, "!G block %d, addr %x %d ",
  reqp->ftr_blockno, reqp->ftr_dmaPaddr, __bolt);
#endif

		/* Otherwise, block is for real I/O. */
		} else if (reqp->ftr_type == FTXR_XFER) {


#if defined(FAST_CPU) && defined(FT_TRACE)
cmn_err (CE_CONT, "!G block %d, addr %x, H=%d C=%d R=%d %d ",
  reqp->ftr_blockno, reqp->ftr_dmaPaddr,
  reqp->ftrH, reqp->ftrC, reqp->ftrR, __bolt);
#endif

			/* Store floppy pseudocoordinates into command string. */

			fdcRWCmd[RW_CMD_SCTR] = reqp->ftrR;
			fdcRWCmd[RW_CMD_CYL] = reqp->ftrC;
			fdcRWCmd[RW_CMD_HEAD] = reqp->ftrH;

			/* Turn on DMA. */
			if (!dmaon(DMA_CH2, reqp->ftr_dmaPaddr, FT_BLK_SIZE,
			  ftx.ftx_dmaDir)) {
#if FT_TRACE
				cmn_err (CE_WARN, "!FT DMA straddle ");
#endif
				break;
			}
			dmago(DMA_CH2);
			ftx.ftx_DMA_on = 1;

			/* Tell FDC IRQ handler, let us know when it's over. */
			ftx.ftx_IRQpending = 1;

			/* Send Read or Write command to FDC */
			fdcTblk = reqp->ftr_blockno;
			fdcPutStr(fdcRWCmd, RW_CMD_LEN);

			/* Save block number;  will log it if we get there. */
			ftx.ftx_blockno = reqp->ftr_blockno;

			break;

#if FT_TRACE
		/* Invalid request! */
		} else {
			cmn_err (CE_WARN,
			  "ftxSendCmd : Invalid command type %d",
			  reqp->ftr_type);
#endif
		}
	}

	spl (s);
}

/************************************************************************
 * ftXferFDC
 *
 * Given starting logical block number, number of blocks, and the
 * physical address of a virtually contiguous buffer region, attempt to
 * read/write between tape and buffer.  Argument "bbtSw" says whether to
 * use the bad block bitmap.
 * Blocks marked bad are skipped over.
 * Argument "iodir" is FT_READ for read, FT_WRITE for write.
 *
 * Tape motion is started at the beginning of the xfer and continues
 * until all data requested has been transferred or skipped over, or a
 * read or write fails; then motion is stopped.
 *
 * Return number of bytes read/written.
 ***********************************************************************/
#if __USE_PROTO__
int ftXferFDC (int logBlock, int numBlocks,
  caddr_t ioBuf, struct ftDiskLocn * dlp, int bbtSw, int ioDir, int retry)
#else
int
ftXferFDC (logBlock, numBlocks, ioBuf, dlp, bbtSw, ioDir, retry)
int logBlock;
int numBlocks;
caddr_t ioBuf;
struct ftDiskLocn * dlp;
int bbtSw;
int ioDir;
int retry;
#endif
{
	char	* dirname;	/* ASCII name of I/O direction, for debug. */
	int	blocksToBeDone;
	int	tapeStarted = 0;
	int	myblock = logBlock;

	__caddr_t	bufVaddr;

	/* Very little is different for read vs. write. Here's the diff. */
	switch (ioDir) {
	case FT_READ:
		ftx.ftx_dmaDir = DMA_TO_MEM;
		fdcRWCmd [RW_CMD_VERB] = FDC_CMD_RD;
		dirname = "read";
		break;
	case FT_WRITE:
		ftx.ftx_dmaDir = DMA_FROM_MEM;
		fdcRWCmd [RW_CMD_VERB] = FDC_CMD_WR;
		dirname = "write";
		break;
	default:
#if FT_TRACE
		cmn_err (CE_WARN, "FT : Invalid ioDir = %d", ioDir);
#endif
		goto endXferFDC;
	}

#if FT_TRACE
cmn_err (CE_NOTE, "!ftXferFDC (%d, %d, %x, %x, %d, %s, %d)",
  myblock, numBlocks, (int) ioBuf, (int) dlp, bbtSw, dirname, retry);
#endif

	/* Return now if no work to be done. */

	if (numBlocks <= 0)
		goto endXferFDC;

	/*
	 * Control loop for single-block transfer requests.
	 * The tape is in motion while this loop executes.
	 *
	 * "Producer" makes requests to transfer or skip a block, and puts
	 * them onto a queue so the interrupt handler can fetch them.
	 *
	 * "Consumer 1" starts the chain of i/o ops that run off the request
	 * queue, and watches the state of the interrupt level routine.
	 *
	 * "Consumer 2" runs from the interrupt handler, fetches the next
	 * request from the queue, and updates the transfer count.
	 */
	ftx.ftx_DMA_on = 0;
	ftx.ftx_IRQpending = 0;
	ftx.ftx_bytesXferred = 0;
	ftx.ftx_error = 0;
	ftx.ftx_residual = 0;
	ftx.ftx_stopio = 0;
	ftx.ftx_timedOut = 0;
	ftx.ftx_blockno = 0;
	ftxQinit ();

	blocksToBeDone = numBlocks;
	bufVaddr = ioBuf;

	for (;;) {

		int waitStat;
		int busyWaitStat;

		/* Queue as many requests as there is room for. */
		while (blocksToBeDone > 0) {

			int		logSeg;
			int		offset;
			unsigned int	bitMap;
			struct FtxReq	request;
			struct FtxReq	* reqp = & request;
			__paddr_t	tmpPaddr;

			/* Generate virt address for current block. */
			if (ftBufVtop (bufVaddr, & tmpPaddr)) {

				/* Failure.  Stop queueing requests. */
				blocksToBeDone = 0;
				break;
			}

			/* See if request queue is full. */
			if (ftxReqFreeSize () == 0)
				break;

			reqp->ftr_dmaPaddr = tmpPaddr;
			reqp->ftr_blockno = myblock;

			logSeg = myblock / FT_BLK_PER_SEG;
			offset = myblock % FT_BLK_PER_SEG;

			/* Is the current block good? */

			if (bbtSw == FT_USE_BBT && ftBBT (logSeg, & bitMap) == 0
			 && (bitMap & (1 << offset))) {

				/* Bad block. */
				reqp->ftr_type = FTXR_SKIP;

#if FT_TRACE
cmn_err (CE_CONT, "!P block %d, addr %x %d\n",
  reqp->ftr_blockno, reqp->ftr_dmaPaddr, __bolt);
#endif

			} else {

				/* Good block.  Do real i/o. */
				reqp->ftr_type = FTXR_XFER;

				/* Store floppy pseudocoordinates into request. */
				reqp->ftrR = dlp->ftDLsector;
				reqp->ftrC = dlp->ftDLcylinder;
				reqp->ftrH = dlp->ftDLhead;

#if FT_TRACE
cmn_err (CE_CONT, "!P block %d, addr %x, H=%d C=%d R=%d %d\n",
  reqp->ftr_blockno, reqp->ftr_dmaPaddr,
  reqp->ftrH, reqp->ftrC, reqp->ftrR, __bolt);
#endif
			}

			/* reqp now points to a valid request record. */
			ftxReqPut (reqp);

			/* Advance pseudo coordinates to next block. */
			if (ftCDLInc (dlp)) {

				/* Failure.  Stop queueing requests. */
				blocksToBeDone = 0;
				break;
			}

			/* Advance buffer address for next block. */
			bufVaddr += FT_BLK_SIZE;

			blocksToBeDone --;
			myblock++;
		}

		/* Get tape positioned at the right block and moving. */
		if (!tapeStarted) {
			tapeStarted = 1;
			if (ftMPosTape (logBlock)) {
#if FT_TRACE
				cmn_err (CE_WARN,
				  "FT : Position for Transfer Failed");
#endif
				goto endXferFDC;
			}
		}

		
		/* Check on status of interrupt handler. */
		if (ftx.ftx_error) {
#if FT_TRACE
cmn_err (CE_WARN, "!ftx err %d", ftx.ftx_error);
#endif
			break;
		}

		/* If no interrupt is pending, issue a request. */
		if (!ftx.ftx_IRQpending && ftxReqSize () > 0) {
#if defined(FAST_CPU) && defined(FT_TRACE)
cmn_err(CE_CONT, "!Level 1 send ");
#endif
			ftxSendCmd ();
		}

		/* If all work is done, quit. */

#if FT_TRACE
cmn_err(CE_CONT, "!btbd=%d frs=%d irqpd=%d\n",
  blocksToBeDone, ftxReqSize (), ftx.ftx_IRQpending);
#endif

		if (blocksToBeDone == 0 && ftxReqSize () == 0
		  && ftx.ftx_IRQpending == 0)
			break;

		/* Sleep while interrupt routine dequeues requests. */
#if FT_TRACE
cmn_err (CE_CONT, "!sleep %d\n", __bolt);
#endif
		waitStat = ftWaitWithTimeout (FT_XFER_SECS * HZ, "ftXfer");

		/* If signaled, wait for an IRQ before leaving the loop. */
		if (waitStat == FT_RESULT_SIGNALED) {

#if FT_TRACE
cmn_err (CE_NOTE, "!ftx signaled");
#endif

			/* Tell Consumer 2 not to start any more blocks. */
			ftx.ftx_stopio = 1; 

			/* Busywait for Consumer 2 to finish current request. */
			busyWaitStat = busyWait (ftxC2chk, FT_XFER_SECS * HZ);

			if (busyWaitStat != 0) {
				/* Consumer 2 finished. */
				break;
			}

#if FT_TRACE
cmn_err (CE_NOTE, "!ftx timed out 1");
#endif

			/* Alas, timed out waiting for IRQ. */
			ftx.ftx_timedOut = 1; 
		}

		/* If timed out, stop Consumer 2 then do cleanup below. */
		if (waitStat == FT_RESULT_TIMED_OUT) {

#if FT_TRACE
cmn_err (CE_NOTE, "!ftx timed out 2 %d", __bolt);
#endif

			ftx.ftx_timedOut = 2; 
		}

		/* Timed out, do some recovery then leave the loop. */
		if (ftx.ftx_timedOut != 0) {
#if FT_TRACE
			cmn_err (CE_WARN, "!ftXfer - timeout %d",
			  ftx.ftx_timedOut);
#endif
			ftx.ftx_IRQpending = 0;
			ftSelect(ft.ft_drv, ft.ft_softsel);
			break;
		}
	}

	/* Turn off the DMA channel, in case of error break from loop. */

	if (ftx.ftx_DMA_on) {
		ftx.ftx_residual = dmaoff (DMA_CH2);
		ftx.ftx_DMA_on = 0;
	}

#if FT_TRACE
	if (ftx.ftx_residual != 0) {
		cmn_err (CE_WARN,
		  "!FT : ftXferFDC : DMA residual %d bytes ",
		  ftx.ftx_residual);
	}
#endif

	/* Stop the tape. */
	ftStopTape ();

endXferFDC:

#if FT_TRACE
	cmn_err (CE_NOTE, "!ftXferFDC : ok %s of %d bytes",
	  dirname, ftx.ftx_bytesXferred);
#endif

	return ftx.ftx_bytesXferred;
}

/************************************************************************
 * ftxIntr
 *
 * Consumer 2 part of block transfer.
 * Runs as part of the FDC IRQ handler.
 ***********************************************************************/
#if	__USE_PROTO__
void ftxIntr (void)
#else
void
ftxIntr ()
#endif
{
	int	noErr = 1;

#if defined(FAST_CPU) && defined(FT_TRACE)
cmn_err (CE_CONT, "!I:XI %d ", __bolt);
#endif

	/* Wrap up the request that just ended. */

	/* Conclude DMA transfer. */
	ftx.ftx_residual = dmaoff (DMA_CH2);
	ftx.ftx_DMA_on = 0;

	/* Did the FDC report an error? */
	if ((fdc.fdc_cmdstat [0] & ST0_IC) != ST0_NT) {
		ftx.ftx_error = 2;

#if FT_TRACE
cmn_err (CE_CONT, "!err2\n");
#endif

		/* Display block number on screen if data or crc fdc error. */
		if (fdc.fdc_cmdstat[1] & ST1_DE || fdc.fdc_cmdstat[2] & ST2_DD)
			fdcStatus ();

		wakeup(&ft.ft_wakeMeUp);
		noErr = 0;
		goto ftxIntrDone;
	}

	/* Set error status (will stop i/o) if DMA was not complete. */
	if (ftx.ftx_residual != 0) {
		ftx.ftx_error = 1;

#if FT_TRACE
cmn_err (CE_CONT, "!err1\n");
#endif

		wakeup(&ft.ft_wakeMeUp);
		noErr = 0;
		goto ftxIntrDone;
	}

	/* A block was successfully transferred. */
	ftx.ftx_bytesXferred += FT_BLK_SIZE;

	/* Has normal priority level requested a stop to i/o? */
	if (ftx.ftx_stopio) {
		ftx.ftx_stopio = 0;

#if FT_TRACE
cmn_err (CE_CONT, "!stopio\n");
#endif

		wakeup(&ft.ft_wakeMeUp);
		goto ftxIntrDone;
	}

	/* Now - immediately the previous block is done, command the next. */
	if (ftxReqSize () > 0) {
#if defined(FAST_CPU) && defined(FT_TRACE)
cmn_err (CE_CONT, "!Level 2 send\n");
#endif
		ftxSendCmd ();
	}

	/* If the queue falls below threshold, alert control level. */
	if (ftxReqFreeSize () > 3) {
#if defined(FAST_CPU) && defined(FT_TRACE)
cmn_err (CE_CONT, "!wake ");
#endif
		wakeup(&ft.ft_wakeMeUp);
	}

#if defined(FAST_CPU) && defined(FT_TRACE)
cmn_err (CE_CONT, "!endXI\n");
#endif

ftxIntrDone:

	/* Make a note of our location on the tape. */
	if (noErr)
		ftMSetLocB (ftx.ftx_blockno);

	return;
}