/**
 * ps0 - a simple device driver for raw floppy disks
 *
 * This program is written by Dave Gymer and is hereby placed in
 * the public domain.
 * The code is based on the clock device by Eric R Smith.
 *
 * Some improvements you could make:
 *	- ioctl calls to reset disk characteristics on the fly
 *	- maybe an ioctl call to format a track or whole disk
 *	- command line arguments during installation governing which drive
 * 	to use
 *	- write Floprd(2) and Flopwr(2) in this code to aboid calling the
 *	Xbios altogether, and also allow it to do hard disks, too (bloody
 *	dangerous if you can overwrite the boot block!)
 *	- write TOS/minix-ST/minix-PC filing systems that use this (in a hard
 *	disk version, most likely) instead of Rwabs(2) [ya think I'm kiddin?]
**/

#include <osbind.h>		/* for Floprd & Flopwr */
#include "filesys.h"
#include "kernel.h"
#include "atarierr.h"

static DEVDRV floppy_device;	/* Tentative forward declarations */

/* characteristics of the floppy in question */
#define DRIVE	0				/* BIOS drive number, 0 == A:, 1 == B: */
#define TRACKS	80				/* tracks per disk */
#define SIDES	2				/* sides per track */
#define SECTORS	9				/* sectors per track */
#define SECSIZE	512L			/* bytes per sector */
#define BLOCKSIZE (SECSIZE * SECTORS)

/* 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 {
	int track;
	int side;
	long byte;
	enum {
		INVALID, VALID, DIRTY, ATEOF
	} state;
	char *buffer;
} FLOBLOCK;

/**
 * Initial setup, return the device driver to the OS
**/
static DEVDRV *
floppy_init(struct kerinfo *k)
{
	kernel = k;
	install_time = t_gettime();
	install_date = t_getdate();
	return &floppy_device;
}

/*
 * utility functions
 * note that blocks are whole tracks
 */

/* read a block */
static int
read_block(FLOBLOCK *floblock)
{
	int rv;

	rv = Floprd(floblock->buffer, 0L, DRIVE, 1,
	  floblock->track, floblock->side, SECTORS);
	if (rv) {
		DEBUG(("Floprd failed in read_buf"));
		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;
	rv = Flopwr(floblock->buffer, 0L, DRIVE, 1,
	  floblock->track, floblock->side, SECTORS);
	if (rv)
		DEBUG(("Flopwr failed in flush_block"));
	else
		floblock->state = VALID;
	return rv;
}

/* convert long seek position into floppy_block */
static void
seek2int(long pos, FLOBLOCK *floblock)
{
	if (pos < 0)
		pos = 0;
	floblock->byte = pos % BLOCKSIZE;
	pos /= BLOCKSIZE;
	floblock->side = (int)(pos % SIDES);
	pos /= SIDES;
	floblock->track = (int)pos;
	if (floblock->track >= TRACKS) {
		floblock->track = TRACKS;
		floblock->side = 0;
		floblock->byte = 0;
		floblock->state = ATEOF;
	}
}

/* convert floppy_block into long seek position */
static long
int2seek(FLOBLOCK *floblock)
{
	return ((long)floblock->track * SIDES + floblock->side) * BLOCKSIZE
	  + floblock->byte;
}

/* move to next block - read it, after flushing the old one */
static int
next_block(FLOBLOCK *floblock)
{
	int rv = 0;

	if (floblock->state != ATEOF) {
		rv = flush_block(floblock);
		if (++floblock->side == SIDES) {
			floblock->side = 0;
			if (++floblock->track == TRACKS) {
				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
floppy_open(FILEPTR *f)
{
	FLOBLOCK *floblock = (FLOBLOCK *)kmalloc(sizeof(FLOBLOCK));

	if (!floblock)
		return ENSMEM;
	floblock->buffer = (char *)kmalloc(BLOCKSIZE);
	if (!floblock->buffer)
		return ENSMEM;
	f->devinfo = (long)floblock;
	floblock->state = INVALID;
	floblock->track = 0;
	floblock->side = 0;
	floblock->byte = 0;
	return 0;
}

static long
floppy_write(FILEPTR *f, char *buf, long bytes)
{
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;
	int rv = 0;
	long bytes_written = 0;

	if (floblock->state == INVALID)	/* not started yet */
		rv = read_block(floblock);

	/* keep going until we've written enough, or there's an error or EOF */
	while (!rv && floblock->state != ATEOF && bytes) {
		if (floblock->byte < BLOCKSIZE) {	/* data in buffer */
			char *ptr = floblock->buffer + floblock->byte;
			long num = BLOCKSIZE - floblock->byte;

			if (num > bytes)
				num = bytes;
			bytes_written += num;
			bytes -= num;
			floblock->byte += num;
			while (num--)
				*ptr++ = *buf++;
			floblock->state = DIRTY;
		}
		else					/* must get next block */
			rv = next_block(floblock);
	}
	return rv ? rv : bytes_written;
}

static long
floppy_read(FILEPTR *f, char *buf, long bytes)
{
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;
	int rv = 0;
	long bytes_read = 0;

	if (floblock->state == INVALID)	/* not started yet */
		rv = read_block(floblock);

	/* keep going until we've read enough, or there's an error or EOF */
	while (!rv && floblock->state != ATEOF && bytes) {
		if (floblock->byte < BLOCKSIZE) {	/* data in buffer */
			char *ptr = floblock->buffer + floblock->byte;
			long num = BLOCKSIZE - floblock->byte;

			if (num > bytes)
				num = bytes;
			bytes_read += num;
			bytes -= num;
			floblock->byte += num;
			while (num--)
				*buf++ = *ptr++;
		}
		else					/* must get next block */
			rv = next_block(floblock);
	}
	return rv ? rv : bytes_read;
}

static long
floppy_lseek(FILEPTR *f, long where, int whence)
{
	long newpos = where;
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;

	switch (whence) {
		case SEEK_SET:
			break;
		case SEEK_CUR:
			newpos += int2seek(floblock);
			break;
		case SEEK_END:
			newpos = SIDES * TRACKS * BLOCKSIZE - newpos;
			break;
		default:
			DEBUG(("ps0: illegal whence (%d) in seek", whence));
			return ERANGE;
	}
	if (int2seek(floblock) % BLOCKSIZE != newpos % BLOCKSIZE) {
		if (flush_block(floblock))
			DEBUG(("flush_block failed in floppy_lseek"));
		floblock->state = INVALID;
	}
	seek2int(newpos, floblock);
	return newpos;
}

static long
floppy_ioctl(FILEPTR *f, int mode, void *buf)
{
	switch (mode) {
		case FIONREAD:
		case FIONWRITE:
			/*
			 * we never block - use BLOCKSIZE as a sensible
			 * number to read as a chunk
			 */
			*((long *)buf) = BLOCKSIZE;
			return 0;
		default:
			return EINTRN;
	}
}

static long
floppy_datime(FILEPTR *f, short *timeptr, int wrflag)
{
	if (wrflag)
		return EINVFN;
	*timeptr++ = install_time;
	*timeptr = install_date;
	return 0;
}

static long
floppy_close(FILEPTR *f, int pid)
{
	int rv = 0;
	FLOBLOCK *floblock = (FLOBLOCK *)f->devinfo;

	if (!f->links) {
		rv = flush_block(floblock);	/* flush the buffer */
		kfree(floblock->buffer);
		kfree(floblock);
	}
	return rv;
}

static long
floppy_select(FILEPTR *f, long proc, int mode)
{
	return 1;					/* we're always ready for I/O */
}

static void
floppy_unselect(FILEPTR *f, long proc, int mode)
{
	/* nothing for us to do here */
}

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
};
