/* 
 * Mach Operating System
 * Copyright (c) 1991,1990 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 * 
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 * 
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 *	File: rz_tape.c
 * 	Author: Alessandro Forin, Carnegie Mellon University
 *	Date:	10/90
 *
 *	Top layer of the SCSI driver: interface with the MI.
 *	This file contains operations specific to TAPE-like devices.
 */

#include <mach/std_types.h>
#include <scsi/compat_30.h>

#include <sys/ioctl.h>
#ifdef	MACH_KERNEL
#include <device/tape_status.h>
#else	/*MACH_KERNEL*/
#include <mips/PMAX/tape_status.h>
#endif	/*MACH_KERNEL*/

#include <scsi/scsi.h>
#include <scsi/scsi_defs.h>
#include <scsi/rz.h>

#if  (NSCSI > 0)


void sctape_start(); /* forward */

int scsi_tape_timeout = 5*60;	/* secs, tk50 is slow when positioning far apart */

int sctape_open(tgt, req)
	target_info_t	*tgt;
	io_req_t	req;
{
	io_return_t	ret;
	io_req_t	ior;
	int		i;
	scsi_mode_sense_data_t *mod;

#ifdef	MACH_KERNEL
	req->io_device->flag |= D_EXCL_OPEN;
#endif	/*MACH_KERNEL*/

	/* Preferably allow tapes to disconnect */
	if (BGET(scsi_might_disconnect,(unsigned char)tgt->masterno,tgt->target_id))
		BSET(scsi_should_disconnect,(unsigned char)tgt->masterno,tgt->target_id);

	/*
	 * Dummy ior for proper sync purposes
	 */
	io_req_alloc(ior,0);
	ior->io_count = 0;

	/*
	 * Do a mode sense first, some drives might be picky
	 * about changing params [even if the standard might
	 * say otherwise, sigh.]
	 */
	do {
		ior->io_op = IO_INTERNAL;
		ior->io_next = 0;
		ior->io_error = 0;
		ret = scsi_mode_sense(tgt, 0, 32, ior);
	} while (ret == SCSI_RET_RETRY);

	mod = (scsi_mode_sense_data_t *)tgt->cmd_ptr;
	if (scsi_debug) {
		int	p[5];
		bcopy((char*)mod, (char*)p, sizeof(p));
		printf("[modsns(%x): x%x x%x x%x x%x x%x]", ret,
			p[0], p[1], p[2], p[3], p[4]);
	}
	if (ret == SCSI_RET_DEVICE_DOWN)
		goto out;
	if (ret == SCSI_RET_SUCCESS) {
		tgt->dev_info.tape.read_only = mod->wp;
		tgt->dev_info.tape.speed = mod->speed;
		tgt->dev_info.tape.density = mod->bdesc[0].density_code;
	} /* else they all default sensibly, using zeroes */

	/* Some tapes have limits on record-length */
again:
	ior->io_op = IO_INTERNAL;
	ior->io_next = 0;
	ior->io_error = 0;
	ret = scsi_read_block_limits( tgt, ior);
	if (ret == SCSI_RET_RETRY) goto again;
	if (!ior->io_error && (ret == SCSI_RET_SUCCESS)) {
		scsi_blimits_data_t	*lim;
		int			maxl;

		lim = (scsi_blimits_data_t *) tgt->cmd_ptr;

		tgt->block_size = (lim->minlen_msb << 8) |
				   lim->minlen_lsb;

		maxl =	(lim->maxlen_msb << 16) |
			(lim->maxlen_sb  <<  8) |
			 lim->maxlen_lsb;
		if (maxl == 0)
			maxl = (unsigned)-1;
		tgt->dev_info.tape.maxreclen = maxl;
		tgt->dev_info.tape.fixed_size = (maxl == tgt->block_size);
	} else {
		/* let the user worry about it */
		/* default: tgt->block_size = 1; */
		tgt->dev_info.tape.maxreclen = (unsigned)-1;
		tgt->dev_info.tape.fixed_size = FALSE;
	}

	/* Try hard to do a mode select */
	for (i = 0; i < 5; i++) {
		ior->io_op = IO_INTERNAL;
		ior->io_error = 0;
		ret = sctape_mode_select(tgt, 0, 0, FALSE, ior);
		if (ret == SCSI_RET_SUCCESS)
			break;
	}
	if (scsi_watchdog_period < scsi_tape_timeout)
		scsi_watchdog_period += scsi_tape_timeout;

#if 0	/* this might imply rewind, which we do not want, although yes, .. */
	/* we want the tape loaded */
	ior->io_op = IO_INTERNAL;
	ior->io_next = 0;
	ior->io_error = 0;
	ret = scsi_start_unit(tgt, SCSI_CMD_SS_START, ior);
#endif
	req->io_device->bsize = tgt->block_size;
out:
	io_req_free(ior);
	return ret;
}


io_return_t sctape_close(tgt)
	target_info_t	*tgt;
{
	io_return_t	ret = SCSI_RET_SUCCESS;
	io_req_t	ior;

	/*
	 * Dummy ior for proper sync purposes
	 */
	io_req_alloc(ior,0);
	ior->io_op = IO_INTERNAL;
	ior->io_next = 0;
	ior->io_count = 0;

	if (tgt->ior) printf("TAPE: Close with pending requests ?? \n");

	/* write a filemark if we xtnded/truncated the tape */
	if (tgt->flags & TGT_WRITTEN_TO) {
		tgt->ior = ior;
		ior->io_error = 0;
		ret = scsi_write_filemarks(tgt, 2, ior);
		if (ret != SCSI_RET_SUCCESS)
			 printf("%s%d: wfmark failed x%x\n",
			 (*tgt->dev_ops->driver_name)(TRUE), tgt->target_id, ret);
		/*
		 * Don't bother repositioning if we'll rewind it
		 */
		if (tgt->flags & TGT_REWIND_ON_CLOSE)
			goto rew;
retry:
		tgt->ior = ior;
		ior->io_op = IO_INTERNAL;
		ior->io_error = 0;
		ior->io_next = 0;
		ret = scsi_space(tgt, SCSI_CMD_SP_FIL, -1, ior);
		if (ret != SCSI_RET_SUCCESS) {
			if (ret == SCSI_RET_RETRY) {
				timeout(wakeup, tgt, hz);
				await(tgt);
				goto retry;
			}
			printf("%s%d: bspfile failed x%x\n",
			 (*tgt->dev_ops->driver_name)(TRUE), tgt->target_id, ret);
		}
	}
rew:
	if (tgt->flags & TGT_REWIND_ON_CLOSE) {
		/* Rewind tape */
		ior->io_error = 0;
		ior->io_op = IO_INTERNAL;
		ior->io_error = 0;
		tgt->ior = ior;
		(void) scsi_rewind(tgt, ior, FALSE);
		iowait(ior);
		if (tgt->done == SCSI_RET_RETRY) {
			timeout(wakeup, tgt, 5*hz);
			await(tgt);
			goto rew;
		}
	}
		io_req_free(ior);

	tgt->flags &= ~(TGT_ONLINE|TGT_WRITTEN_TO|TGT_REWIND_ON_CLOSE);
	return ret;
}

int sctape_strategy(ior)
	register io_req_t	ior;
{
	target_info_t  *tgt;
	register int    i = ior->io_unit;

	tgt = scsi_softc[rzcontroller(i)]->target[rzslave(i)];

	if (((ior->io_op & IO_READ) == 0) &&
	    tgt->dev_info.tape.read_only) {
		ior->io_error = D_INVALID_OPERATION;
		ior->io_op |= IO_ERROR;
		ior->io_residual = ior->io_count;
		iodone(ior);
		return ior->io_error;
	}

	return rz_simpleq_strategy( ior, sctape_start);
}

static void
do_residue(ior, sns, bsize)
	io_req_t	  ior;
	scsi_sense_data_t *sns;
	int		  bsize;
{
	int residue;

	/* Not an error situation */
	ior->io_error = 0;
	ior->io_op &= ~IO_ERROR;

	if (!sns->addr_valid) {
		ior->io_residual = ior->io_count;
		return;
	}

	residue = sns->u.xtended.info0 << 24 |
		  sns->u.xtended.info1 << 16 |
		  sns->u.xtended.info2 <<  8 |
		  sns->u.xtended.info3;
	/* fixed ? */
	residue *= bsize;
	/*
	 * NOTE: residue == requested - actual
	 * We only care if > 0
	 */
	if (residue < 0) residue = 0;/* sanity */
	ior->io_residual += residue;
}

void sctape_start( tgt, done)
	target_info_t	*tgt;
	boolean_t	done;
{
	io_req_t		head, ior = tgt->ior;

	if (ior == 0)
		return;

	if (done) {

		/* see if we must retry */
		if ((tgt->done == SCSI_RET_RETRY) &&
		    ((ior->io_op & IO_INTERNAL) == 0)) {
			delay(1000000);/*XXX*/
			goto start;
		} else
		/* got a bus reset ? ouch, that hurts */
		if (tgt->done == (SCSI_RET_ABORTED|SCSI_RET_RETRY)) {
			/*
			 * we really cannot retry because the tape position
			 * is lost.
			 */
			printf("Lost tape position\n");
			ior->io_error = D_IO_ERROR;
			ior->io_op |= IO_ERROR;
		} else

		/* check completion status */

		if (tgt->cur_cmd == SCSI_CMD_REQUEST_SENSE) {
			scsi_sense_data_t *sns;

			ior->io_op = ior->io_temporary;
			ior->io_error = D_IO_ERROR;
			ior->io_op |= IO_ERROR;

			sns = (scsi_sense_data_t *)tgt->cmd_ptr;

			if (scsi_debug)
				scsi_print_sense_data(sns);

			if (scsi_check_sense_data(tgt, sns)) {
			    if (sns->u.xtended.ili) {
				if (ior->io_op & IO_READ) {
				    do_residue(ior, sns, tgt->block_size);
				    if (scsi_debug)
					printf("Tape Short Read (%d)\n",
						ior->io_residual);
				}
			    } else if (sns->u.xtended.eom) {
				do_residue(ior, sns, tgt->block_size);
				if (scsi_debug)
					printf("End of Physical Tape!\n");
			    } else if (sns->u.xtended.fm) {
				do_residue(ior, sns, tgt->block_size);
				if (scsi_debug)
					printf("File Mark\n");
			    }
			}
		}

		else if (tgt->done != SCSI_RET_SUCCESS) {

		    if (tgt->done == SCSI_RET_NEED_SENSE) {

			ior->io_temporary = ior->io_op;
			ior->io_op = IO_INTERNAL;
			if (scsi_debug)
				printf("[NeedSns x%x x%x]", ior->io_residual, ior->io_count);
			scsi_request_sense(tgt, ior, 0);
			return;

		    } else if (tgt->done == SCSI_RET_RETRY) {
			/* only retry here READs and WRITEs */
			if ((ior->io_op & IO_INTERNAL) == 0) {
				ior->io_residual = 0;
				goto start;
			} else{
				ior->io_error = D_WOULD_BLOCK;
				ior->io_op |= IO_ERROR;
			}
		    } else {
			ior->io_error = D_IO_ERROR;
			ior->io_op |= IO_ERROR;
		    }
		}

		if (scsi_debug)
			printf("[Resid x%x]", ior->io_residual);

		/* dequeue next one */
		head = ior;

		simple_lock(&tgt->target_lock);
		ior = head->io_next;
		tgt->ior = ior;
		if (ior)
			ior->io_prev = head->io_prev;
		simple_unlock(&tgt->target_lock);

		iodone(head);

		if (ior == 0)
			return;
	}
	ior->io_residual = 0;
start:
	if (ior->io_op & IO_READ) {
		tgt->flags &= ~TGT_WRITTEN_TO;
		sctape_read( tgt, ior );
	} else if ((ior->io_op & IO_INTERNAL) == 0) {
		tgt->flags |= TGT_WRITTEN_TO;
		sctape_write( tgt, ior );
	}
}

io_return_t
sctape_get_status( dev, tgt, flavor, status, status_count)
	int		dev;
	target_info_t	*tgt;
	dev_flavor_t	flavor;
	dev_status_t	status;
	natural_t	*status_count;
{
	switch (flavor) {
	case DEV_GET_SIZE:
		
		status[DEV_GET_SIZE_DEVICE_SIZE] = 0;
		status[DEV_GET_SIZE_RECORD_SIZE] = tgt->block_size;
		*status_count = DEV_GET_SIZE_COUNT;
		break;
	case TAPE_STATUS: {
		struct tape_status *ts = (struct tape_status *) status;

		ts->mt_type		= MT_ISSCSI;
		ts->speed		= tgt->dev_info.tape.speed;
		ts->density		= tgt->dev_info.tape.density;
		ts->flags		= (tgt->flags & TGT_REWIND_ON_CLOSE) ?
						TAPE_FLG_REWIND : 0;
		if (tgt->dev_info.tape.read_only)
			ts->flags |= TAPE_FLG_WP;
#ifdef	MACH_KERNEL
		*status_count = TAPE_STATUS_COUNT;
#endif

		break;
	    }
	/* U*x compat */
	case MTIOCGET: {
		struct mtget *g = (struct mtget *) status;

		bzero(g, sizeof(struct mtget));
		g->mt_type = 0x7;	/* Ultrix compat */
#ifdef	MACH_KERNEL
		*status_count = sizeof(struct mtget)/sizeof(int);
#endif
		break;
	    }
	default:
		return D_INVALID_OPERATION;
	}
	return D_SUCCESS;
}

io_return_t
sctape_set_status( dev, tgt, flavor, status, status_count)
	int		dev;
	target_info_t	*tgt;
	dev_flavor_t	flavor;
	dev_status_t	status;
	natural_t	status_count;
{
	scsi_ret_t		ret;

	switch (flavor) {
	case TAPE_STATUS: {
		struct tape_status *ts = (struct tape_status *) status;
		if (ts->flags & TAPE_FLG_REWIND)
			tgt->flags |= TGT_REWIND_ON_CLOSE;
		else
			tgt->flags &= ~TGT_REWIND_ON_CLOSE;

		if (ts->speed || ts->density) {
			unsigned int ospeed, odensity;
			io_req_t	ior;

			io_req_alloc(ior,0);
			ior->io_op = IO_INTERNAL;
			ior->io_error = 0;
			ior->io_next = 0;
			ior->io_count = 0;

			ospeed = tgt->dev_info.tape.speed;
			odensity = tgt->dev_info.tape.density;
			tgt->dev_info.tape.speed = ts->speed;
			tgt->dev_info.tape.density = ts->density;

			ret = sctape_mode_select(tgt, 0, 0, (ospeed == ts->speed), ior);
			if (ret != SCSI_RET_SUCCESS) {
				tgt->dev_info.tape.speed = ospeed;
				tgt->dev_info.tape.density = odensity;
			}

			io_req_free(ior);
		}

		break;
	    }
	/* U*x compat */
	case MTIOCTOP: {
		struct tape_params *mt = (struct tape_params *) status;
		io_req_t	ior;

		if (scsi_debug)
			printf("[sctape_sstatus: %x %x %x]\n",
				flavor, mt->mt_operation, mt->mt_repeat_count);

		io_req_alloc(ior,0);
retry:
		ior->io_count = 0;
		ior->io_op = IO_INTERNAL;
		ior->io_error = 0;
		ior->io_next = 0;
		tgt->ior = ior;

		/* compat: in U*x it is a short */
		switch ((short)(mt->mt_operation)) {
		case MTWEOF:	/* write an end-of-file record */
			ret = scsi_write_filemarks(tgt, mt->mt_repeat_count, ior);
			break;
		case MTFSF:	/* forward space file */
			ret = scsi_space(tgt, SCSI_CMD_SP_FIL, mt->mt_repeat_count, ior);
			break;
		case MTBSF:	/* backward space file */
			ret = scsi_space(tgt, SCSI_CMD_SP_FIL, -mt->mt_repeat_count,ior);
			break;
		case MTFSR:	/* forward space record */
			ret = scsi_space(tgt, SCSI_CMD_SP_BLOCKS, mt->mt_repeat_count, ior);
			break;
		case MTBSR:	/* backward space record */
			ret = scsi_space(tgt, SCSI_CMD_SP_BLOCKS, -mt->mt_repeat_count, ior);
			break;
		case MTREW:	/* rewind */
		case MTOFFL:	/* rewind and put the drive offline */
			ret = scsi_rewind(tgt, ior, TRUE);
			iowait(ior);
			if ((short)(mt->mt_operation) == MTREW) break;
			ior->io_op = 0;
			ior->io_next = 0;
			ior->io_error = 0;
			(void) scsi_start_unit(tgt, 0, ior);
			break;
		case MTNOP:	/* no operation, sets status only */
		case MTCACHE:	/* enable controller cache */
		case MTNOCACHE:	/* disable controller cache */
			ret = SCSI_RET_SUCCESS;
			break;
		default:
			tgt->ior = 0;
			io_req_free(ior);
			return D_INVALID_OPERATION;
		}

		if (ret == SCSI_RET_RETRY) {
			timeout(wakeup, ior, 5*hz);
			await(ior);
			goto retry;
		}

		io_req_free(ior);
		if (ret != SCSI_RET_SUCCESS)
			return D_IO_ERROR;
		break;
	}
	case MTIOCIEOT:
	case MTIOCEEOT:
	default:
		return D_INVALID_OPERATION;
	}
	return D_SUCCESS;
}
#endif  /* NSCSI > 0 */