
/*
 * Copyright 1987 Alan Kent
 *
 * Permission is granted to redistribute this code as long
 * as this message is retained in the code and the code is
 * not sold without written permission from the author.
 *
 * UUCP: {seismo,hplabs,mcvax,ukc,nttlab}!munnari!goanna.oz!ajk
 * ACSnet: ajk@goanna.oz
 * ARPA: munnari!goanna.oz!ajk@SEISMO.ARPA
 */

#include "hd.h"
#include <hardware/custom.h>
#include <hardware/intbits.h>



extern void rdsec ();
extern void wrsec ();

extern int		init_irq ();
extern void		free_irq ();
extern void		wait_for_irq ();
extern void		set_cmd_issued ();

extern ULONG *	NewHashTable ();
extern void		FreeHashTable ();
extern void		AddHashTable ();
extern LONG		TestHashTable ();


ULONG *			bad_hash_table;


struct {
	LONG			cmd_issued;
	struct Task *	task;
	LONG			sig_bits;
} data;



struct wd1002 {
	UBYTE	pad0;
	UBYTE	data;
	UBYTE	pad1;
	UBYTE	error;
#define write_precomp	error
	UBYTE	pad2;
	UBYTE	sec_count;
	UBYTE	pad3;
	UBYTE	sec_num;
	UBYTE	pad4;
	UBYTE	cyl_low;
	UBYTE	pad5;
	UBYTE	cyl_high;
	UBYTE	pad6;
	UBYTE	select;
	UBYTE	pad7;
	UBYTE	status;
#define cmd_reg		status
};


#define WD		((struct wd1002 *)0x9ffff0)


#define WDS_BUSY		0x80
#define WDS_READY		0x40
#define WDS_WRFAULT		0x20
#define WDS_SEEKDONE	0x10
#define WDS_DRQ			0x08
#define WDS_ERRCORRECTED 0x04
#define WDS_ERROR		0x01

#define WDE_BADBLOCK	0x80
#define WDE_CRCERROR	0x40
#define WDE_IDNOTFOUND	0x10
#define WDE_ABORT		0x04
#define WDE_TR0ERROR	0x02
#define WDE_DAMERROR	0x01

#define WDC_RESTORE		0x10
#define WDC_STEPSPEED	0x00
#define WDC_SEEK		0x70
#define WDC_READ		0x20
#define WDC_WRITE		0x30
#define WDC_FORMAT		0x50

#define WDC_MULTIPLE	0x04

/* drive 1, ECC, 512 byte sectors */
#define WD_SELECT		0xa0
/* deselct by selecting drive 2 */
#define WD_DESELECT		0xa8

#define WD_HEAD			0x03


static int wd_head;


int
wd_open ()
{
	int status;
	char dummy;
	struct posn posn;
	UBYTE *p;
	int i;

	/* first make sure its plugged in!!! */

	WD->sec_count = 0;
	if ( WD->sec_count != 0 )
		return ( -1 );
	WD->sec_count = 56;
	if ( WD->sec_count != 56 )
		return ( -1 );

	/* initialize hash table for quick bad sector map checking */

	bad_hash_table = NewHashTable ( 4000L );
	if ( bad_hash_table == NULL )
		return ( -1 );

	/* initialize interrupts */

	if ( init_irq () < 0 ) {
		FreeHashTable ( bad_hash_table );
		return ( -1 );
	}

	/* select the drive */

	WD->select = WD_SELECT;	

	busy_wait ();
	while ( WD->status & WDS_DRQ ) {
		dummy = WD->data;
		WD->data = 0;
	}
	WD->write_precomp = 32;
	WD->sec_count = 1;
	WD->cyl_low = 0;
	WD->cyl_high = 0;
	WD->select = WD_SELECT;
	wd_head = 0;
	status = wd_cmd ( WDC_RESTORE | WDC_STEPSPEED , TRUE );
	if ( status == 0 ) {
		deselect ();

		/* ok, now read in the header sectors */

		posn.sector = 0;
		posn.block = 0;
		posn.surface = 0;
		posn.cylinder = 0;
		p = (UBYTE*) &first;
		status = read_sector ( &posn , p );
		if ( status == 0 ) {
			if ( first.magic != HD_MAGIC ) {

				/* no magic sectors at beginning. default something */

				first.sectors = 16;
				first.heads = 4;
				first.cylinders = 480;
				first.bad_sectors = 0;
				first.park_cylinder = 512;
				first.map_sectors = 0;
			}
			else {

				/* read in rest of first structure */

				for ( posn.sector = 1;
				posn.sector < first.map_sectors;
				posn.sector++ ) {
					p += HD_SECTOR;
					posn.block = posn.sector;
					status = read_sector ( &posn , p );
					if ( status != 0 )
						break;
				}

				/* if all ok, set up hash table for bad sectors */

				if ( status == 0 ) {
					for ( i = 0; i < first.bad_sectors; i++ )
						AddHashTable ( bad_hash_table , first.map[i] );
				}
			}
		}
	}
	if ( status != 0 ) {
		free_irq ();
		FreeHashTable ( bad_hash_table );
		return ( -1 );
	}
	return ( 0 );
}


void
wd_close ()
{
	free_irq ();
	FreeHashTable ( bad_hash_table );
}


int
wd_cmd ( cmd , wait )
int cmd;
int wait;
{
	register int error;
	register int status;
	char dummy;

	WD->select = WD_SELECT | wd_head;
	busy_wait ();
	while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ;

	set_cmd_issued ();

	WD->cmd_reg = cmd;

	if ( wait ) {
		wait_for_irq ();
	}

	busy_wait ();
	while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ;
	error = WD->status;
	status = 0;
	if ( ( error & WDS_READY ) == 0 )
		status = TDERR_NotSpecified;
	else if ( error & WDS_WRFAULT )
		status = TDERR_WriteProt;
	else if ( error & WDS_ERROR ) {
		error = WD->error;
		if ( error & WDE_BADBLOCK )
			status = TDERR_BadSecSum;
		else if ( error & WDE_ABORT )
			status = TDERR_NotSpecified;
		else if ( error & WDE_TR0ERROR )
			status = TDERR_SeekError;
		else if ( error & WDE_DAMERROR )
			status = TDERR_BadSecPreamble;
		else if ( error & WDE_IDNOTFOUND )
			status = TDERR_NoSecHdr;
		else if ( error & WDE_CRCERROR )
			status = TDERR_BadSecSum;
		else
			status = TDERR_NotSpecified;
	}
	if ( status != 0 ) {

		/* try and recover from error - dont leave registers in bad way */

		busy_wait ();
		while ( WD->status & WDS_DRQ )
			dummy = WD->data;

		/* force a read of the error register */
		error = WD->error;

		while ( WD->status & WDS_DRQ )
			dummy = WD->data;

		/* do a restore too */

		WD->select = WD_SELECT;
		busy_wait ();
		while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ;
		set_cmd_issued ();
		WD->cmd_reg = WDC_RESTORE;
		wait_for_irq ();
		busy_wait ();
		while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ;
		error = WD->status;

	}
	return ( status );
}


int
wd_seek ( posn )
register struct posn *posn;
{
	int status;

	WD->select = WD_SELECT;
	busy_wait ();
	wd_head = posn->surface;
	WD->cyl_low = posn->cylinder;
	WD->cyl_high = posn->cylinder >> 8;
	WD->sec_num = posn->sector;
	status = wd_cmd ( WDC_SEEK , TRUE );
	if ( status != 0 ) {
		bad_error = status;
		bad_posn = *posn;
	}
	else
		deselect ();
	return ( status );
}


wd_park ()
{
	int status;
	int dummy;

	WD->select = WD_SELECT;
	wd_head = 0;
	busy_wait ();
	WD->cyl_low = first.park_cylinder;
	WD->cyl_high = first.park_cylinder >> 8;
	WD->sec_num = 0;
	status = wd_cmd ( WDC_SEEK , TRUE );
	if ( status != 0  &&  bad_error == 0 ) {
		bad_error = status;
		bad_posn = cur_posn;
	}

	/* flush anything else (or if error) */

	while ( WD->status & WDS_DRQ )
		dummy = WD->data;

	/* if parked, dont deselect unless error */
	/* (my drive's light turns yellow when parked) */

	if ( status != 0 )
		deselect ();

	return ( status );
}


int
read_sector ( posn , buf )
struct posn *posn;
UBYTE *buf;
{
	int status;
	int dummy;

	cur_posn = *posn;
	bad_remap ( &cur_posn );
	WD->select = WD_SELECT;
	wd_head = cur_posn.surface;
	busy_wait ();
	WD->cyl_low = cur_posn.cylinder;
	WD->cyl_high = cur_posn.cylinder >> 8;
	WD->sec_num = cur_posn.sector;
	status = wd_cmd ( WDC_READ , TRUE );
	if ( status != 0  &&  bad_error == 0 ) {
		bad_error = status;
		bad_posn = cur_posn;
	}
	if ( status == 0 )
		rdsec ( buf , &WD->data , (LONG)HD_SECTOR );

	/* flush anything else (or if error) */

	while ( WD->status & WDS_DRQ )
		dummy = WD->data;
	deselect ();
	return ( status );
}


#ifdef DAM_SECTOR_MAPPING
/* This wont really work because of bad sector remapping */
int
read_track ( posn , buf )
register struct posn *posn;
UBYTE *buf;
{
	int status;
	int dummy;
	int sector;

	cur_posn = *posn;
	bad_remap ( &cur_posn );
	WD->select = WD_SELECT;
	wd_head = cur_posn.surface;
	busy_wait ();
	WD->cyl_low = cur_posn.cylinder;
	WD->cyl_high = cur_posn.cylinder >> 8;
	for ( sector = 0; sector < first.sectors; sector++ ) {
		WD->sec_num = sector;
		status = wd_cmd ( WDC_READ , TRUE );
		if ( status != 0  &&  bad_error == 0 ) {
			bad_error = status;
			bad_posn = cur_posn;
		}
		if ( status != 0 )
			break;
		rdsec ( buf , &WD->data , (LONG)HD_SECTOR );
		buf += HD_SECTOR;
	}

	/* flush anything else (or if error) */

	while ( WD->status & WDS_DRQ )
		dummy = WD->data;
	deselect ();
	return ( status );
}
#endif


/* this version simply does multiple calls to read_sector() */
/* so the bad sector mapping should be ok */
int
read_track ( posn , buf )
struct posn *posn;
UBYTE *buf;
{
	int status;
	struct posn save;
	int sector;

	save = *posn;
	for ( save.sector = 0; save.sector < first.sectors; save.sector++ ) {
		save.block = save.sector + save.surface * first.sectors
			+ save.cylinder * first.heads * first.sectors;
		status = read_sector ( &save , buf );
		if ( status != 0 )
			break;
		buf += HD_SECTOR;
	}
	return ( status );
}


int
write_sector ( posn , buf )
register struct posn *posn;
char *buf;
{
	int status;
	int dummy;

	if ( posn->cylinder < 0 ) {
		return;
	}
	cur_posn = *posn;
	bad_remap ( &cur_posn );
	WD->select = WD_SELECT;
	wd_head = cur_posn.surface;
	busy_wait ();
	WD->cyl_low = cur_posn.cylinder;
	WD->cyl_high = cur_posn.cylinder >> 8;
	WD->sec_num = cur_posn.sector;
	status = wd_cmd ( WDC_WRITE , FALSE );
	if ( status != 0  &&  bad_error == 0 ) {
		bad_error = status;
		bad_posn = cur_posn;
	}
	if ( status == 0 )
		wrsec ( buf , &WD->data , (LONG)HD_SECTOR );

	/* Flush buffer in case of error */

	while ( WD->status & WDS_DRQ )
		dummy = WD->data;

	wait_for_irq ();

	deselect ();
	return ( status );
}


int
wd_format_track ( posn )
register struct posn *posn;
{
	register int i;
	int dummy;

	WD->sec_num = 0;
	WD->cyl_low = posn->cylinder;
	WD->cyl_high = posn->cylinder >> 8;
	WD->select = WD_SELECT | posn->surface;
	WD->sec_count = first.sectors;

	set_cmd_issued ();
	WD->cmd_reg = WDC_FORMAT;   /* format track */

	busy_wait ();
	while ( ( WD->status & WDS_SEEKDONE ) == 0 );
	while ( ( WD->status & WDS_DRQ ) == 0 );
	wrsec ( first.interleave , &WD->data , (LONG)HD_SECTOR );
	wait_for_irq ();
	deselect ();
	return ( 0 );
}


deselect ()
{
	busy_wait ();
	WD->select = WD_DESELECT;
}


busy_wait ()
{
	while ( WD->status & WDS_BUSY )
		/*Delay ( 1L )*/;
}


bad_remap ( posn )
register struct posn *posn;
{
	register int i;


	if ( ! TestHashTable ( bad_hash_table , posn->block ) )
		return;

	for ( i = 0; i < first.bad_sectors; i++ ) {
		if ( first.map[i] == posn->block ) {
			posn->block = i + HD_MAP_SECTORS;
			posn->sector = posn->block % first.sectors;
			posn->surface = ( posn->block / first.sectors ) % first.heads;
			posn->cylinder = posn->block / ( first.sectors * first.heads );
			break;
		}
	}
}




/************************ Interrupt handling code ***********************/

/* change task that should get sent interrupt signals */

wd_subtask ( task )
struct Task *task;
{
	data.task = task;
}


int
init_irq ()
{

	/* set up data structure */

	data.cmd_issued = 0;
	data.task = FindTask ( 0L );
	data.sig_bits = AllocSignal ( -1L );
	if ( data.sig_bits < 0 ) {
		return ( -1 );
	}
	data.sig_bits = 1L << data.sig_bits;

	/* allocate interrupt structure node */

	HDInterrupt = (struct Interrupt *)
		AllocMem ( (LONG)sizeof ( struct Interrupt ) , (LONG)MEMF_PUBLIC );
	if ( HDInterrupt == NULL ) {
		return ( -1 );
	}

	/* set up interrupt structure flags */

	HDInterrupt->is_Node.ln_Type = NT_INTERRUPT;
	HDInterrupt->is_Node.ln_Pri = 0;
	HDInterrupt->is_Node.ln_Name = "Hard Disk IRQ";
	HDInterrupt->is_Data = (APTR) &data;
	HDInterrupt->is_Code = (VOID(*)()) HDHandler;

	AddIntServer ( (LONG)INTB_PORTS , HDInterrupt );

	return ( 0 );
}


void
free_irq ()
{
	if ( HDInterrupt != NULL ) {
		RemIntServer ( (LONG)INTB_PORTS , HDInterrupt );
		FreeMem ( HDInterrupt , (LONG)sizeof ( struct Interrupt ) );
		HDInterrupt = NULL;
	}
}


void
set_cmd_issued ()
{
	/* clear spurious signals */

	SetSignal ( (LONG)0 , data.sig_bits );

	data.cmd_issued = 1;
}


void
wait_for_irq ()
{
	Wait ( data.sig_bits );
}


LONG
HDHandler ()
{
	if ( ! ( WD->status & WDS_BUSY ) ) {
		if ( data.cmd_issued ) {
			data.cmd_issued = 0;
			Signal ( data.task , data.sig_bits );
			return ( 1 );
		}
	}
	return ( 0 );
}
