/*
 * USC-ACSC Unix Monitor: $Header:badblock.c 12.1$
 * Copyright University of Southern California, 1988
 */

/* This file is almost the same as the original pc_code file. It has
 * a few changes to use the OS/2 disk drivers instead of int13 and a lot
 * of rearrangement of the original code, static declarations and other
 * cosmetic changes.
 */

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

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

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


#include <dos.h>
#include <stdio.h>
#include <sys/types.h>
#include "pctype.h"
#include "pcparam.h"
#include "rb.h"
#include "dio.h"
#include "bios.h"
#include "os2data.h"

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


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


static struct bbt      bbt[NHD] = {0};
static int             bbprocessing = FALSE; /* flag */

static char            defect_flag[] = "DEFECT";

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

static 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.
 *
 *
 */

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

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

	drv -= 0x80;
	blk = -1;

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

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

static int
badblklookup(blk, bbtp, ncyl, nhd, nsec)
u_long          blk;
struct bbt     *bbtp;
u_short        *ncyl, *nhd, *nsec;
{
	register int    i, j;
	int             rval = 0;
	struct hdgood  *hdg;

#ifdef BBT_DEBUG
	printf("badblklookup: blk: 0x%x\n", blk);
#endif
	j = bbtp->numbad;
	for (i = 0; i < j; i++) {
#ifdef BBT_DEBUG
		printf("\tbbt_entry[%d] 0x%lx -> 0x%lx\n",i,
			bbtp->bbt_entry[i].hdbad, bbtp->bbt_entry[i].hdgood);
#endif
		if (bbtp->bbt_entry[i].hdbad == blk) {
			hdg = (struct hdgood *) & (bbtp->bbt_entry[i].hdgood);
			*ncyl = hdg->cyl;
			*nhd = hdg->hd;
			*nsec = hdg->sec;
			rval = 1;
#ifdef BBT_DEBUG
			printf("bblookup:(%#lx)-->Cyl %#x,Hd %#x,Sec %#x\n",
					      blk, *ncyl, *nhd, *nsec);
#endif
		}
	}
	return (rval);
}

/*
 * 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];

static 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 BBT_DEBUG
	printf("det_badsec: Drive %#x, Cyl %#x, Hd %#x, Sec %#x, Num %#x\n",
			      drive, cyl, hd, sec, num);
#endif


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

#ifdef BBT_DEBUG
	printf("det_badsec: Start with block # %#lx\n", blk);
#endif

	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) {
#ifdef BBT_DEBUG
	  printf("det_badsec: Bad sector at Cyl %#d Hd %#d Sec %#d Blk %#ld\n",
				      ncyl, nhd, nsec, blk);
#endif
	}
	else {
		blk = 0;
#ifdef BBT_DEBUG
		printf("det_badsec: Could Not find a bad sector????\n");
#endif
	}


	return (blk);
}


/*********************************************************************
 *
 * 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 forwarding 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.
 */

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

/*
 * 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.
 *
 */

	drv = drive - 0x80;
	hdp = (struct drvparm *) & hdparm[drv];
	nspc = hdp->maxsec * (hdp->maxhd + 1);
	cyl = rb_part[drv].pstart;
	sec = 9;

	bbprocessing = TRUE; /* don't want to invoke procbbt if the read
			      * fails.
			      */

#ifdef BBT_DEBUG
	printf("bbtinit: drive:0x%x cyl:%d sec:%d\n",drive,cyl,sec);
#endif

	if (hdio(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];
			bbtp->numbad = 0;
			if ((bbcnt = exchw(hdbad->count)) > MAXBADBLOCKS)
				bbcnt = MAXBADBLOCKS;

#ifdef BBT_DEBUG
			printf("bbtinit: DEFECT found, %d entries\n", bbcnt);
			printf("bbtinit: relative bad block map:\n");
#endif
			j = 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 BBT_DEBUG
					printf(
					"%d: %ld->%ld(cyl %d,hd %d,sec %d)\n",
					j, bblock, gblock, cyl, hd, sec);
#endif
				}
				else
					printf(
					"bbtinit: Can't Convert block %#lx\n",
							gblock);
			}
			bbtp->numbad = j;
			rval = 0;
		}
#ifdef BBT_DEBUG
		else
			printf("bbtinit: NO DEFECT MAP PRESENT\n");
#endif
	}
	bbprocessing = FALSE;
	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 BBT_DEBUG
	printf("procbbt:\n");
#endif

	rval = rc;

	if ( bbprocessing == TRUE )
		return rc;
	bbprocessing = TRUE;

	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 BBT_DEBUG
			printf("Transfer Started At Block# %#lx\n",startblk);
			printf("Bad Block Located At Block# %#lx\n",errorblk);
			printf("Num Good Blocks = %#x\n", goodblks);
#endif

			hd_return_code[drv].badblock = exchl(blk);

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

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

				/*
				 * Now get the good block. 
				 */

#ifdef BBT_DEBUG
				printf("\tRemap Bad Block\n");
#endif

				rval = bbio(op, drive, nhd, ncyl, nsec, 1, buff);
				if (rval < 0) {
#ifdef BBT_DEBUG
					printf(
					"Error Reading good block!, Exit\n");
#endif
					bbprocessing = FALSE;
					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 BBT_DEBUG
				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");
#endif

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

			}
			 /* End if(badblklookup() */ 
			else {
				/* Entry not in bad block table */
#ifdef BBT_DEBUG
				printf("No bad block found!, Exit\n");
#endif
				bbprocessing = FALSE;
				return (rval);
			}

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

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