/*****************************************************************************
 * $Id: tape.c,v 1.3 1992/09/12 18:10:55 ak Exp $
 *****************************************************************************
 * $Log: tape.c,v $
 * Revision 1.3  1992/09/12  18:10:55  ak
 * Added scsi_name
 * Added device name support to tctl.c
 *
 * Revision 1.2  1992/09/02  19:05:22  ak
 * Version 2.0
 * - EMX version
 * - AIX version
 * - SCSI-2 commands
 * - ADD Driver
 * - blocksize support
 *
 * Revision 1.1.1.1  1992/01/06  20:27:39  ak
 * Interface now based on ST01 and ASPI.
 * AHA_DRVR no longer supported.
 * Files reorganized.
 *
 * Revision 1.1  1992/01/06  20:27:37  ak
 * Initial revision
 *
 *****************************************************************************/

static char *rcsid = "$Id: tape.c,v 1.3 1992/09/12 18:10:55 ak Exp $";


#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#ifdef __EMX__
#ifndef _far
#define _far
#endif
#endif

#include "scsi.h"
#include "tape.h"
#include "scsitape.h"

#define SenseLength	16
#define SenseAlloc	16

static int		target = 4;
static unsigned char	cdb [10];
static unsigned char	sense_buf [SenseAlloc];
static unsigned char *	sense_ptr = sense_buf;
static int		sense_len = SenseLength;
static int		active;
static long		active_nb;
static void *		ctrl;
int			scsiLevel = 1;
static long		blocksize = 512;
static long		valid_blocksize = 0;

#define int16(x)	((unsigned)(x)[0] << 8 | (x)[1])
#define int24(x)	((unsigned long)int16(x) << 8 | (x)[2])
#define int32(x)	((unsigned long)int24(x) << 8 | (x)[3])

int
tape_cmd(void *cdb, int cdb_len)
{
	return scsi_cmd(target, 0, cdb, cdb_len, sense_ptr, sense_len, (void *)0, 0L, 0);
}

int
tape_read_cmd(void *cdb, int cdb_len, void _far *data, long length)
{
	return scsi_cmd(target, 0, cdb, cdb_len, sense_ptr, sense_len, data, length, 1);
}

int
tape_write_cmd(void *cdb, int cdb_len, void _far *data, long length)
{
	return scsi_cmd(target, 0, cdb, cdb_len, sense_ptr, sense_len, data, length, 0);
}

static void
init()
{
	char *env;
	long size;

	sense_ptr = sense_buf;
	sense_len = SenseLength;
	if ((env = getenv("TAPEMODE")) != NULL)
		scsiLevel = atoi(env);
	else
		scsiLevel = senseMode;
}

void
tape_init(void)
{
	scsi_init();
	init();
}

void
tape_name(char *name)
{
	scsi_name(name);
	init();
}

void
tape_file(int fd)
{
	scsi_file(fd);
	init();
}

void
tape_term(void)
{
	scsi_term();
}

int
tape_reset(int bus)
{
	return scsi_reset(target, 0, bus);
}

void
tape_trace(int level)
{
	scsi_trace(level);
}

int
tape_target(int no)
{
	int old = target;
	target = no;
	return old;
}

void
tape_sense(void *data, int len)
{
	if (len >= 8 && data) {
		sense_ptr = data;
		sense_len = len;
	} else {
		sense_ptr = sense_buf;
		sense_len = SenseLength;
	}
}

char *
tape_error(int code)
{
	switch (code) {
	case EndOfData: return "End of data";
	case EndOfTape: return "End of tape";
	case FileMark:  return "File mark";
	}
	return scsi_error(code);
}

int
tape_inquiry(void *data, int len)
{
	cdb[0] = CmdInquiry;
	cdb[1] = 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = len;
	cdb[5] = 0;
	return tape_read_cmd(cdb, 6, data, len);
}	

int
tape_mode_sense(int page, void *data, int len)
{
	cdb[0] = CmdModeSense;
	cdb[1] = 0;
	cdb[2] = page;
	cdb[3] = 0;
	cdb[4] = len;
	cdb[5] = 0;
	return tape_read_cmd(cdb, 6, data, len);
}	

int
tape_mode_select(int page, void *data, int len, int SMP)
{
	cdb[0] = CmdModeSelect;
	cdb[1] = 0;
	cdb[2] = page;
	cdb[3] = 0;
	cdb[4] = len;
	cdb[5] = SMP ? 0x40 : 0x00;
	return tape_write_cmd(cdb, 6, data, len);
}	

int
tape_rewind(int imed)
{
	cdb[0] = SeqRewind;
	cdb[1] = imed ? 1 : 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	cdb[5] = 0;
	return tape_cmd(cdb, 6);
}

long
tape_tell(void)
{
	int rc;

	if (scsiLevel >= 2) {
		unsigned char addr [20];
		cdb[0] = SeqReadPosition;
		cdb[1] = 1;
		cdb[2] = 0;
		cdb[3] = 0;
		cdb[4] = 0;
		cdb[5] = 0;
		cdb[6] = 0;
		cdb[7] = 0;
		cdb[8] = 0;
		cdb[9] = 0;
		if ((rc = tape_read_cmd(cdb, 10, addr, 20)) != 0)
			return rc;
		return (long)addr[4] << 24 | (long)addr[5] << 16
		     | (long)addr[6] << 8 | addr[7];
	} else {
		unsigned char addr [3];
		cdb[0] = SeqRequestBlockAddress;
		cdb[1] = 0;
		cdb[2] = 0;
		cdb[3] = 0;
		cdb[4] = 3;
		cdb[5] = 0;
		if ((rc = tape_read_cmd(cdb, 6, addr, 3)) != 0)
			return rc;
		return (long)addr[0] << 16 | (long)addr[1] << 8 | addr[2];
	}
}

static int
num_result(int ret, long count, long *actual)
{
	if (actual)
		*actual = TapeUndefLength;
	if (ret == MappedError+0)
		return FileMark;
	if (ErrorClass(ret) == SenseKey) {
		if (actual && sense_ptr[0] & VADD)
			*actual = count - int32(sense_ptr+3);
		if (ret == SenseKey+NoSense) {
			if (sense_ptr[2] & EOM)
				return EndOfTape;
			if (sense_ptr[2] & FM)
				return FileMark;
		}
	} else if (actual && ret == 0)
		*actual = count;
	return ret;
}

int
tape_space(int mode, long nitems, long *actual)
{
	cdb[0] = SeqSpace;
	cdb[1] = mode;
	cdb[2] = nitems >> 16;
	cdb[3] = nitems >> 8;
	cdb[4] = nitems;
	cdb[5] = 0;
	return num_result(tape_cmd(cdb, 6), nitems, actual);
}

int
tape_seek(int imed, long blkno)
{
	if (scsiLevel >= 2) {
		cdb[0] = SeqLocate;
		cdb[1] = imed ? 5 : 4;
		cdb[2] = 0;
		cdb[3] = blkno >> 24;
		cdb[4] = blkno >> 16;
		cdb[5] = blkno >> 8;
		cdb[6] = blkno;
		cdb[7] = 0;
		cdb[8] = 0;
		cdb[9] = 0;
		return tape_cmd(cdb, 10);
	} else {
		cdb[0] = SeqSeek;
		cdb[1] = imed ? 1 : 0;
		cdb[2] = blkno >> 16;
		cdb[3] = blkno >> 8;
		cdb[4] = blkno;
		cdb[5] = 0;
		return tape_cmd(cdb, 6);
	}
}

static int
rw_result(int ret, long nb, long *actual)
{
	if (actual)
		*actual = TapeUndefLength;
	if (ret == MappedError+0)
		return FileMark;
	if (ErrorClass(ret) == SenseKey) {
		if (actual && sense_ptr[0] & VADD)
			*actual = (blocksize ? blocksize : 1)
				* (nb - int32(sense_ptr+3));
		if (ret == SenseKey+NoSense) {
			if (sense_ptr[2] & EOM)
				return EndOfTape;
			if (sense_ptr[2] & FM)
				return FileMark;
		}
	} else if (actual && ret == 0)
		*actual = (blocksize ? blocksize : 1) * nb;
	return ret;
}

int
tape_read(void _far *data, long length, long *actual)
{
	long nb;
	if (!valid_blocksize)
		tape_get_blocksize();
	nb = blocksize ? length / blocksize : length;
	cdb[0] = SeqRead;
	cdb[1] = blocksize ? 1 : 0;
	cdb[2] = nb >> 16;
	cdb[3] = nb >> 8;
	cdb[4] = nb;
	cdb[5] = 0;
	return rw_result(tape_read_cmd(cdb, 6, data, length), nb, actual);
}

int
tape_compare(void _far *data, long length, long *actual)
{
	int ret;
	long nb;
	if (!valid_blocksize)
		tape_get_blocksize();
	nb = blocksize ? length / blocksize : length;
	cdb[0] = SeqVerify;
	cdb[1] = blocksize ? 3 : 2;
	cdb[2] = nb >> 16;
	cdb[3] = nb >> 8;
	cdb[4] = nb;
	cdb[5] = 0;
	return rw_result(tape_write_cmd(cdb, 6, data, length), nb, actual);
}	

int
tape_verify(long length, long *actual)
{
	int ret;
	long nb;
	if (!valid_blocksize)
		tape_get_blocksize();
	nb = blocksize ? length / blocksize : length;
	cdb[0] = SeqVerify;
	cdb[1] = blocksize ? 1 : 0;
	cdb[2] = nb >> 16;
	cdb[3] = nb >> 8;
	cdb[4] = nb;
	cdb[5] = 0;
	return rw_result(tape_cmd(cdb, 6), nb, actual);
}	

int
tape_write(void _far *data, long length, long *actual)
{
	int ret;
	long nb;
	if (!valid_blocksize)
		tape_get_blocksize();
	nb = blocksize ? length / blocksize : length;
	cdb[0] = SeqWrite;
	cdb[1] = blocksize ? 1 : 0;
	cdb[2] = nb >> 16;
	cdb[3] = nb >> 8;
	cdb[4] = nb;
	cdb[5] = 0;
	return rw_result(tape_write_cmd(cdb, 6, data, length), nb, actual);
}

static void
cleanup(void)
{
	if (active) {
		while (scsi_wait(&ctrl, sense_ptr, 1) == ComeAgain)
			;
		active = 0;
	}
}

int
tape_buffered_write(void _far *data, long length)
{
	int ret;

	if (!valid_blocksize)
		tape_get_blocksize();

	if (ctrl == NULL) {
		atexit(cleanup);
		ctrl = scsi_alloc();
	}

	active_nb = blocksize ? length / blocksize : length;

	cdb[0] = SeqWrite;
	cdb[1] = blocksize ? 1 : 0;
	cdb[2] = active_nb >> 16;
	cdb[3] = active_nb >> 8;
	cdb[4] = active_nb;
	cdb[5] = 0;

	ret = scsi_start(ctrl, target, 0, cdb, 6, sense_len, data, length, 0);
	if (ret == ComeAgain)
		active = 1;
	return ret;
}

int
tape_buffered_wait(long *actual)
{
	int ret;

	if (!active)
		return NoCommand;
	ret = scsi_wait(ctrl, sense_ptr, 1);
	active = 0;
	return rw_result(ret, active_nb, actual);
}

int
tape_filemark(int imed, long count, long *actual)
{
	cdb[0] = SeqWriteFilemarks;
	cdb[1] = imed ? 1 : 0;
	cdb[2] = count >> 16;
	cdb[3] = count >> 8;
	cdb[4] = count;
	cdb[5] = 0;
	return num_result(tape_cmd(cdb, 6), count, actual);
}

int
tape_setmark(int imed, long count, long *actual)
{
	cdb[0] = SeqWriteFilemarks;
	cdb[1] = imed ? 3 : 2;
	cdb[2] = count >> 16;
	cdb[3] = count >> 8;
	cdb[4] = count;
	cdb[5] = 0;
	return num_result(tape_cmd(cdb, 6), count, actual);
}

int
tape_erase(void)
{
	cdb[0] = SeqErase;
	cdb[1] = 1;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	cdb[5] = 0;
	return tape_cmd(cdb, 6);
}

long
tape_get_blocksize(void)
{
	unsigned char data [12];
	int rc;

	cdb[0] = CmdModeSense;
	cdb[1] = 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 12;		/* length of parameter list */
	cdb[5] = 0;

	if ((rc = tape_read_cmd(cdb, 6, data, 12)) != 0)
		return rc;
	blocksize = (long)data[9] << 16 | (long)data[10] << 8 | data[11];
	valid_blocksize = 1;
	scsi_set_blocksize(target, 0, blocksize);
	return blocksize;
}

int
tape_set_blocksize(long size)
{
	unsigned char data [12];

	blocksize = size;
	scsi_set_blocksize(target, 0, size);

	cdb[0] = CmdModeSelect;
	cdb[1] = 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 12;		/* length of parameter list */
	cdb[5] = 0;

	data[0]  = 0;
	data[1]  = 0;
	data[2]  = 0x10;	/* buffered mode */
	data[3]  = 8;		/* block descriptor length */

	data[4]  = 0;		/* density is default */
	data[5]  = 0;
	data[6]  = 0;
	data[7]  = 0;
	data[8]  = 0;
	data[9]  = size >> 16;
	data[10] = size >> 8;
	data[11] = size;

	return tape_write_cmd(cdb, 6, data, 12);
}

int
tape_speed(int code)
{
	unsigned char data [4];

	cdb[0] = CmdModeSelect;
	cdb[1] = 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 4;		/* length of parameter list */
	cdb[5] = 0;

	data[0]  = 0;
	data[1]  = 0;
	data[2]  = 0x10 | code;	/* buffered mode */
	data[3]  = 0;		/* block descriptor length */

	return tape_write_cmd(cdb, 6, data, 4);
}

int
tape_ready(void)
{
	cdb[0] = CmdTestUnitReady;
	cdb[1] = 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	cdb[5] = 0;
	return tape_cmd(cdb, 6);
}

int
tape_load(int imed, int retension)
{
	cdb[0] = SeqLoad;
	cdb[1] = imed != 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = retension ? 03 : 01;
	cdb[5] = 0;
	return tape_cmd(cdb, 6);
}

int
tape_unload(int imed, int mode)
{
	cdb[0] = SeqLoad;
	cdb[1] = imed != 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = (scsiLevel >= 2) ? mode & 07 : mode & 03;
	cdb[5] = (scsiLevel >= 2) ? 0 : mode & 04 << 6;
	return tape_cmd(cdb, 6);
}

void
tape_print_sense(FILE *out, int code)
{
	struct Tape_Extended_Sense *p
		= (struct Tape_Extended_Sense *) sense_ptr;

	if (ErrorClass(code) == ErrorClass(MappedError)) {
		fprintf(out, "%s\n", tape_error(code));
		return;
	}
	fprintf(out, "Return code:	%s\n", tape_error(code));
	fprintf(out, "Sense key:	%s\n", senseTab[p->sense_key]);

	if (p->filemark || p->end_of_media || p->incorrect_length) {
		fprintf(out, "    ");
		if (p->filemark)
			fprintf(out, "Filemark ");
		if (p->end_of_media)
			fprintf(out, "End-of-media ");
		if (p->incorrect_length)
			fprintf(out, "Incorrect-length ");
		fprintf(out, "\n");
	}

	switch (senseMode) {
	case TDC3600:
		fprintf(out, "ERCL+ERCD:	%s\n",
			find_error(tdc3600ercd, p->u.tdc.error));
		fprintf(out, "EXERCD:		%s\n",
			find_error(tdc3600xercd, p->u.tdc.xerror));
		fprintf(out, "    blocks=%ld filemarks=%u remaining=%u\n",
			int24(p->u.tdc.no_blocks),
			int16(p->u.tdc.no_filemarks),
			p->u.tdc.no_remaining);
		fprintf(out, "    recovered=%u underruns=%u marginal=%u\n",
			int16(p->u.tdc.no_recovered),
			int16(p->u.tdc.no_underruns),
			p->u.tdc.no_marginal);
		break;
	case SCSI2:
		fprintf(out, "ASC::		%02X\n", p->u.scsi2.asc);
		fprintf(out, "ASCQ:		%02X\n", p->u.scsi2.ascq);
		fprintf(out, "Description:	%s\n",
			find_error(scsi2asc,
				p->u.scsi2.asc << 8 | p->u.scsi2.ascq));
		break;
	}
}
