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

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

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


#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 "vars.h"

extern struct rb_part rb_part[];
extern char far *buff_dio;
extern struct drvparm hdparm[];
extern struct drvparm fdparm[];
extern struct dio_rtn_codes *hd_return_code[];	/* In dio.c   */

#ifndef ABIOS
u_long          chs2blk();
#endif /* ABIOS */


struct hdbad
{
	char            defect[6];
	u_short         count;
	struct hdmap
	{
		u_long          hdbad;
		u_long          hdgood;
	}               hdmap[MAXBADBLOCKS];
};


struct bbt      bbt[NHD] = {0};
struct bbt far *bbtp = (struct bbt far *) bbt;
int             bbprocessing = FALSE;

char            defect_flag[] = "DEFECT";

/*********************************************************************
 *
 * bbtinit - Get the bad block table off of the disk.
 *
 *    This routine starts off by verifying that a bad block table exists
 *    by checking for the string DEFECT in the first 6 bytes of the bad
 *    block map. If one does exist we read it into memory and convert
 *    the absolute block number of the BAD BLOCK to Cyl, Hd, Sec values.
 *    This make bad block forarding work a little faster since this calculation
 *    is only performed once (at startup, when things are expected to take
 *    a little longer) for each bad block look up.
 */
#ifdef ABIOS
int bbtinit(drive,hdbad)
	u_short         drive;
	struct hdbad far *hdbad;
{
	register int    i;
	int             rval = -1;
	u_long		bad_block_loc;
	struct bbt     *bbtp;
	u_long          bblock, gblock;
	u_short nspc = hdparm[drive].maxsec * (hdparm[drive].maxhd + 1);

#ifdef DEBUG
	u_short         ldebug = *dbugptr & BBTDEBUG;
#endif /* DEBUG */

/*
 * First check the defect map to see if there is a bad block table
 * present. The defect map starts at BOOTCYLS cylinders into the
 * the 4.2A partition at sector 8.
 *
 */
		if ((hdbad->defect[0] == 'D') && (hdbad->defect[1] == 'E')
		 && (hdbad->defect[2] == 'F') && (hdbad->defect[3] == 'E')
		 && (hdbad->defect[4] == 'C') && (hdbad->defect[5] == 'T'))
		{
			bbtp = (struct bbt *) & bbt[drive];
			if ((bbtp->numbad = exchw(hdbad->count)) > MAXBADBLOCKS)
				bbtp->numbad = MAXBADBLOCKS;

			DEBUGF(ldebug, printf("bbtinit: DEFECT found, %d entries\n", bbtp->numbad));
			DEBUGF(ldebug, printf("bbtinit: relative bad block map:\n"));
			/*
			 * For each good block convert it from an absolute
			 * block number to a geometric disk address. 
			 */

			for (i = 0; i < bbtp->numbad; i++)
			{
				bblock = exchl(hdbad->hdmap[i].hdbad) & 
								0x00ffffff;
				gblock = exchl(hdbad->hdmap[i].hdgood) & 
								0x00ffffff;
				DEBUGF(ldebug,
					printf("%ld -> %ld)\n",bblock, gblock);
				)
				bbtp->bbt_entry[i].hdbad = bblock + 
						(rb_part[drive].pstart * nspc);
				bbtp->bbt_entry[i].hdgood = gblock + 
						(rb_part[drive].pstart * nspc);
			}
			rval = 0;
		}
#ifdef DEBUG
		else {
			if (ldebug) {
				printf("bbtinit: NO DEFECT MAP PRESENT\n");
			}
			bbt[drive].numbad = 0;
		}
#endif /* DEBUG */
	return (rval);
}
#endif /* ABIOS */

#ifndef ABIOS
int bbtinit(drive)
	u_short         drive;
{
	register int    i;
	int             j;
	u_short         drv, cyl, hd, sec, bbcnt;
	int		rval = -1;
	struct hdgood  *hdg;
	struct hdbad far *hdbad = (struct hdbad far *) buff_dio;
	struct bbt     *bbtp;
	u_long          bblock, gblock;
	u_short         nspc;
	struct drvparm *hdp;

#ifdef DEBUG
	u_short         ldebug = *dbugptr & BBTDEBUG;
#endif /* DEBUG */

/*
 * First read the defect map to see if there is a bad block table
 * present. The defect map starts at BOOTCYLS cylinders into the
 * the 4.2A partition at sector 8.
 *
 */

	DEBUGF(ldebug, printf("bbtinit: drive = %#x\n", drive));
	drv = drive - 0x80;
	hdp = (struct drvparm *) & hdparm[drv];
	nspc = hdp->maxsec * (hdp->maxhd + 1);
	cyl = rb_part[drv].pstart;
	sec = 9;
	if (io(DISKOP_READ, drive, 0, cyl, sec, 1, (char far *) hdbad) >= 0)
	{

		if ((hdbad->defect[0] == 'D') && (hdbad->defect[1] == 'E')
		 && (hdbad->defect[2] == 'F') && (hdbad->defect[3] == 'E')
		 && (hdbad->defect[4] == 'C') && (hdbad->defect[5] == 'T'))
		{
			bbtp = (struct bbt *) & bbt[drv];
			if ((bbcnt = exchw(hdbad->count)) > MAXBADBLOCKS)
				bbcnt = MAXBADBLOCKS;

			DEBUGF(ldebug, printf("bbtinit: DEFECT found, %d entries\n", bbcnt));
			DEBUGF(ldebug, printf("bbtinit: relative bad block map:\n"));
			j = 0;
			bbtp->numbad = 0;

			/*
			 * For each good block convert it from an absolute
			 * block number to a geometric disk address. 
			 */

			for (i = 0; i < bbcnt; i++)
			{
				hdg = (struct hdgood *) & (bbtp->bbt_entry[i].hdgood);
				bblock = exchl(hdbad->hdmap[i].hdbad) & 0x00ffffff;
				gblock = exchl(hdbad->hdmap[i].hdgood) & 0x00ffffff;

				/*
				 * If we can't convert it don't put it in the
				 * table. 
				 */

				if (blk2chs(drive, gblock, &cyl, &hd, &sec) >= 0)
				{
					bbtp->bbt_entry[j++].hdbad = bblock + (rb_part[drv].pstart * nspc);
					hdg->cyl = cyl + rb_part[drv].pstart;	/* add in c part offset */
					hdg->sec = sec;
					hdg->hd = hd;
#ifdef DEBUG
					if (ldebug)
						printf("%d:  %ld -> %ld (cyl %d, hd %d  sec %d)\n",
						       j, bblock, gblock, cyl, hd, sec);
#endif /* DEBUG */
				} else
					DEBUGF(ldebug, printf("bbtinit: Can't Convert block %#lx\n", gblock));
			}
			bbtp->numbad = j;
			rval = 0;
		}
#ifdef DEBUG
		else {
			if (ldebug) {
				printf("bbtinit: NO DEFECT MAP PRESENT\n");
			}
		}
#endif /* DEBUG */
	}
	return (rval);
}

/*
 *
 *
 */
int procbbt(op, drive, hd, cyl, sec, num, buff, rc)
	int             op, drive, hd, cyl, sec, num, rc;
	char far       *buff;

{
	int             rval;
	u_short         ncyl, nhd, nsec, drv, goodblks;
	struct bbt     *bbtp;
	u_long          errorblk, startblk, blk;
#ifdef DEBUG
	u_short         ldebug = *dbugptr & BBTDEBUG;
#endif /* DEBUG */

	rval = rc;

	bbprocessing = TRUE;	/* Seg global flag, used in io() */

	DEBUGF(ldebug, printf("procbbt:\n"));

	if ((drv = drive - 0x80) < NHD)
	{
		bbtp = (struct bbt *) & bbt[drv];

		ncyl = cyl;
		nhd = hd;
		nsec = sec;

		/*
		 * Figure out the block numbers of where the transfer
		 * started, where it ended, and where it failed. 
		 */

		do
		{
			startblk = chs2blk(drive, ncyl, nhd, nsec);
			errorblk = det_badsec(drive, ncyl, nhd, nsec, num);

			if (errorblk == 0)
			{
				goodblks = 0;
				blk = startblk;
			} else
			{
				goodblks = errorblk - startblk;
				blk = errorblk;
			}

#ifdef DEBUG
			if (ldebug)
			{
				printf("\tTransfer Started At Block  # %#lx\n", startblk);
				printf("\tBad Block Located At Block # %#lx\n", errorblk);
				printf("\tNum Good Blocks = %#x\n", goodblks);
			}
#endif /* DEBUG */

			hd_return_code[cur_cbcb->cpu][drv].badblock = exchl(blk);

			if ((bbtp->numbad <= 0) || (errorblk == 0))
			{
				DEBUGF(ldebug, printf("Exit From procbbt\n"));
				return (rval);
			}
			DEBUGF(ldebug, printf("Bad Block Processing: Calling badblklookup\n"));
			DEBUGF(ldebug, printf("Original error = %x\n", rval));

			if (badblklookup(errorblk, bbtp, &ncyl, &nhd, &nsec))
			{
				buff += goodblks * SECSIZE;

				/*
				 * Now get the good block. 
				 */

				DEBUGF(ldebug, printf("\tRemap Bad Block\n"));

				rval = bbio(op, drive, nhd, ncyl, nsec, 1, buff);
				if (rval < 0)
				{
					DEBUGF(ldebug, printf("Error Reading good block!, Exit\n"));
					return (rval);
				}
				/*
				 * Adjust the cyl, hd, sec, num and buff
				 * values so that we can finish the request. 
				 */

				buff += SECSIZE;
				blk = errorblk + 1;
				num -= (goodblks + 1);
				blk2chs(drive, blk, &ncyl, &nhd, &nsec);
#ifdef DEBUG
				if (ldebug)
				{
					if (num > 0)
					{
						printf("Restart at Cyl %#x Hd %#x Sec %#x\n", ncyl, nhd, nsec);
						printf(" num = %#x, Buff = %#lx\n", num, buff);
					} else
						printf("No More blocks to process\n");
				}	/* End if(ldebug) */
#endif /* DEBUG */

				if (num > 0)
					rval = bbio(op, drive, nhd, ncyl, nsec, num, buff);

			}
			 /* End if(badblklookup() */ 
			else
			{	/* Entry not in bad block table */
				DEBUGF(ldebug, printf("No bad block found!, Exit\n"));
				return (rval);
			}

		} while ((num > 0) && (rval < 0));

	}			/* End if(drv = .. */
	return (rval);
}

/*************************************************************************
 * blk2chs - Convert an absolute block number to a geometric disk address
 *
 *
 */

int blk2chs(drv, blk, cyl, hd, sec)
	u_short         drv;
	u_long          blk;
	u_short        *cyl, *hd, *sec;

{
	register u_short nspc, numsec, numhd;
	u_long          lsec;
	struct drvparm *hdp;
	int             rc = -1;

	drv -= 0x80;

	if (drv < NHD)
	{
		hdp = (struct drvparm *) & hdparm[drv];
		numhd = hdp->maxhd + 1;
		numsec = hdp->maxsec;

		lsec = blk;
		nspc = numhd * numsec;	/* Number of sectors per cylinder */
		lsec %= nspc;

		*hd = (u_short) (lsec / numsec);
		*cyl = (u_short) (blk / nspc);
		*sec = (u_short) ((lsec % numsec) + 1);

		rc = 0;
	}
	return (rc);
}






/*************************************************************************
 * chs2blk - Convert a geometric disk address to an absolute block number.
 *
 *
 */

u_long
chs2blk(drv, cyl, hd, sec)
	u_short         drv, cyl, hd, sec;

{
	register struct drvparm *hdp;
	u_long          numsec, numhd, blk;

	blk = -1;
	if (drv & 0x80) {
		drv -= 0x80;
		if (drv < NHD) {
			hdp = (struct drvparm *) & hdparm[drv];
		} else {
			return(blk);
		}
	} else {
		if (drv < NFD) {
			hdp = (struct drvparm *) & fdparm[drv];
		} else {
			return(blk);
		}
	}
	numhd = hdp->maxhd + 1;
	numsec = hdp->maxsec;

	blk = numhd * numsec * cyl;
	blk += hd * numsec;
	blk += (sec - 1);
	return (blk);
}
#endif /* ABIOS */

#ifndef ABIOS
int badblklookup(blk, bbtp, ncyl, nhd, nsec)
	u_long          blk;
	struct bbt     *bbtp;
	u_short        *ncyl, *nhd, *nsec;
#else  /* ABIOS */
int badblklookup(unit, blk, goodblk)
	u_short		unit;
	u_long          blk;
	u_long         *goodblk;
#endif /* ABIOS */
	
{
#ifdef ABIOS
	struct	bbt	*bbtp = &bbt[unit];
#endif /* ABIOS */
	register int    i, j;
	int             rval = 0;
	struct hdgood  *hdg;
#ifdef DEBUG
	u_short         ldebug = *dbugptr & BBTDEBUG;
#endif /* DEBUG */

	j = bbtp->numbad;
	for (i = 0; i < j; i++)
		if (bbtp->bbt_entry[i].hdbad == blk)
		{
#ifndef ABIOS
			hdg = (struct hdgood *) & (bbtp->bbt_entry[i].hdgood);
			*ncyl = hdg->cyl;
			*nhd = hdg->hd;
			*nsec = hdg->sec;
			DEBUGF(ldebug,printf(
			  "bblookup: (%#lx) --> Cyl %#x, Hd %#x, Sec %#x\n",
					      blk, *ncyl, *nhd, *nsec)
			);
#else /* ABIOS */
			*goodblk = bbtp->bbt_entry[i].hdgood;
			DEBUGF(ldebug,rbprintf("bblookup%d:(%lx)-->block %lx\n",
					      unit, blk, *goodblk);
			);
#endif /* ABIOS */
			rval = 1;
		}
	return (rval);
}

#ifndef ABIOS
/*
 * For a multi-block transfer that fails BIOS
 * does not tell us whic sector caused the error.
 *
 * To get by this problem in a controller independent
 * manner we do the following:
 *
 */

static char     dummy[1024];

u_long
det_badsec(drive, cyl, hd, sec, num)
	u_short         drive;
	u_short         cyl, hd, sec;
	u_short         num;
{
	register int    i;
	u_long          blk;
	u_short         ncyl, nhd, nsec, op;
#ifdef DEBUG
	u_short         ldebug = *dbugptr & BBTDEBUG;
#endif /* DEBUG */


	DEBUGF(ldebug, printf("det_badsec: Drive %#x, Cyl %#x, Hd %#x, Sec %#x, Num %#x\n",
			      drive, cyl, hd, sec, num));


	op = DISKOP_VERIFY;
	blk = chs2blk(drive, cyl, hd, sec);

	DEBUGF(ldebug, printf("det_badsec: Start with block # %#lx\n", blk));

	ncyl = cyl;
	nsec = sec;
	nhd = hd;

	for (i = 0; i < num; i++)
	{
		if ((bbio(op, drive, nhd, ncyl, nsec, 1, (char far *) dummy)) < 0)
			break;
		blk2chs(drive, ++blk, &ncyl, &nhd, &nsec);
	}

	if (i < num)
	{
		DEBUGF(ldebug, printf("det_badsec: Bad sector at Cyl %#x, Hd %#x Sec %#x %#lx\n",
				      ncyl, nhd, nsec, blk));
	} else
	{
		blk = 0;
		DEBUGF(ldebug, printf("det_badsec: Could Not find a bad sector????\n"));
	}


	return (blk);
}


static
int bbio(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;


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

	DEBUGF(ldebug, printf("bbio: 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));

	rc = int13(op, drive, head, track, sector, num, buff);

	return (rc);
}
#endif /* ABIOS */
