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

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

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


#ifndef ABIOS
#include <dos.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"

extern struct rb_part rb_part[];

/*
 * Characteristics of drives attached to system
 *   Used by: loadboot(), getdiskgeometry(), diskio()
 */
struct drvparm  fdparm[NFD] =
{
 0
};

struct drvparm  hdparm[NHD] =
{
 0
};


/*
 * 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     hdr_buff[4 + NHD * NCPU * sizeof (struct dio_rtn_codes)];
static char     fdr_buff[4 + NFD * NCPU * sizeof (struct dio_rtn_codes)];

struct dio_rtn_codes *hd_return_code[NCPU] = {
	(struct dio_rtn_codes *) hdr_buff };
struct dio_rtn_codes *fd_return_code[NCPU] = {
	(struct dio_rtn_codes *) fdr_buff };

/*
 * Diskette Parameters: This vector points to a data region containing
 * the parameters required for the diskette drive. The power-on routines
 * initialize this vector to point to the parameters contained in the ROM
 * diskette routine. These default parameters represent the specified
 * values for any IBM drives attached to the system. Changing this
 * parameter block may be necessary to reflect the specifications of other
 * drives attached.
 *
 *    The following two structures can be found in bios.h
 */

struct dbt far *dbt = {0};



/*************************************************************
 * diskop(dplqe) - Do a disk operation
 *
 *  This routine is called from the main program to do a disk (floppy
 *  or fixed) 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 
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         fdstat;

#ifdef DEBUG
	int             ldebug = *dbugptr & DISKDEBUG;
#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 (ISFLOPPY(drive))
	{
		i = dplptr->op_code;
		if ((i == DISKOP_READ) || (i == DISKOP_WRITE))
		{
			dbt = (struct dbt far *) pcmem[DISK_BASE].vector;
			dbt->last_sec = fdparm[drive].maxsec;
		}
	}
	switch (dplptr->op_code)
	{
	case DISKOP_RESET:
		rc = RESET(drive);
		if ((drive >= 0x80) && (rc >= 0))
			bbtinit(drive);
		break;
	case DISKOP_STATUS:
		fdstat = 0;
		rc = getdiskgeometry(drive, (int *)&fdstat);
		if (rc > 0)
			rc = fdstat;
		break;

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

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

	case DISKOP_VERIFY:
		rc = verifyop(dplptr);
		break;

	case DISKOP_SMT:
		rc = smt(dplptr);
		break;

	case DISKOP_FORMAT:
		rc = format(dplptr);
		break;

	default:
		break;
	}


dio_exit:


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

	rc = exchw(rc);

	s = spl();
	if (ISFLOPPY(drive))
	{
		fd_return_code[cur_cbcb->cpu][drive].errno = rc;
		rompint3(3,FAKEPOLL(FDIRQ), 0, 0, cur_cbcb);
	} else
	{
		drive -= 0x80;
		hd_return_code[cur_cbcb->cpu][drive].errno = rc;
		rompint3(3,FAKEPOLL(HDIRQ), 0, 0, cur_cbcb);

	}
	splx(s);
}

/*
 * io()   - Send a request off to BIOS, take care of retries if error
 *
 */

int 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             cnt = 0, rc, retry, floppy = (drive < 2);


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

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

	if (floppy)
		retry = FLOPPY_RETRY_COUNT;
	else
		retry = HD_RETRY_COUNT;

	DEBUGF(ldebug, printf("io: retry set to %d\n", retry));

	/*
	 * Send the request off to     BIOS. If it fails reset the drive and
	 * try it again. Do this until cnt reaches retry. 
	 */

	do
	{
		/*
		 * write dos partitions through int26 so dos buffers see the
		 * results.
		 */
#ifdef DOS_FLOPPY
		if (op == DISKOP_WRITE) {
			int	hd_unit = drive & 3;

			/* floppies are always dos partitions */
			if (floppy) {
				long	block = chs2blk(drive,track,head,sector);
				
				rc = int26(drive,num,block,buff);
			/* hard disk partition. */
			} else 
# else
		if (op == DISKOP_WRITE && !floppy) {
			int	hd_unit = drive & 3;
#endif DOS_FLOPPY
				/*
				 * are we in the dos partition?
				 */
			     if ((track >= rb_part[hd_unit].dos_start) && 
					(track <= rb_part[hd_unit].dos_end)) {
				 /* yes?? */
				long  block = chs2blk(drive,
				 track-rb_part[hd_unit].dos_start,head,sector);

				/* cylinder 0 is special for dos */
				if (rb_part[hd_unit].dos_start == 0) {
					block = block - chs2blk(drive,0,1,1);
				}
				/* if block is negative, we are writing in
				 * the special blocks in the front of the disk
				 * (master boot record...)
				 */
				if (block >= 0) {
					rc = int26(hd_unit+2,num,block,buff);
				} else {
					/*
					 * stuff on the "Dos Cyl", but first
					 * track only..
					 */
					rc = int13(op, drive, head, track,
							 sector, num, buff);
				}
			} else {
				/* not in a dos partition */
				rc = int13(op, drive, head, track, sector, 
								num, buff);
			}
		} else {
			/* for all other operations ignore dos buffers.. */
			rc = int13(op, drive, head, track, sector, num, buff);
		}
		if (rc < 0)
		{
			DEBUGF(ldebug, printf("io: op failed! rc = %#x\n", rc));

			if ((rc & 0x0f) == 0x0a || (op == DISKOP_VERIFY))
				cnt = retry;
			else
				int13(DISKOP_RESET, drive, head, track, sector, num, buff);

		}
	} while ((rc < 0) && (++cnt < retry));

	/*
	 * If hard disk operations reach the retry count, then we check to
	 * see if we need to do some bad block processing 
	 *
	 */

	if ((!floppy) && (cnt >= retry))
		if ((op == DISKOP_READ) || (op == DISKOP_WRITE) || (op == DISKOP_VERIFY))
			rc = procbbt(op, drive, head, track, sector, num, buff, rc);

	return (rc);
}

/*  diskio() - Do multi track reads on the diskette, send hard file
 *             requests directly to io()
 *
 *
 */
int 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 & DISKDEBUG;
#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;

	if (ISFLOPPY(drive))
	{

#ifdef DEBUG
		if (ldebug)
		{
			printf("diskio: Values from fdparm for drive %d:\n", drive);
			printf("        maxhd  = %d\n", fdparm[drive].maxhd);
			printf("        maxsec = %d\n", fdparm[drive].maxsec);
			printf("        maxcyl = %d\n", fdparm[drive].maxcyl);
			printf("diskio: op= %#x, hd= %#x, trk= %#x, sec= %#x, num= %#x\n",
			       op, hd, trk, sec, num);
			printf("diskio: use disk buffer at %#lx\n", (char far *) cptr);
		}
#endif /* DEBUG */

		maxsec = fdparm[drive].maxsec;
		maxhd = fdparm[drive].maxhd;

		while (num > 0)
		{
			/*
			 * Check to see if the operation spans a track. 
			 */
			ontrk = maxsec - sec + 1;
			DEBUGF(ldebug, printf("diskio: ontrk = %d\n", ontrk));

			if (ontrk >= num)	/* Does not span track, only
						 * requires 1 op */
			{
				rc = io(op, drive, hd, trk, sec, num, cptr);
				num = 0;
			}
			 /* End ontrk >= num */ 
			else
			{
				/*
				 * The request spans a track. Break the
				 * operation up so that we do them a track at
				 * a time 
				 */

#ifdef DEBUG
				if (ldebug)
				{
					printf("diskio: Request spans track\n");
					printf("diskio: op=%#x, hd=%#x, trk=%#x, sec=%#x, num=%#x\n",
					       op, hd, trk, sec, num);
					printf("diskio: use disk buffer at %#lx\n", cptr);
				}
#endif /* DEBUG */
				rc = io(op, drive, hd, trk, sec, ontrk, cptr);
				num -= ontrk;
				sec = 1;
				if (++hd > maxhd)
				{
					hd = 0;
					trk++;
				}
				cptr += ontrk * SECSIZE;
			}	/* End else */
			if (rc < 0)
				break;
		}		/* End while (num > 0) */
	}
	 /* End if(FLOPPY)) */ 
	else			/* Hard disk */
	{
		rc = io(op, drive, hd, trk, sec, num, cptr);
	}
	return (rc);
}


/*
 * getdiskgeometry() - Find out what type of drive we have.
 *
 * Input: drive - Bios drive number of disk
 *        fdstat - Place status information here.
 *
 * Global Data Areas:
 *
 *       Fills in either hdparm or fdparm based on drive number.
 *
 */

int getdiskgeometry(drive, fdstat)
	int             drive, *fdstat;
{

	register int    i;

	int             rc, media, maxsector;

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


	struct drvparm *drvparm;

	union REGS      inregs, outregs;

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


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


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

	if (fdstat != (int *) 0)
		*fdstat = 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));
		if (ISFLOPPY(drive) && (option_flag & OPTION_AT)) {
			outregs.h.cl = 15;	/* 15 sectors */
			outregs.h.ch = 80-1;	/* 80 tracks */
			outregs.h.bl = HCDD;	/* high capacity */
			outregs.h.dl = 1;	/* assume one drive */
			outregs.h.dh = 2-1;	/* 2 heads */
		} else
			return (-1);
	}
	maxsector = (outregs.h.cl & 0x3f);

	if (ISFLOPPY(drive))
	{

		/*
		 * Read track 1 to find out if diskette is present. 
		 */

		rc = io(DISKOP_READ, drive, 0, 0, 1, 1, bp);
		if (rc < 0)
		{
			DEBUGF(ldebug, printf("getdiskgeometry: Read FAILED!\n"));
			return (-1);
		}
		/*
		 * Write Track 1 to find out if it is write protected. 
		 */

		rc = io(DISKOP_WRITE, drive, 0, 0, 1, 1, bp);
		if (rc < 0)
		{
			if (fdstat != (int *) 0)
				*fdstat |= FDWP;
			DEBUGF(ldebug, printf("getdiskgeometry: Write Protected\n"));
		}
		DEBUGF(ldebug, printf("getdiskgeo: Try to read sec %d\n", maxsector));

		rc = io(DISKOP_READ, drive, 0, 0, maxsector, 1, bp);
		if (rc < 0)
		{
			DEBUGF(ldebug, printf("getdiskgeometry: Read FAILED!\n"));
			outregs.h.cl &= 0xc0;
			outregs.h.cl |= 9;
			maxsector = 9;
		}
		media = outregs.h.bl & 0x0f;
		DEBUGF(ldebug, printf("getdiskgeometry: BIOS media type = %d\n", media));
		switch (media)
		{
		case DSDD:
			i = FD5LO;
			break;

		case HCDD:
			if (maxsector == 9)
				i = FD5LO;
			else
				i = FD5HI;
			break;

		case PSDDLO:
			i = FD3LO;
			break;

		case PSDDHI:
			if (maxsector == 9)
				i = FD3LO;
			else
				i = FD3HI;
			break;

		default:
			printf("pccode: Unknown media type %x, default to 720K\n");
			i = FD3LO;
			break;
		}		/* End switch */

		if (fdstat != (int *) 0)
			*fdstat |= i;
	}
	drvparm = (struct drvparm *) 0;
	switch (drive)
	{
	case 0:
	case 1:
	case 2:
	case 3:
		drvparm = &fdparm[drive];
		break;

	case 0x80:
		if (outregs.h.dl >= 1)
			drvparm = &hdparm[drive - 0x80];
		break;

	case 0x81:
		if (outregs.h.dl >= 2)
			drvparm = &hdparm[drive - 0x80];
		break;

	default:
		break;
	}

	if (drvparm != (struct drvparm *) 0)
	{
		drvparm->maxhd = outregs.h.dh;
		drvparm->maxsec = (outregs.h.cl & 0x3f);
		drvparm->maxcyl = XTRACTCYL(outregs.h.ch, outregs.h.cl);
#ifdef DEBUG
		if (ldebug)
			printf("getdiskg: maxhd = %#x   maxsec = %#x   maxcyl = %#x\n",
			  drvparm->maxhd, drvparm->maxsec, drvparm->maxcyl);
#endif /* DEBUG */


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


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

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

	rc = diskio(dpl);
	if (rc > -1)
		rc = copy_data_out(pcaddr, dpl);

	return (rc);
}

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

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

	}
	return(rc);
}

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


	if ((rc = copy_data_in(pcaddr, dpl)) >= 0)
		rc = diskio(dpl);

	return (rc);
}

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

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


/*
 *  Do a disk read-verify
 *  Note that we do NOTHING with the data buffer from unix!
 */

verifyop(dpl)
	pcdpladdr_t     dpl;
{
	int             rc;

	rc = diskio(dpl);

	return (rc);
}

/*
 * Set the diskette media type.
 *
 */

int smt(dpl)
	pcdpladdr_t     dpl;
{
	register int    rc;

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

	DEBUGF(ldebug, printf("smt: drive = %#x, numtrk = %#x, nspt = %#x\n",
			      dpl->drive, dpl->cyl, dpl->number));

	rc = io(dpl->op_code, dpl->drive, 0, dpl->cyl, dpl->number, 0, 0);

	dbt = (struct dbt far *) pcmem[DISK_BASE].vector;
	dbt->last_sec = dpl->number;

#ifdef DEBUG
	if (ldebug)
	{
		if (rc < 0)
			printf("smt failed! rc = %#x\n", rc & 0xff);
		else
		{
			printf("Disk Base Table Values:\n");
			printf("\tSectors per track = %#x\n", dbt->last_sec);
			printf("\tGap len for format = %#x\n", dbt->gap_len_fmt);
		}

	}
#endif /* DEBUG */

	return (rc);

}

int format(dpl)
	pcdpladdr_t     dpl;
{
	u_short         num, cyl, head, drive, op;

	int             rc;

	char far       *pcaddr = buff_dio;


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

	op = dpl->op_code;
	drive = dpl->drive;
	cyl = dpl->cyl;
	head = dpl->head;
	num = dpl->number;

	DEBUGF(ldebug, printf("fmt: drive = %#x, cyl = %#x, hd = %#x, num = %#x\n",
			      drive, cyl, head, num));

	if ((rc = copy_data_in(pcaddr, dpl)) < 0)
		return(rc);

#ifdef DEBUG
	if (ldebug)
	{
		register int    i, j;
		j = 40;
		printf("fmt: First %d bytes of buffer\n", j);
		for (i = 0; i < j; i++)
		{
			printf("%02x ", pcaddr[i]);
		}
		printf("\n");
	}
#endif /* DEBUG */

	rc = io(op, drive, head, cyl, 0, num, pcaddr);

	DEBUGF(((rc < 0) && (ldebug)), printf("fmt: format failed rc = %#x\n",
					      rc & 0xff));


	return (rc);

}
#endif ABIOS
