#include "pun.h"
#include "xhdi.h"

#include <string.h>

#ifdef MFS_XFS
#include "minixfs.h"
#include "global.h"
#define XRWABS RWABS
#define DWARN(mes,drive) ALERT("Drive %c: " mes,drive)
#else
#define DWARN(mes,drive) fprintf(stderr,"Drive %c: " mes "\n",drive)
#define ALERT(x)	 fprintf(stderr,x "\n")
#define NEED_SUPER
#define RWABS Rwabs
#define Kmalloc malloc
#define Kfree free
#define GETBPB Getbpb
#include <osbind.h>
#include <alloc.h>
#include <stdio.h>
#include "hdio.h"

#define XRWABS(a,b,c,d,e,f) \
trap_13_wwlwwwl((short)(0x04),(short)(a),(long)(b),(short)(c),(short)(d)\
,(short)(e),(long)(f) )

#define trap_13_wwlwwwl(n, a, b, c, d, e, f)				\
({									\
	register long retvalue __asm__("d0");				\
	volatile short _a = (volatile short)(a);			\
	volatile long  _b = (volatile long) (b);			\
	volatile short _c = (volatile short)(c);			\
	volatile short _d = (volatile short)(d);			\
	volatile short _e = (volatile short)(e);			\
	volatile long  _f = (volatile long) (f);			\
	    								\
	__asm__ volatile						\
	("\
		movl	%5,sp@-; \
		movw    %4,sp@-; \
		movw    %3,sp@-; \
		movw    %2,sp@-; \
		movl    %1,sp@-; \
		movw    %0,sp@-	"					\
	:					      /* outputs */	\
	: "r"(_a), "r"(_b), "r"(_c), "r"(_d), "r"(_e), "r"(_f) /* inputs  */ \
	);								\
									\
	__asm__ volatile						\
	("\
		movw    %1,sp@-; \
		trap    #13;	\
		addw    #18,sp "					\
	: "=r"(retvalue)			/* outputs */		\
	: "g"(n)				/* inputs  */		\
	: "d0", "d1", "d2", "a0", "a1", "a2"    /* clobbered regs */	\
	);								\
	retvalue;							\
})

#endif

/* List of error codes for get_hddinf */

char *hdd_err[] = { 
"OK",
"Bad BPB on floppy drive",				/* 1 */
"Need drive A-P for PUN_INFO",				/* 2 */
"Invalid or no PUN_INFO structure",			/* 3 */
"Invalid drive",					/* 4 */
"Physical mode disabled for ICD software",		/* 5 */
"Physical lrecno error",				/* 6 */
"XHInqDev2 failed (old XHDI version?) and bad BPB",	/* 7 */
"XHInqDev failed",					/* 8 */
"Unrecognised partition id",				/* 9 */
"XHInqTarget failed",					/* 10 */
"Unsupported physical sector size",			/* 11 */
"Invalid partition start (zero BPB?)" 			/* 12 */
"ICD software too old to fix",				/* 13 */
/* These are from set_lrecno */
"Memory allocation failure",				/* 14 */
"Unable to access last block"				/* 15 */

};


/*
 * Hard disk info. This is a general purpose routine to handle minixfs' needs
 * for hard disks. If this function returns non-zero then the partition 
 * cannot be accessed. XHDI and pun_info are used to get partition info.
 * The structure 'hdinf' is filled in as approproiate.
 *
 * If this looks awful then that's because it *is*.
 */

static char rno_xhdi,try_xhdi;
static char try_lrecno,rno_lrecno;
static char try_plrecno,rno_plrecno;

char is_icd =-1 ;
unsigned char *cache_icd;

int get_hddinf(drive,hdinf,flag)
int drive;
struct hdinfo *hdinf;
char flag;
{
	long ret;
#ifdef NEED_SUPER
	long tstack;
	tstack=Super(0l);
	if(!((*(long *)0x4c2) & (1l<<drive))) return 4;
#endif
	ret = _get_hddinf(drive,hdinf,flag);
#ifdef NEED_SUPER
	Super(tstack);
#endif
	return ret;
}

int _get_hddinf(drive,hdinf,flag)
int drive;
struct hdinfo *hdinf; 
char flag;
{
	_BPB *bpb;

	hdinf->major=drive;	/* May get overwritten later */

	hdinf->drive=drive;

	init_icd();

	bpb=GETBPB(drive); 
	if( flag ) bpb=0;


	/* Step 1: if bpb OK and sector size 512 bytes or 1K we may get away
	 * with normal Rwabs.
	 */

	if( !bpb || (bpb->recsiz!=512 && bpb->recsiz!=1024) )
	{
		long tsecsiz;
		char mpid[4];

		/* OK can't use normal Rwabs: try XHDI or pun_info */

		/* Bypass this rubbish for floppies */
		if(drive < 2 ) return 1;

		/* Try and get info from pun_inf structure */
		if( no_xhdi() )
		{
			struct pun_info *pinf;
			if(drive >= MAXUNITS) return 2;
			if(!(*(long *)0x516)) return 3;
			pinf=PUN_PTR;
			if(!pinf || (PUN_VALID & pinf->pun[drive]) ) return 4;
			hdinf->scsiz = 1;

			if(is_icd)
#ifdef NO_ICD_PHYS
				return 5;
#else
			{
				if(is_icd==2) return 13;
				hdinf->start = pinf->partition_start[drive+4];
			}
#endif
			else hdinf->start = pinf->partition_start[drive];

			if(!hdinf->start) return 12;

			hdinf->size = 0;
			hdinf->minor = pinf->pun[drive];
			if(is_icd) hdinf->major=drive;
			else hdinf->major = (hdinf->minor & PUN_DEV) +2;
			hdinf->rwmode = RW_PHYS;
			/* We want to access at least first few sectors */
			if(hdinf->start > 0xfff0)
			{
				if(no_plrecno(hdinf->major)) return 6;
				else hdinf->rwmode |= RW_LRECNO;
			}
			return 0;
		}

		hdinf->rwmode = RW_XHDI | RW_LRECNO;

		/* Hmmmm Getbpb failed or bad secsize: see what XHDI can do */

		if( XHInqDev2(drive,&hdinf->major,&hdinf->minor,&hdinf->start,
							  0,&hdinf->size,mpid) )
		{
			if(!bpb && !flag ) return 7;
			if( XHInqDev(drive,&hdinf->major,&hdinf->minor,
						    &hdinf->start,0) ) return 8;
			hdinf->size=0;
		}
		else if(!bpb && strcmp(mpid,"RAW") && strcmp(mpid,"MIX")
		   	&& strcmp(mpid,"BGM") && strcmp(mpid,"GEM") ) return 9;

		/* Get physical sector size */
		if( XHInqTarget(hdinf->major,hdinf->minor,&tsecsiz,0,0) )
								      return 10;

		if(tsecsiz==512) hdinf->scsiz=1;
		else 
		{
			if(tsecsiz==1024) hdinf->scsiz=0;
			else return 11;
		}
		return 0;
	}
	if(bpb->recsiz==512) hdinf->scsiz=1;
	else hdinf->scsiz=0;
	hdinf->size=0;
	hdinf->rwmode = RW_NORMAL;
	return 0;
}

/* Special kludge for icd software. This software accesses far too many sectors
 * when physical mode I/O is attempted on sectors bigger than 0xffff with the
 * cache on. What we do
 * is to:
 * 1. Test for ICD software.
 * 2. Set a pointer to the 'cache flag'.
 * Return values:
 * 0 Non ICD software or PUN_INFO problem.
 * 1 Probably non ICD host adaptor used with ICD software.
 * 2 ICD software doesn't have a 'cache flag' (probably too old).
 * 3 Probably kludgable (OK).
 * Only '2' is fatal.
 */


int init_icd()
{
	char *icd_magic;	
	if(is_icd!=-1) return is_icd;
	if(!*((long *)0x516)) return is_icd=0;
	icd_magic=((char *)PUN_PTR)-6;
	if(strncmp(icd_magic,"ICDB",4)) return is_icd=0;
	else
	{
		char *icdh_magic;
		if( icd_magic[5] < 0x50 ) return is_icd=2;
		icdh_magic=*((char ** )(icd_magic-4));
		if(strncmp(icdh_magic,"ICDH",4)) return is_icd=1;
		if( icdh_magic[4] < 0x50 ) return is_icd=2;
		cache_icd = (unsigned char *)(icdh_magic+6);
		return is_icd=3;
	}
}

/* This function is called after get_hddinf and is used to finalise the
 * accessibility of a partition. The 'size' parameter is the size of the
 * partition in K; this info will typically come from the super block of
 * a filesystem. 
 * Return values:
 * 0	OK
 * 1	Malloc failure.
 * 2	Can't access last block.
 * 3    Physical lrecno error.
 */

int set_lrecno(hdinf,size)
struct hdinfo *hdinf;
long size;
{
	long tsize;
	tsize=size;
	if(hdinf->scsiz) tsize <<=1;
	if( ( (hdinf->rwmode & RW_MODE) == RW_XHDI) && hdinf->size
						      && (hdinf->size < tsize) )
	{
		DWARN("Filesystem size bigger than partition size!",
								hdinf->drive);
	}
	else hdinf->size = tsize;

	hdinf->rwmode |= RW_CHECK;

	if(hdinf->rwmode & RW_LRECNO) return 0;

	switch(hdinf->rwmode & RW_MODE)
	{
		case RW_NORMAL:
		if(tsize < 0xffff)
		{
			char *tmp_buf;
			tmp_buf=Kmalloc(1024);
			if(!tmp_buf) return 14;
			/* Try to read last block */
			if(!block_rwabs(2,tmp_buf,1,size-1,hdinf)) 
			{
				Kfree(tmp_buf);
				return 0;
			}
			Kfree(tmp_buf);
#ifndef BYPASS_LOGICAL
			return 15;
#endif
		}

		if( tsize < 0xffff || no_lrecno(hdinf->major) )
		{
		/* Bad lrecno or access error, try physical mode access */
			int drive,err;
			drive = hdinf->major;
			if( (err=get_hddinf(drive,hdinf,1)) ) return err;
			else return (set_lrecno(hdinf,size));
		}
		else hdinf->rwmode |= RW_LRECNO;
		return 0;

		case RW_PHYS:
		if(tsize+hdinf->start >= 0xfffe)
		{
			if(no_plrecno(hdinf->major)) return 1;
			hdinf->size = tsize;
			hdinf->rwmode |= RW_LRECNO;
		}
		return 0;
	}

	return 1;	/* This can't happen */
}

/* Test for 'lrecno' parameter on drive 'drive': mode' is RWABS mode to use 
 * (2=logical,10=physical).
 * Return values:
 * 0 OK
 * 1 Read error.
 * 2 No lrecno recognised.
 * 3 Error reading large sector number (possibly OK if partition too small).
 * 4 Wraparound bug present.
 * 5 Allocation error.
 */

int test_lrecno(drive,mode)
int drive;
int mode;
{
	char *block_buf1,*block_buf2;
	int size;
	_BPB *bpb;
	bpb=Getbpb(drive);
	if( (mode & 8) || !bpb ) size=1024;
	else size=bpb->recsiz;

	block_buf1=Kmalloc(size<<1);
	block_buf2=block_buf1+size;
	bzero(block_buf1,size<<1);

	if(!block_buf1) return 5;

	/* read in boot sector */
	if(RWABS(mode,block_buf1,1,0,drive)) 
	{
		Kfree(block_buf1);
		return 1;
	}

	/* read it in with lrecno */	
	if( XRWABS(mode,block_buf2,1,-1,drive,0l) ) 
	{
		Kfree(block_buf1);
		return 2;
	}

	/* Compare the two */
	if(bcmp(block_buf1,block_buf2,size))
	{
		Kfree(block_buf1);
		return 2;
	}

	/* read in next sector with lrecno */
	if(XRWABS(mode,block_buf2,1,-1,drive,1l))
	{
		Kfree(block_buf1);
		return 1;
	}

	/* compare the two */
	if(!bcmp(block_buf1,block_buf2,size))
	{
		Kfree(block_buf1);
		return 2;
	}

	/* Check for lrecno bug, this causes the upper word of a long sector
	 * number to be ignored. Read in sector 0 and 0x10000, if the bug is
	 * present then these will be identical.
	 */
	bzero(block_buf2,size);

	if(XRWABS(mode,block_buf2,1,-1,drive,0x10000l))
	{
		Kfree(block_buf1);
		return 3;
	}
	else if(!bcmp(block_buf1,block_buf2,size))
	{
		Kfree(block_buf1);
		return 4;
	}

	Kfree(block_buf1);
	return 0;

}

int no_lrecno(drv)
int drv;
{
	if( !try_lrecno )
	{
		rno_lrecno = test_lrecno(drv,2) ;
		try_lrecno = 1;
	}
	return rno_lrecno;
}

int no_plrecno(drv)
int drv;
{
#ifdef NO_ICD_PHYS
	if(is_icd) return 1;
#endif

	if( !try_plrecno ) 
	{
		unsigned char cache_tmp=0;
		try_plrecno = 1;
		if(cache_icd)
		{
			cache_tmp=*cache_icd;
			*cache_icd=0;
		}
		rno_plrecno = test_lrecno(drv,10) ;
		if(cache_icd) *cache_icd=cache_tmp;
	}
	return rno_plrecno;
}

int no_xhdi()
{
	if( !try_xhdi ) 
	{
		if( !XHGetVersion() ) rno_xhdi=1;
		try_xhdi=1;
	}
	return rno_xhdi;
}

/* 
 * This is (finally!) the I/O function hdinf uses. It reads/writes in 1K chunks
 * and calls the relevant functions according to the hdinf structure.
 */

long block_rwabs(rw,buf,num,recno,hdinf)
int rw;
void *buf;
unsigned num;
long recno;
struct hdinfo *hdinf;
{
	unsigned char cache_tmp=0;
	long ret;

	if( hdinf->scsiz )
	{
		recno <<=1;
		num <<=1;
	}

	if( (hdinf->rwmode & RW_CHECK) && (recno+num > hdinf->size) )
	{
		DWARN("Attempted access outside partition",hdinf->drive);
		return -1;
	}

	switch(hdinf->rwmode & (RW_MODE|RW_LRECNO))
	{
		case RW_NORMAL:
		return RWABS(rw,buf,num,(unsigned)recno,hdinf->major);

		case RW_NORMAL | RW_LRECNO:
		return XRWABS(rw,buf,num,-1,hdinf->major,recno);

		case RW_PHYS:
		if(cache_icd)
		{
			cache_tmp=*cache_icd;
			*cache_icd=0;
		}
		ret = RWABS(rw | 8,buf,num,(unsigned)(recno+hdinf->start),
								  hdinf->major);
		if(cache_icd) *cache_icd=cache_tmp;
		return ret;

		case RW_PHYS | RW_LRECNO:
		if(cache_icd)
		{
			cache_tmp=*cache_icd;
			*cache_icd=0;
		}
		ret = XRWABS(rw | 8,buf,num,-1,hdinf->major,
							    recno+hdinf->start);
		if(cache_icd) *cache_icd=cache_tmp;
		return ret;

		case RW_XHDI | RW_LRECNO:
		return XHReadWrite(hdinf->major,hdinf->minor,rw,
						    recno+hdinf->start,num,buf);
	}
	return 1;	/* This can't happen ! */
}

#ifndef SUPER_CALL

/* Determine partition size from drive info. Check Rwabs errors ... it should
 * give an error for an attempt to read past the end of a partition. From then
 * on use a log search to find partition size.
 * If this seems awkward you are right ... certain disk software doesn't enter
 * the correct size in the boot sector (to correct a TOS bug).
 */

char *size_err[] = 
{
"OK",
"Getbpb failed",
"Allocation error",
"Driver software incompatible"
};

int get_size(drive,size)
int drive;
long *size;
{
	char *buf;
	_BPB *bpb;
	unsigned secnum,bitnum;
	if(! (bpb=GETBPB(drive)) ) return 1; /* Bad bpb */
	if(! (buf=Kmalloc(bpb->recsiz)) ) return 2; /* alloc error */
	
	if(!RWABS(2,buf,1,0xfffe,drive))
	{
		Kfree(buf);
		return 3;	/* Software doesn't return errors */
	}

	bitnum=0x8000;
	secnum=0;

	do
	{
		secnum |= bitnum;
		if( RWABS(2,buf,1,secnum,drive) ) secnum &=~bitnum;
		bitnum >>=1 ;
	} while(bitnum);

	secnum++;

	*size=(((long)secnum) * bpb->recsiz)>>10;
	Kfree(buf);
	return 0;

}

#endif



