/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */

/* $Header:tapeio.c 12.1$ */
/* $ACIS:tapeio.c 12.1$ */
/* $Source: /ibm/acis/usr/sys/pc_code/RCS/tapeio.c,v $ */

#ifndef lint
static char *rcsid = "$Header:tapeio.c 12.1$";
#endif


#include <dos.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include "pcparam.h"
#include "rb.h"
#include "abios_st.h"
#include "dio.h"
#include "bios.h"
#include "trace.h"
#include "tape.h"
#include "vars.h"

#define BIOS_ERROR	0x8000	/* flag for BIOS error */
#define BSIZE	512
#define BSHIFT	9		/* 512 bytes */

#define CVT_BLOCK(n,t) (((n) << BSHIFT) / (t)->blocksize)

#define STUNIT(drive) ((drive)>>6)
#define STPART(drive) ((drive)&0x3f)

extern char far *buff_dio;

extern union pcmem far *pcmem;

#define NST	4

#define NPART	64		/* number of logical partitions/drive */

#define NDRIVE	(NST * NPART)	/* number of drives/partitions */
#define STIRQ	12		/* use ROMP IRQ 12 since RT tape does */

char stdisk_name[64] = "UNIXFILE.%03d";

/*
 * The return code of each disk steration is placed in the one of the
 * following arrays. The address of each of these data items is
 * placed into the cbcb.cb_ent[XXX].pc_cb field so that the Unix
 * code can get to the data.
 */

static char     str_buff[NDRIVE * sizeof (struct dio_rtn_codes)];

struct dio_rtn_codes *st_return_code = (struct dio_rtn_codes *) str_buff;


static int st_int;			/* streaming tape interrupt code */

struct tapeinfo tapeinfo[MAXDRIVES];	/* the tape info structure */

int st_readst(pcdpladdr_t);
int st_writest(pcdpladdr_t);
void st_tapeop(pcplqaddr_t);
int st_io(int, int, int, int, int, int, char far *);
int st_open(int, int);
int st_close(int);
char st_base();
int st_init(int);
static int find_tape_driver();
static int stdriver(struct tapeinfo *);


/*************************************************************
 * st_tapeop(dplqe) - Do an streaming tape operation
 *
 *  This routine is called from the main program to do an sttical disk 
 *  steration. The routine starts off by copying the
 *  disk paramater list (pcdpl) down from Unix. Then it takes care
 *  of any byte swapping that needs to be done on the parameters.
 *  Once all of the parameters are in PC memory we call the apprstiate
 *  routine to handle the specific disk operation.
 *
 */

void 
st_tapeop(pcplqe)
	pcplqaddr_t     pcplqe;
{
	pcdpl_t         dpl;
	register pcdpladdr_t dplptr = &dpl;
	register int    i;
	u_short         s;
	int             rc, drive;
	u_long          unixaddr;
	char far       *pcaddr;
	u_short         fdstat;
	u_long		size;

#ifdef DEBUG
	int             ldebug = *dbugptr & TAPEDEBUG;
#endif /* DEBUG */

	/*
	 * First read the DPL down from Unix 
	 */

	pcaddr = (char far *) dplptr;
	unixaddr = (u_long) pcplqe->unix_cb;

	DEBUGF(ldebug, printf("diskst: pcplqe = %#lx\n", (u_long) pcplqe));
	DEBUGF(ldebug, printf("diskst: Copy the dpl from Unix"));

	rc = movein(unixaddr, pcaddr, sizeof(pcdpl_t));

	if (rc < 0)
	{
		DEBUGF(ldebug, printf("pc code: Can't read DPL\n"));
		goto dio_exit;
	}

	TRACE(TRACE_TAPE, pcaddr, sizeof (pcdpl_t));

	/*
	 * Change byte order 
	 */

	dplptr->op_code = exchw(dplptr->op_code);
	dplptr->drive = exchw(dplptr->drive) & 0x00ff;
	dplptr->head = exchw(dplptr->head);
	dplptr->cyl = exchw(dplptr->cyl);
	dplptr->sector = exchw(dplptr->sector);
	dplptr->number = exchw(dplptr->number);

	i = 0;
	while ((dplptr->atr_cnt[i] = exchw(dplptr->atr_cnt[i])) > 0)
	{
		dplptr->atr_addr[i] = exchl(dplptr->atr_addr[i]);
		DEBUGF(ldebug, printf("tapeop: %d: addr %#lx cnt %#x\n",
			       i, dplptr->atr_addr[i], dplptr->atr_cnt[i]));
		if (++i >= 32)
			break;
	}

	drive = dplptr->drive;
	if ((unsigned) drive >= NDRIVE)
		rc = 1;			/* bad drive number */
	else
		switch (dplptr->op_code)
	{
	case DISKOP_RESET:
		rc = st_init(drive); 	/* used to tell if we're alive */
		break;

	case DISKOP_OPEN:		/* open the drive */
		rc = st_open(drive,dplptr->number);
/*			st_return_code[drive].badblock = exchl(size);	/* later */
		break;

#ifdef notdef
	case DISKOP_STATUS:
		fdstat = 0;
		rc = getstgeometry(drive, &fdstat);
		if (rc > 0)
			rc = fdstat;
		break;
#endif

	case DISKOP_READ:
		rc = st_readst(dplptr);
		break;

	case DISKOP_WRITE:
		rc = st_writest(dplptr);
		break;

	case DISKOP_CLOSE:
		rc = st_close(drive);
		break;
	default:
		break;
	}


dio_exit:


	if (rc >= 0)
		rc |= 0x1000;

	rc = exchw(rc);

	s = spl();
	st_return_code[drive].errno = rc;
	rompint4(4,FAKEPOLL((STIRQ&7)), 0, 0, cur_cbcb);

	splx(s);
}

/*
 * st_io()   - Send a request off to the real driver
 *
 */

int st_io(st, drive, head, track, sector, num, buff)
	int             st;
	int             drive;
	int             head;
	int             track;
	int             sector;
	int             num;
	char far       *buff;	/* Data buffer for operation */
{
	int             cnt = 0, rc;
	unsigned int	bytes;	/* number of bytes written */
	long		pos;
	struct tapeinfo	*t = &tapeinfo[drive];


#ifdef DEBUG
	int             ldebug = *dbugptr & TAPEDEBUG;
#endif /* DEBUG */

	DEBUGF(ldebug, printf("st_io: st=0x%x, drive=0x%x, head=0x%x, sector=0x%x, num=0x%x, buff = 0x%lx\n",
		   st, drive, head, sector, num, (char far *) buff));


	/*
	 * Send the request off to the driver.
	 */

	pos = (((long) (unsigned) sector) ) |
	      (((long) (unsigned) head) << 16);	/* position in bytes */
	pos /= t->blocksize;	/* get the tape block */

	t->block = pos;
	t->count = CVT_BLOCK(num,t);
	t->op = (st == DISKOP_READ) ? TAPE_READ: TAPE_WRITE;
	t->buffer = buff;

	rc = stdriver(t);

	DEBUGF(ldebug, printf("st_io: pos=%lx rc=%x bytes=%x\n",pos,rc,t->xcount));

	if (rc != 0 || bytes == 0)
		rc |= BIOS_ERROR;		/* flag error */
	st_return_code[drive].pad = exchw(t->xcount*t->blocksize);	/* return bytes read/written */
	return (rc);
}

/*  st_diskio() - Do reads on the tape
 *
 *
 */
int st_diskio(dpl)
	pcdpladdr_t     dpl;
{
	register int    num, sec;
	u_short         drive, hd, trk, ontrk, rc, st, maxsec, maxhd;
	char far       *cptr;
#ifdef DEBUG
	int             ldebug = *dbugptr & TAPEDEBUG;
#endif /* DEBUG */

	cptr = (char far *) buff_dio;
	hd = dpl->head;
	trk = dpl->cyl;
	sec = dpl->sector;
	num = dpl->number;
	st = dpl->op_code;
	drive = dpl->drive;

	rc = st_io(st, drive, hd, trk, sec, num, cptr);
	return (rc);
}


/*
 *  Do the disk read, then copy the data back to Unix
 */

int st_readst(dpl)
	pcdpladdr_t     dpl;
{
	register u_short i = 0, j;
	int             rc;
	u_long          unixaddr;
	char far       *pcaddr = buff_dio;

	rc = st_diskio(dpl);
	if (rc > -1)
	{

		while ((j = dpl->atr_cnt[i]) > 0)
		{
			unixaddr = dpl->atr_addr[i];
			rc = moveout(unixaddr, pcaddr, j);
			if (rc < 0)
				break;
			pcaddr += j;
			i++;

		}
	}
	return (rc);
}

/*
 * st_writest()  - Copy the data down from Unix, then do the disk st
 *
 */
int st_writest(dpl)
	pcdpladdr_t     dpl;
{
	register u_short i = 0, j;
	int             rc;
	u_long          unixaddr;
	char far       *pcaddr = buff_dio;


	while ((j = dpl->atr_cnt[i]) > 0)
	{
		unixaddr = dpl->atr_addr[i];
		rc = movein(unixaddr, pcaddr, j);
		if (rc < 0)
			return (rc);
		pcaddr += j;
		i++;
	}
	rc = st_diskio(dpl);
	return (rc);
}



/*
 * open the tape drive "drive".
 * if the file already exists we open it for update, otherwise we create
 * it.
 */
int st_open(drive,how)
	int		drive, how;
{
	char name[128];
	int err_code;
	struct tapeinfo *t = &tapeinfo[drive];

#ifdef DEBUG
	int             ldebug = *dbugptr & TAPEDEBUG;
#endif /* DEBUG */

	if (st_init(drive) == 0)
		return(BIOS_ERROR+ENODEV);
	t->buffer = (char far *) name;
	t->op = TAPE_OPEN;
	t->info = (how == 0) ? TAPE_OPEN_READ : TAPE_OPEN_RW;
	sprintf(name,stdisk_name, STPART(drive));  	/* build file name */

	DEBUGF(ldebug, printf("st_open: drive=%x how=%x name=%s ...",
		drive,how,name));
	err_code = stdriver(t);

	if (err_code != TAPE_RC_OK)
		err_code |= BIOS_ERROR;
	DEBUGF(ldebug, printf(" err_code=%x errno=%d msg=%s\n",err_code,
		t->errno, t->errmsg ? t->errmsg : "" ));
	return(err_code);
}

int st_close(drive)
	int		drive;
{
	int		err_code;
	struct tapeinfo *t = &tapeinfo[drive];

	t->op = TAPE_CLOSE;
	err_code = stdriver(t);
	if (err_code)
		err_code |= BIOS_ERROR;
	return(err_code);
}


void far st_done(t)
	struct tapeinfo far *t;
{
}

/*
 * first time initializtion of the tape driver
 */
int st_init(drive)
	int drive;
{
	struct tapeinfo *t = &tapeinfo[drive];
	extern void huge * far halloc(long,u_short);
	extern void far hfree(void huge *);

	if (st_int)
		return(st_int);	/* already initialized */
	if ((st_int = find_tape_driver()) == 0)
		return(0);	/* driver not present */
	bzero(t, sizeof (struct tapeinfo));
	t->op = TAPE_INIT;		/* initialize the drive etc */
	t->drive = drive;
	t->alloc = halloc;
	t->free = hfree;
	t->done = st_done;
	if (stdriver(t) == TAPE_RC_OK)
		st_int = 0;
	return(st_int);
}

static int stdriver(t)
	struct tapeinfo *t;
{
	union REGS      inregs, outregs;

	DEBUGF(*dbugptr & TAPEDEBUG, printf("stdriver: drive=%x op=%x\n",
		t->drive,t->op));
	inregs.x.dx = FP_OFF(t);
	int86(st_int, &inregs, &outregs);	/* ds already set properly */

	st_wait(t);			/* wait for completion */

	return(t->rc);
}

/*
 * wait for the tape drive to complete the current operation 
 */
static st_wait(t)
	struct tapeinfo		*t;
{
	while (t->rc == TAPE_RC_BUSY)
		;
}


/*
 * locate the tape driver by scanning thru the user interrupt 
 * vectors looking for one that has TAPE_NAME just before the 
 * entry point. We return the appropriate int number if found
 * or zero if not.
 */
static int find_tape_driver()
{
	int i;
	for (i=FIRST_USER_INT; i<=LAST_USER_INT; ++i)
		{
		DEBUGF(*dbugptr & TAPEDEBUG, printf("user vector %x = %lx\n", i, pcmem[i].vector));
		if (pcmem[i].vector != 0)
			{
			int len = strlen(TAPE_NAME);
			int j;
			char far *ptr = (char far *) pcmem[i].vector - len;
			for (j=0; j<len; ++j)
				if (ptr[j] != TAPE_NAME[j])
					break;
			if (j >= len)
				{
				DEBUGF(*dbugptr & TAPEDEBUG, printf("found tape driver at int %x\n",i));
				return(i);
				}
			}
		}
	DEBUGF(*dbugptr & TAPEDEBUG, printf("no tape driver found\n"));
	return(0);
}
