/*------------------------------------------------------+
|							|
|  Name:						|
|    ide_conf						|
|							|
|  Usage:						|
|    ide_conf [drive number]				|
|							|
|  Description:						|
|    Displays IDE drive configuration under MSDOS.	|
|    Optional drive parameter selects drive for query.	|
|    Legal values are 0..3 where 0 and 1 are drives 0	|
|    and 1 attached to a primary controller while 2 and	|
|    3 are drives 0 and 1 attached to a controller at	|
|    the alternate controller address (0x170 - has not	|
|    been tested).  If omitted, drive 0 is checked.	|
|							|
|  Author:						|
|    Frank P. MacLachlan				|
|							|
|  Date:						|
|    18-Oct-92						|
|							|
|  Compilation using MSC 6.0:				|
|    cl ide_conf.c					|
|							|
+------------------------------------------------------*/

#include <stdio.h>
#include <sys/types.h>
#include "wddefs.h"

#define	TPS	18		/* ticks/second (really 18.2) */

typedef	unsigned long	ulong;
typedef unsigned int	uint;
typedef unsigned short	ushort;
typedef unsigned char	uchar;

/*
**  The following structure corresponds to the ROM BIOS
**  disk parameter table:
*/
#pragma	pack(1)
typedef struct parms {
	uint	ncyls;
	uchar	nheads;
	uint	res0;
	uint	wpc;
	uchar	ebl;
	uchar	ctlb;
	uchar	res1[3];
	uint	lzc;
	uchar	nsecs;
	uchar	res2;
} BIOS_PARMS;
#pragma	pack()

/*
**  The following structure corresponds to the values as returned
**  by the ESDI/IDE identify drive command.
*/
typedef struct {
	ushort	gcfg;	/* general configuration */
	ushort	fcyl;	/* number of fixed cyls */
	ushort	rcyl;	/* number of removable cyls */
	ushort	hds;	/* number of heads */
	ushort	bpt;	/* bytes/track */
	ushort	bps;	/* bytes/sector */
	ushort	spt;	/* sectors/track */
	ushort	isg;	/* inter-sector gap */
	ushort	plo;	/* PLO sync field */
	ushort	vsw;	/* vendor status word */
} ID_PARMS;

/*
**  Used to manipulate identify drive data.
*/
union {
	ID_PARMS	id_parms;
	unsigned short	id_data[256];
} id_buf;

/* pointer to low word of system time value at 0040:006c */
ushort	far *TimePtr = (ushort far *)0x0040006cL;

int	drive = 0;		/* drive number */
int	wd_base = WDP_BASE0;	/* base I/O port addr of HDC */
char	*my_name;

void	reboot(void);
void	chk_drv(void);
void	dsp_bios_cfg(void);
void	dsp_hw_cfg(void);
void	reset_hdc(void);
void	delay(int);
void	sel_drv(uint, uint);
void	bsy_chk(void);
void	usage(void);
void	io_delay(void);
void	test_hdc(uint, uint, uint);


/*-----------------------------------------------
 |
 |  Main program
 |
 +----------------------------------------------*/
main(argc, argv)
int argc;
char *argv[];
{
	my_name = argv[0];
	if (argc > 1) {
		if (   sscanf(argv[1], "%d", &drive) < 1
		    || drive < 0
		    || drive > 3)
			usage();
		if (drive > 1) {
			drive &= 1;
			wd_base = WDP_BASE1;
		}
	}
	chk_drv();		/* make sure controller, drive present */
	dsp_bios_cfg();		/* display BIOS drive config */
	dsp_hw_cfg();		/* display H/W drive config */
	reset_hdc();		/* reset the controller */
	dsp_hw_cfg();		/* display H/W drive config */
	reboot();		/* ask user to reboot system */
}

/*-----------------------------------------------
 |
 |  Ask user to reboot system.
 |
 +----------------------------------------------*/
void reboot()
{
	fprintf(stderr, "Please reboot system!\n");
	while (1)
		;
}

/*-----------------------------------------------
 |
 |  Check if controller, drive present.
 |
 +----------------------------------------------*/
void chk_drv()
{
#define	MASK	(WDM_BSY|WDM_RDY|WDM_WTF|WDM_SKC)
#define	EXP	(WDM_RDY|WDM_SKC)

	test_hdc(wd_base+WDP_CL, 0x55, 0xff);
	test_hdc(wd_base+WDP_CL, 0xaa, 0xff);
	test_hdc(wd_base+WDP_CH, 0x55, 0x01);
	test_hdc(wd_base+WDP_CH, 0xaa, 0x01);
	sel_drv(drive, 0);
	if ((inp(wd_base+WDP_CSR) & MASK) != EXP) {
		fprintf(stderr, "Drive missing or status hosed\n");
		exit(2);
	}
}

/*-----------------------------------------------
 |
 |  Display BIOS configuration info for this drive.
 |
 +----------------------------------------------*/
void dsp_bios_cfg()
{
	/*
	 * Array of pointers to HD parameter table entries indexed
	 * by drive number
	 */
	static BIOS_PARMS far * far *dskptr[2] = {
		(BIOS_PARMS far * far *)(4*0x41L),
		(BIOS_PARMS far * far *)(4*0x46L)
	};
	BIOS_PARMS far *bpp = *dskptr[drive];

	printf("BIOS reports drive has %u cyls, %u hds, %u secs/trk\n\n",
		bpp->ncyls, bpp->nheads, bpp->nsecs);
}


/*-----------------------------------------------
 |
 |  Ask the drive to identify itself.
 |
 +----------------------------------------------*/
void dsp_hw_cfg()
{
	int n;

	/* select drive */
	sel_drv(drive, 0);

	/* Issue Get Drive Parameters cmd */
	outp(wd_base+WDP_CSR, WDC_GDP);

	/* Wait for Busy status to be asserted */
	for (n = 1000; n > 0 && (inp(wd_base+WDP_CSR) & WDM_BSY) == 0; n--)
		;

	/* Now wait for Busy status to be negated */
	bsy_chk();

	/* Print error msg and bail out if error */
	if ((n = inp(wd_base+WDP_CSR)) & WDM_HER) {
		fprintf(stderr,
			"Identify drive cmd failed: csr=0x%02x, err=0x%02x\n",
			n, inp(wd_base+WDP_ERR) );
		reboot();
	}

	/* Wait for Data request to be asserted */
	while ((inp(wd_base+WDP_CSR) & WDM_DRQ) == 0)
		;

	/* Input parameter info from controller */
	for (n = 0; n < sizeof(id_buf.id_data)/sizeof(id_buf.id_data[0]); n++)
		id_buf.id_data[n] = inpw(wd_base+WDP_DAT);

	/* Print parameter info */
	printf("Controller reports drive has %u cyls, %u hds, %u secs/trk\n\n",
		id_buf.id_parms.fcyl, id_buf.id_parms.hds, id_buf.id_parms.spt);
}

/*-----------------------------------------------
 |
 |  Reset hard disk controller (or drive if IDE).
 |
 +----------------------------------------------*/
void reset_hdc()
{
	printf("Resetting controller/drive\n\n");
	outp(wd_base+WDP_DCR, WDM_RSTGO);
	delay(2);
	outp(wd_base+WDP_DCR, WDM_RSTNO|WDM_HS3);
	delay(1*TPS);
}

/*-----------------------------------------------
 |
 |  Delay n system timer ticks.  18.2 ticks = 1 sec.
 |
 +----------------------------------------------*/
void delay(n)
int n;
{
	int cur_lsb = *TimePtr & 1;

	while (n-- > 0) {
		while ((*TimePtr & 1) == cur_lsb)
			;
		cur_lsb = 1 - cur_lsb;
	}
}

/*-----------------------------------------------
 |
 |  Select drive.
 |
 +----------------------------------------------*/
void sel_drv(drv, head)
uint drv, head;
{
	outp(wd_base+WDP_SDH, WDM_ECC|WDM_512|(drv<<4)|head);
	bsy_chk();
}

/*-----------------------------------------------
 |
 |  Wait for Busy status to be reset.
 |
 +----------------------------------------------*/
void bsy_chk()
{
	while (inp(wd_base+WDP_CSR) & WDM_BSY)
		;
}

/*-----------------------------------------------
 |
 |  Display usage message, abort.
 |
 +----------------------------------------------*/
void usage()
{
	static char msg[] =
		"Usage: %s [drive]\n"
		"Drive may be 0..3 (2,3 => drives on alternate controller)\n"
		"Default is 0\n";

	fprintf(stderr, msg, my_name);
	exit(2);
}

/*-----------------------------------------------
 |
 |  Delay a bit between back to back I/O operations.
 |  The delay results from the call/return overhead.
 |
 +----------------------------------------------*/
void io_delay()
{
}

/*-----------------------------------------------
 |
 |  Check if hard disk controller is present and
 |  probably register compatible with the standard
 |  AT controller.  Abort if not.
 |
 +----------------------------------------------*/
void test_hdc(reg, pat, msk)
uint reg;
uint pat;
uint msk;
{
	outp(reg, pat);
	io_delay();
	io_delay();
	io_delay();
	io_delay();
	io_delay();
	io_delay();
	if ((inp(reg)&msk) != (pat&msk)) {
		fprintf(stderr,
			"Non-compatible or missing Hard Disk Controller!\n");
		exit(2);
	}
}
