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

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

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


#include <dos.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.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 "vars.h"

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

#define OPUNIT(drive) ((drive)>>5)
#define OPPART(drive) ((drive)&0x1f)

extern char far *buff_dio;

extern union pcmem far *pcmem;

#define NOP	8

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

#define NDRIVE	(NOP * NPART)	/* number of drives/partitions */
#define OPIRQ	2		/* use ROMP IRQ 2 since nobody else does */

char opdisk_name[64] = "%c:\\UNIXFILE.%03d";

/*
 * The return code of each disk operation 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     opr_buff[NDRIVE * sizeof (struct dio_rtn_codes)];

struct dio_rtn_codes *op_return_code = (struct dio_rtn_codes *) opr_buff;


int ophandle[NDRIVE];		/* DOS file handles */

int op_readop(pcdpladdr_t);
int op_writeop(pcdpladdr_t);
void op_diskop(pcplqaddr_t);
int op_io(int, int, int, int, int, int, char far *);
int op_open(int, int);
int op_close(int);
char op_base();

/*
 * our DOS interface routines
 * we don't use the standard Microsoft Routines because:
 * 1. they don't exist on all compilers (such as the IBM C1.0 compiler)
 * 2. there are bugs (dos_read and dos_write don't set bytes as documented)
 * 3. the dos_lseek function is missing
 */
int dos_read(int, char far *, unsigned, unsigned int *);
static int dos_write(int, char far *, unsigned, unsigned int *);
static int dos_open(char far *, int, int *);
static int dos_creat(char far *, int, int *);
long dos_lseek(int, long, int);
static int dos_close(int);



/*************************************************************
 * op_diskop(dplqe) - Do an optical disk operation
 *
 *  This routine is called from the main program to do an optical disk 
 *  operation. 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 appropiate
 *  routine to handle the specific disk operation.
 *
 */

void 
op_diskop(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         opstat;
	u_long		size;

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

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

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

	DEBUGF(ldebug, printf("diskop: pcplqe = %#lx\n", (u_long) pcplqe));
	DEBUGF(ldebug, printf("diskop: 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_DISK, 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("diskop: %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 = 0;			/* used to tell if we're alive */
		break;

	case DISKOP_OPEN:		/* open the drive */
		rc = op_open(drive,dplptr->number);
		if (rc == 0) {
			size = dos_lseek(ophandle[drive], 0L, 2) >> BSHIFT;
			op_return_code[drive].badblock = exchl(size);
		}
		break;

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

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

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

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


dio_exit:


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

	rc = exchw(rc);

	s = spl();
	op_return_code[drive].errno = rc;
	rompint3(3,FAKEPOLL(OPIRQ), 0, 0, cur_cbcb);

	splx(s);
}

/*
 * op_io()   - Send a request off to DOS
 *
 */

int op_io(op, drive, head, track, sector, num, buff)
	int             op;
	int             drive;
	int             head;
	int             track;
	int             sector;
	int             num;
	char far       *buff;	/* Data buffer for operation */
{
	int             rc;
	int		handle = ophandle[drive];
	unsigned int	bytes;	/* number of bytes written */
	long		pos;


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

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


	if (handle <= 0)
		return(BIOS_ERROR);

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

	pos = (((long) (unsigned) sector) << BSHIFT) |
	      (((long) (unsigned) head) << (BSHIFT+16));
	(void) dos_lseek(handle, pos , 0);

	if (op == DISKOP_READ)
		rc = dos_read(handle, buff, (unsigned) num << BSHIFT, &bytes);
	else
		rc = dos_write(handle, buff, (unsigned) num << BSHIFT, &bytes);

	DEBUGF(ldebug, printf("op_io: pos=%lx rc=%x bytes=%x\n",pos,rc,bytes));

	if (rc != 0 || bytes == 0)
		rc |= BIOS_ERROR;		/* flag error */
	op_return_code[drive].pad = exchw(bytes);	/* return bytes read/written */
	return (rc);
}

/*  op_diskio() - Do multi track reads on the optical disk
 *
 *
 */
int op_diskio(dpl)
	pcdpladdr_t     dpl;
{
	register int    num, sec;
	u_short         drive, hd, trk, ontrk, rc, op, maxsec, maxhd;
	char far       *cptr;
#ifdef DEBUG
	int             ldebug = *dbugptr & OPDISKDEBUG;
#endif /* DEBUG */

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

	rc = op_io(op, drive, hd, trk, sec, num, cptr);
	return (rc);
}


#ifdef notdef
/*
 * getopgeometry() - Find out what type of drive we have.
 *
 * Input: drive - Bios drive number of disk
 *        opstat - Place status information here.
 *
 * Global Data Areas:
 *
 */

int getopgeometry(drive, opstat)
	int             drive, *opstat;
{

	register int    i;

	int             rc, media, maxsector;

	char far       *bp = (char far *) buff_dio;

	char           *cp1, *cp2;

	struct drvparm *drvparm;

	union REGS      inregs, outregs;

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


	DEBUGF(ldebug, printf("getopgeometry: drive %#x\n", drive));

	cp1 = (char *) &inregs;
	cp2 = (char *) &outregs;

	for (i = 0; i < sizeof(union REGS); i++)
	{
		*cp1++ = 0;
		*cp2++ = 0;
	}

	if (opstat != (int *) 0)
		*opstat = 0;

	inregs.h.ah = 0x08;	/* Get drive Parameters */
	inregs.h.dl = drive;	/* Drive Number          */
	int86(0x13, &inregs, &outregs);

	/*
	 * On return from BIOS interrupt 0x13 function # 0x08: 
	 *
	 * dl : Number of consecutive acknowledging drives attached dh:  Max
	 * useable value for head ch:  Max useable value for cylinder cl: 
	 * Max useable value for sector is low order 6 bits the high order 2
	 * bits are used as the high order 2 bits for the cylinder 
	 */

	if (outregs.x.cflag)
	{
		DEBUGF(ldebug, printf("Drive %#x not present, or failed! ax = %#x\n", drive,
				      outregs.x.ax));
		return (-1);
	}
	maxsector = (outregs.h.cl & 0x3f);


	if ((unsigned) drive < NOP)
	{
		drvparm = &opparm[drive];
		drvparm->maxop = outregs.h.dh;
		drvparm->maxsec = (outregs.h.cl & 0x3f);
		drvparm->maxcyl = XTRACTCYL(outregs.h.ch, outregs.h.cl);
#ifdef DEBUG
		if (ldebug)
			printf("getopg: maxop = %#x   maxsec = %#x   maxcyl = %#x\n",
			  drvparm->maxop, drvparm->maxsec, drvparm->maxcyl);
#endif /* DEBUG */


	} else
		printf("pccode: Unknown drive number %x\n", drive);
}
#endif


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

int op_readop(dpl)
	pcdpladdr_t     dpl;
{
	int             rc;
	char far       *pcaddr = buff_dio;

	rc = op_diskio(dpl);
	if (rc > -1)
		rc = copy_data_out(pcaddr, dpl);
	return (rc);
}

/*
 * op_writeop()  - Copy the data down from Unix, then do the disk op
 *
 */
int op_writeop(dpl)
	pcdpladdr_t     dpl;
{
	int             rc;
	char far       *pcaddr = buff_dio;


	if ((rc = copy_data_in(pcaddr, dpl)) >= 0)
		rc = op_diskio(dpl);
	return (rc);
}

#ifndef O_RDWR
#define O_RDWR	2
#endif
#ifndef ENOENT
#define ENOENT	2
#endif

char basename;		/* base optical drive letter */

/*
 * open the optical drive "drive".
 * if the file already exists we open it for update, otherwise we create
 * it.
 */
int op_open(drive,how)
	int		drive, how;
{
	char name[128];
	int err_code;
	extern int	errno;
	static int op_field_count;

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

	if (ophandle[drive] > 0)
		op_close(drive);			/* close it */

	if (basename == 0)
		{
		basename = op_base();			/* ask for it */
		op_field_count = field_count(opdisk_name);
		}

	if (op_field_count > 1)
		sprintf(name,opdisk_name,
			basename + OPUNIT(drive), OPPART(drive));  /* build name */
	else
		sprintf(name,opdisk_name, drive);  /* build name */

	DEBUGF(ldebug, printf("op_open: drive=%x how=%x name=%s ...",
		drive,how,name));
	if ((err_code = dos_open(name, O_RDWR, &ophandle[drive])) != 0 &&
			errno == ENOENT && how != 0)
		err_code = dos_creat(name, 0, &ophandle[drive]);

	if (err_code != 0)
		err_code |= BIOS_ERROR;
	DEBUGF(ldebug, printf(" err_code=%x handle=%d\n",err_code,ophandle[drive]));
	return(err_code);
}

int op_close(drive)
	int		drive;
{
	int		err_code;
	err_code = dos_close(ophandle[drive]);
	ophandle[drive] = 0;
	if (err_code)
		err_code |= BIOS_ERROR;
	return(err_code);
}

long dos_lseek(handle, pos, how)
	int		handle;
	long		pos;
	int		how;
{
	union REGS inregs, outregs;

	inregs.h.ah = 0x42;
	inregs.h.al = how;
	inregs.x.cx = pos>>16;
	inregs.x.dx = pos;
	inregs.x.bx = handle;
	intdos(&inregs, &outregs);

	return(((long) outregs.x.dx << 16) | outregs.x.ax);
}

static int dos_close(handle)
	int		handle;
{
	union REGS inregs, outregs;

	inregs.h.ah = 0x3e;
	inregs.x.bx = handle;
	intdos(&inregs, &outregs);
	if (outregs.x.cflag)
		return(outregs.x.ax);
	return(0);
}

static int dos_open(name, how, handle)
	char far       *name;
	int		how;
	int	       *handle;
{
	union REGS inregs, outregs;
	struct SREGS	segregs;

	inregs.h.ah = 0x3d;
	inregs.h.al = how;
	segregs.ds = FP_SEG(name);
	inregs.x.dx = FP_OFF(name);
	intdosx(&inregs, &outregs, &segregs);
	if (outregs.x.cflag) {
		int err = outregs.x.ax;
		extern int errno;

		inregs.h.ah = 0x59;		/* get extended error */
		intdosx(&inregs, &outregs, &segregs);
		errno = outregs.x.ax;
		return(err);
	}
	*handle = outregs.x.ax;			/* return handle */
	return(0);	/* worked */
}

static int dos_creat(name, attr, handle)
	char far       *name;
	int		attr;
	int	       *handle;
{
	union REGS inregs, outregs;
	struct SREGS	segregs;

	inregs.h.ah = 0x3c;
	segregs.ds = FP_SEG(name);
	inregs.x.dx = FP_OFF(name);
	inregs.x.cx = attr;
	intdosx(&inregs, &outregs, &segregs);
	if (outregs.x.cflag) {
		int err = outregs.x.ax;
		extern int errno;

		inregs.h.ah = 0x59;		/* get extended error */
		intdosx(&inregs, &outregs, &segregs);
		errno = outregs.x.ax;
		return(err);
	}
	*handle = outregs.x.ax;			/* return handle */
	return(0);	/* worked */
}

int dos_read(handle, buff, length, bytes)
	int		handle;
	char far       *buff;
	unsigned	length;
	unsigned int   *bytes;
{
	union REGS inregs, outregs;
	struct SREGS	segregs;

	inregs.h.ah = 0x3f;	/* DOS read */
	segregs.ds = FP_SEG(buff);
	inregs.x.dx = FP_OFF(buff);
	inregs.x.bx = handle;
	inregs.x.cx = length;
	intdosx(&inregs, &outregs, &segregs);
	if (outregs.x.cflag) {
		*bytes = 0;		/* no bytes read */
		return(outregs.x.ax);	/* error code */
	}
	*bytes = outregs.x.ax;		/* bytes read */
	return(0);		/* all ok */
}

static int dos_write(handle, buff, length, bytes)
	int		handle;
	char far       *buff;
	unsigned	length;
	unsigned int   *bytes;
{
	union REGS inregs, outregs;
	struct SREGS	segregs;

	inregs.h.ah = 0x40;	/* DOS write */
	segregs.ds = FP_SEG(buff);
	inregs.x.dx = FP_OFF(buff);
	inregs.x.bx = handle;
	inregs.x.cx = length;
	intdosx(&inregs, &outregs, &segregs);
	if (outregs.x.cflag) {
		*bytes = 0;		/* no bytes written */
		return(outregs.x.ax);	/* error code */
	}
	*bytes = outregs.x.ax;		/* bytes written */
	return(0);		/* all ok */
}


/*
 * get the base letter for the optical drive (one past the
 * number of hard disks present.
 */
char op_base()
{
	union REGS      inregs, outregs;

	bzero(&inregs, sizeof (union REGS));
	bzero(&outregs, sizeof (union REGS));

	inregs.h.ah = 0x08;	/* Get drive Parameters */
	inregs.h.dl = 0x80;	/* Drive Number          */
	int86(0x13, &inregs, &outregs);


	if (outregs.x.cflag)
		return('C');	/* we'll assume its c */

	return('C' + outregs.h.dl);
}

/*
 * scan thru name to find out how many printf fields have
 * been specified.
 */
static field_count(name)
char *name;
{
	char *p;
	int count;

	for (p=name, count=0; *p; )
		if (*p++ == '%' && *p && *p++ != '%')
			++count;
	return(count);
}
