/******-----------------------------------------------------******
 * rawdevices - a multi-devices driver for raw floppy disks      *
 * for PURE_C 1.1                                                *
 *                                                               *
 * This is Dave Gymer's raw MiNT floppy device hacked up         *
 * by Stephane Boisson (boisso_s@epita.fr)                       *
 * and is hereby placed in the public domain.                    *
 *                                                               *
 ******-----------------------------------------------------******/

#include <tos.h>						/* for Floprd & Flopwr */
#include <string.h>
#include <mint\filesys.h>
#include <mint\kernel.h>
#include <mint\atarierr.h>

#pragma warn -par
#pragma warn -pro

#define DEBUG__(x) DEBUG(x)
#define TRACE__(x) /* TRACE(x) */
#define ALERT__(x) ALERT(x)
#define FATAL__(x) FATAL(x)
 
static long cdecl floppy_open(FILEPTR *f);
static long cdecl floppy_write(FILEPTR *f, char *buf, long bytes);
static long cdecl floppy_read(FILEPTR *f, char *buf, long bytes);
static long cdecl floppy_lseek(FILEPTR *f, long where, int whence);
static long cdecl floppy_ioctl(FILEPTR *f, int mode, void *buf);
static long cdecl floppy_datime(FILEPTR *f, short *timeptr, int wrflag);
static long cdecl floppy_close(FILEPTR *f, int pid);
static long cdecl floppy_select(FILEPTR *f, long proc, int mode);
static void cdecl floppy_unselect(FILEPTR *f, long proc, int mode);

static DEVDRV floppy_device = {
	floppy_open, floppy_write, floppy_read, floppy_lseek, floppy_ioctl,
	floppy_datime, floppy_close, floppy_select, floppy_unselect,
	0, 0, 0
};

typedef struct {
	char *name;				/* Device name */
	int drive;				/* BIOS drive number, 0 == A:, 1 == B: */
	int tracks;				/* tracks per disk */
	int sides;				/* sides per track */
	int sectors;			/* sectors per track */
	int secsize;			/* bytes per sector */
	long blocksize;		/* calculated... */
} RAWDEV;

/* characteristics of the floppies*/
static RAWDEV drivers[]= {
	{"fd0", 0, 80, 2, 9, 512},			/* 720 kbyte Double density */
	{"fh0", 0, 80, 2, 18, 512},			/* 1.44 meg high density */
	{"fx0", 0, 80, 2, 20, 512}			/* 1.6 meg fat high density */
};
	

/* kernel information */
struct kerinfo *kernel;

/* Install time, for floppy_datime. */
static int install_date;
static int install_time;

/* structure to hold a block buffer */
typedef struct floppy_block {
	short dev_id;
	int track;
	int side;
	long byte;
	enum {
		INVALID, VALID, DIRTY, ATEOF
	} state;
	char *buffer;
} FLOBLOCK;

static char name[32]="u:\\dev\\";

struct dev_descr devinfo = {	&floppy_device, 0, 0, (struct tty *)0, 0L, 0L, 0L, 0L};

/**
 * Initial setup, return the device driver to the OS
**/
DEVDRV * cdecl floppy_init(struct kerinfo *k)
{
	long result;
	int i, len;
	
	kernel= k;
	TRACE__(("[RAWDEV] floppy_init: init raw devices"));
	len= strlen(name);
	CCONWS("Raw floppy devices for MiNT (" __DATE__ ")\r\n");
	for(i=0; i<(sizeof(drivers) / sizeof(RAWDEV)); i++)
	{
		strcpy(name+len, drivers[i].name);
		devinfo.dinfo= i;
		drivers[i].blocksize= ((long) drivers[i].secsize) * drivers[i].sectors; 
		TRACE__(("[RAWDEV] floppy_init: device [%s]", name));
		if(DCNTL(DEV_INSTALL, name, (long)&devinfo)<=0)
			ALERT__(("[RAWDEV] floppy_init: Unable to install %s", name));
	}
	install_time= TGETTIME();
	install_date= TGETDATE();

	return((DEVDRV *) 1L);
}

/*****
 * utility functions
 * note that blocks are whole tracks
 *****/

/*>>>>>>>>>>>>>>>>>>>>>> read a block <<<<<<<<<<<<<<<<<<<<<<<<*/
static int read_block(FLOBLOCK *floblock)
{
	int rv;

	TRACE__(("[RAWDEV] read_block: drive #%d, track #%d, side %d", 
		drivers[floblock->dev_id].drive, floblock->track, floblock->side));

	rv = Floprd(floblock->buffer, 0L, drivers[floblock->dev_id].drive,
		1, floblock->track, floblock->side,
		drivers[floblock->dev_id].sectors);
		
	if (rv)
	{
		DEBUG__(("[RAWDEV] read_block: Floprd error #%d", rv));
		floblock->state = INVALID;
	}
	else
		floblock->state = VALID;
	return(rv);
}

/*>>>>>>>>>>>>>>>>>>>> flush a block <<<<<<<<<<<<<<<<<<<<<<<<<<*/
static int flush_block(FLOBLOCK *floblock)
{
	int rv;

	if (floblock->state != DIRTY)
		return 0;

	TRACE__(("[RAWDEV] flush_block: drive #%d, track #%d, side %d", 
		drivers[floblock->dev_id].drive, floblock->track, floblock->side));

	rv = Flopwr(floblock->buffer, 0L, drivers[floblock->dev_id].drive,
		1, floblock->track, floblock->side,
		drivers[floblock->dev_id].sectors);

	if (rv)
		DEBUG__(("[RAWDEV] flush_block: Flopwr error #%d", rv));
	else
		floblock->state = VALID;
	return(rv);
}

/*>>>>>>>> convert long seek position into floppy_block <<<<<<<<*/
static void seek2int(long pos, FLOBLOCK *floblock)
{
	TRACE__(("[RAWDEV] seek2int: convert long seek position #%ld", pos));
	if (pos < 0) pos = 0;
	floblock->byte = pos % drivers[floblock->dev_id].blocksize;
	pos /= drivers[floblock->dev_id].blocksize;
	floblock->side = (int)(pos % drivers[floblock->dev_id].sides);
	pos /= drivers[floblock->dev_id].sides;
	floblock->track = (int)pos;

	if (floblock->track >= drivers[floblock->dev_id].tracks)
	{
		floblock->track = drivers[floblock->dev_id].tracks;
		floblock->side = 0;
		floblock->byte = 0;
		floblock->state = ATEOF;
	}
}

/*>>>>>>>> convert floppy_block into long seek position <<<<<<<*/
static long int2seek(FLOBLOCK *floblock)
{
	TRACE__(("[RAWDEV] int2seek: convert floppy_block"));
	return ((long)floblock->track * drivers[floblock->dev_id].sides
		+ floblock->side) * drivers[floblock->dev_id].blocksize + floblock->byte;
}

/*>>>> move to next block - read it, after flushing the old one <<<<<*/
static int next_block(FLOBLOCK *floblock)
{
	int rv = 0;

	TRACE__(("[RAWDEV] next_block: move to next block"));
	if (floblock->state != ATEOF)
	{
		rv = flush_block(floblock);
		if (++floblock->side == drivers[floblock->dev_id].sides)
		{
			floblock->side = 0;
			if (++floblock->track == drivers[floblock->dev_id].tracks)
			{
				DEBUG__(("[RAWDEV] next_block: end of file"));
				floblock->state = ATEOF;
				floblock->side = 0;
			}
		}
		if (floblock->state != ATEOF)
			if (rv)
				floblock->state = INVALID;
			else
				rv = read_block(floblock);
		floblock->byte = 0;
	}
	return(rv);
}

/*****
 * here are the actual device driver functions
 *****/
static long cdecl floppy_open(FILEPTR *f)
{
	FLOBLOCK *floblock;

	TRACE__(("[RAWDEV] floppy_open: open device %s", drivers[f->fc.aux].name));
	floblock = (FLOBLOCK *)kmalloc(sizeof(FLOBLOCK));
	if (!floblock)
	{
		ALERT__(("[RAWDEV] floppy_open: out of memory for FLOBLOCK"));
		return (ENSMEM);
	}

	floblock->buffer = (char *)kmalloc(drivers[f->fc.aux].blocksize);
	if (!floblock->buffer)
	{
		ALERT__(("[RAWDEV] floppy_open: out of memory for blocksize"));
		return (ENSMEM);
	}

	f->devinfo = (long)floblock;
	floblock->state = INVALID;
	floblock->track = 0;
	floblock->side = 0;
	floblock->byte = 0;
	floblock->dev_id= f->fc.aux;
	return (0);
}

static long cdecl floppy_write(FILEPTR *f, char *buf, long bytes)
{
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;
	int rv = 0;
	long bytes_written = 0;
	
	TRACE__(("[RAWDEV] floppy_write: write %ld bytes to %s", bytes, drivers[f->fc.aux].name));

	if (floblock->state == INVALID) rv= read_block(floblock); 	/* not started yet */

	/* keep going until we've written enough, or there's an error or EOF */
	while (!rv && floblock->state != ATEOF && bytes)
	{
		if (floblock->byte < drivers[floblock->dev_id].blocksize) /* data in buffer */
		{
			char *ptr = floblock->buffer + floblock->byte;
			long num = drivers[floblock->dev_id].blocksize - floblock->byte;

			if (num > bytes) num = bytes;
			bytes_written += num;
			bytes -= num;
			floblock->byte += num;
			while (num--)	*ptr++ = *buf++;
/*		memcpy(ptr, buf, num); */
			floblock->state = DIRTY;
		}
		else					/* must get next block */
			rv = next_block(floblock);
	}
	return rv ? rv : bytes_written;
}

static long cdecl floppy_read(FILEPTR *f, char *buf, long bytes)
{
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;
	int rv = 0;
	long bytes_read = 0;
	
	TRACE__(("[RAWDEV] floppy_read: read %ld bytes from %s", bytes, drivers[f->fc.aux].name));
	if (floblock->state == INVALID) rv = read_block(floblock);		/* not started yet */

	/* keep going until we've read enough, or there's an error or EOF */
	while (!rv && floblock->state != ATEOF && bytes)
	{
		if (floblock->byte < drivers[floblock->dev_id].blocksize)		/* data in buffer */
		{
			char *ptr = floblock->buffer + floblock->byte;
			long num = drivers[floblock->dev_id].blocksize - floblock->byte;

			if (num > bytes) num = bytes;
			bytes_read += num;
			bytes -= num;
			floblock->byte += num;
			while (num--) *buf++ = *ptr++;
/*			memcpy(buf, ptr, num); */
		}
		else					/* must get next block */
			rv = next_block(floblock);
	}
	return rv ? rv : bytes_read;
}

static long cdecl floppy_lseek(FILEPTR *f, long where, int whence)
{
	long newpos = where;
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;

	TRACE__(("[RAWDEV] floppy_lseek: seek mode %d of %ld on %s", whence, where, drivers[f->fc.aux].name));
	switch (whence)
	{
		case SEEK_SET:
			break;
		case SEEK_CUR:
			newpos += int2seek(floblock);
			break;
		case SEEK_END:
			newpos = drivers[floblock->dev_id].sides
				* drivers[floblock->dev_id].tracks * drivers[floblock->dev_id].blocksize - newpos;
			break;
		default:
			DEBUG__(("[RAWDEV] floppy_lseek: illegal whence (%d) in seek for %s", whence, drivers[floblock->dev_id].name));
			return ERANGE;
	}
	if (int2seek(floblock) % drivers[floblock->dev_id].blocksize != newpos % drivers[floblock->dev_id].blocksize)
	{
		if (flush_block(floblock))
			DEBUG__(("[RAWDEV] floppy_lseek: flush_block failed on %s",drivers[floblock->dev_id].name));
		floblock->state = INVALID;
	}
	seek2int(newpos, floblock);
	return newpos;
}

static long cdecl floppy_ioctl(FILEPTR *f, int mode, void *buf)
{
	TRACE__(("[RAWDEV] floppy_ioctl: mode %d on %s", mode, drivers[f->fc.aux].name));
	switch (mode) {
		case FIONREAD:
		case FIONWRITE:
			/*
			 * we never block - use BLOCKSIZE as a sensible
			 * number to read as a chunk
			 */
			*((long *)buf) = drivers[f->fc.aux].secsize * drivers[f->fc.aux].sectors ;
			TRACE__(("[RAWDEV] floppy_ioctl: FIONREAD/FIONWRITE on %s -> %ld", drivers[f->fc.aux].name, *((long *)buf)));
			return (0);
		default:
			return EINTRN;
	}
}

static long cdecl floppy_datime(FILEPTR *f, short *timeptr, int wrflag)
{
	TRACE__(("[RAWDEV] floppy_datime: on %s", drivers[f->fc.aux].name));
	if (wrflag) return EINVFN;
	*timeptr++ = install_time;
	*timeptr = install_date;
	return (0);
}

static long cdecl floppy_close(FILEPTR *f, int pid)
{
	int rv = 0;
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;

	TRACE__(("[RAWDEV] floppy_close: %s by pid #%d", drivers[f->fc.aux].name, pid));
	if (!f->links) {
		rv = flush_block(floblock);	/* flush the buffer */
		kfree(floblock->buffer);
		kfree(floblock);
	}
	return rv;
}

static long cdecl floppy_select(FILEPTR *f, long proc, int mode)
{
	TRACE__(("[RAWDEV] floppy_select: %s always ready!", drivers[f->fc.aux].name));
	return 1;					/* we're always ready for I/O */
}

static void cdecl floppy_unselect(FILEPTR *f, long proc, int mode)
{
	TRACE__(("[RAWDEV] floppy_unselect: %s (nothing to do)", drivers[f->fc.aux].name));
	/* nothing for us to do here */
}
