/*
 * A simple device driver for u:\dev\clock. Reading from this
 * device produces a single line containing the current time, in the
 * format:
 * MM/DD/YY hh:mm:ss\r\n
 * Writing to it will change the time to the given one.
 *
 * This program is written by Eric R. Smith and is hereby placed in
 * the public domain.
 *
 * COMPILER NOTE: I've assumed that you're using a compiler (like gcc
 * or Lattice) that preserves registers d2 and a2 across function calls.
 * If your compiler uses these registers as scratch registers (e.g.
 * MWC, Alcyon) then you'll have to provide assembly language wrapper
 * functions that the kernel can call.
 * This code also assumes that sizeof(int) == 2.
 *
 * for gcc: compile with gcc -mshort -O clockdev.c -o clockdev.prg
 */

#ifdef __GNUC__
#include <minimal.h>
#endif
#include <osbind.h>
#include <basepage.h>
#include "mintbind.h"
#include "filesys.h"
#include "atarierr.h"

#ifdef LATTICE
#define BP _pbase
#else
#define BP _base
#endif

/* the name of the device we're installing */
char name[] = "U:\\DEV\\CLOCK";

/* kernel information */
struct kerinfo *kernel;
#define CCONWS (void)(*kernel->dos_tab[0x09])
#define RWABS (*kernel->bios_tab[4])
#define GETBPB (void *)(*kernel->bios_tab[7])
#define TGETTIME (*kernel->dos_tab[0x2c])
#define TGETDATE (*kernel->dos_tab[0x2a])
#define TSETTIME (*kernel->dos_tab[0x2d])
#define TSETDATE (*kernel->dos_tab[0x2b])

#define SPRINTF (*kernel->sprintf)
#define DEBUG (*kernel->debug)
#define ALERT (*kernel->alert)
#define TRACE (*kernel->trace)
#define FATAL (*kernel->fatal)

/* assumption: 16 bit integers */
#define word int

/* device driver information */
static long	clock_open	P_((FILEPTR *)),
		clock_write	P_((FILEPTR *, char *, long)),
		clock_read	P_((FILEPTR *, char *, long)),
		clock_lseek	P_((FILEPTR *, long, word)),
		clock_ioctl	P_((FILEPTR *, word, void *)),
		clock_datime	P_((FILEPTR *, word *, word)),
		clock_close	P_((FILEPTR *));

static long 	clock_select();
static void	clock_unselect();

DEVDRV clock_device = {
	clock_open, clock_write, clock_read, clock_lseek, clock_ioctl,
	clock_datime, clock_close, clock_select, clock_unselect,
	0, 0, 0
};

struct dev_descr devinfo = {
	&clock_device, 0, 0, (struct tty *)0, 0L, 0L, 0L, 0L
};

/*
 * the main program just installs the device, and then does Ptermres
 * to remain resident
 */

main()
{
	kernel = (struct kerinfo *)Dcntl(DEV_INSTALL, name, &devinfo);
	if (!kernel || ((long)kernel) == -32) {
		Cconws("Unable to install clock device\r\n");
		Pterm(1);
	}
	Ptermres(256L + BP->p_tlen + BP->p_dlen + BP->p_blen, 0);
}

/*
 * here are the actual device driver functions
 */

/*
 * utility functions:
 * getclock(buf): get the current date and time and write it into
 * the pointed to buffer in the format "MM/DD/YY hh:mm:ss\r\n"
 *
 * setclock(buf): set the current date and time from the ASCII
 * string pointed to by buf, which must have the same format
 * as that returned by getdate
 */

void
getclock(buf)
	char *buf;
{
	int DD, MM, YY, hh, ss, mm;
	unsigned date, time;

	date = TGETDATE();
	time = TGETTIME();

	DD = date & 31;
	MM = (date >> 5) & 15;
	YY = 80 + ( (date >> 9) & 127 ); if (YY > 99) YY -= 100;

	ss = (time & 31) << 1;
	mm = (time >> 5) & 63;
	hh = (time >> 11) & 31;

	SPRINTF(buf, "%02d/%02d/%02d %02d:%02d:%02d\r\n", MM, DD, YY,
		hh, mm, ss);
}

static int
getint(buf)
	char *buf;
{
	int val = 0;

	val = *buf++ - '0';
	val = 10 * val + *buf - '0';
	return val;
}

void
setclock(buf)
	char *buf;
{
	int DD, MM, YY, hh, mm, ss;
	unsigned time, date;

	MM = getint(buf); buf += 3;
	if (MM < 1 || MM > 12) return;
	DD = getint(buf); buf += 3;
	if (DD < 1 || DD > 31) return;
	YY = getint(buf); buf += 3;
	if (YY < 80 || YY > 99) return;
	hh = getint(buf); buf += 3;
	if (hh < 0 || hh > 23) return;
	mm = getint(buf); buf += 3;
	if (mm < 0 || mm > 59) return;
	ss = getint(buf);
	if (ss < 0 || ss > 59) return;

	time = (hh << 11) | (mm << 5) | (ss >> 1);
	date = ((YY - 80) << 9) | (MM << 5) | DD;
	TSETTIME(time);
	TSETDATE(date);
}

#define NBYTES 19	/* strlen("DD/MM/YY hh:mm:ss\r\n") */

static long
clock_open(f)
	FILEPTR *f;
{
	return 0;
}

static long
clock_write(f, buf, bytes)
	FILEPTR *f; char *buf; long bytes;
{
	static char writebuf[NBYTES];
	static int  bufptr = 0;
	long wrote = 0;

	while (bytes-- > 0 && bufptr < NBYTES) {
/* ignore CR/LF at beginning of line */
		if (bufptr == 0 && (*buf == '\r' || *buf == '\n'))
			buf++;
		else
			writebuf[bufptr++] = *buf++;
		wrote++;
	}

/* do we have a complete date now? if so, set the clock */
	if (bufptr == NBYTES) {
		setclock(writebuf);
		bufptr = 0;
	}
	return wrote;
}

static long
clock_read(f, buf, bytes)
	FILEPTR *f; char *buf; long bytes;
{
	static char readbuf[NBYTES];
	int where;
	long total = 0;

	getclock(readbuf);
	while (f->pos < NBYTES) {
		*buf++ = readbuf[f->pos++];
		total++;
	}
	return total;
}

static long
clock_lseek(f, where, whence)
	FILEPTR *f; long where; int whence;
{
	long newplace;

	switch(whence) {
	case 0:
		newplace = where;
		break;
	case 1:
		newplace = f->pos + where;
		break;
	case 2:
		newplace = (NBYTES-1) - where;
		break;
	}

	if (newplace < 0 || newplace >= NBYTES)
		return ERANGE;

	f->pos = newplace;
	return newplace;
}

static long
clock_ioctl(f, mode, buf)
	FILEPTR *f; int mode; void *buf;
{
	if (mode == FIONREAD || mode == FIONWRITE) {
		*((long *)buf) = (NBYTES-1) - f->pos;
		return 0;
	}
	else
		return EINVFN;
}

static long
clock_datime(f, timeptr, rwflag)
	FILEPTR *f;
	word *timeptr;
	int rwflag;
{
	if (rwflag)
		return EACCDN;
	*timeptr++ = TGETTIME();
	*timeptr = TGETDATE();
	return 0;
}

static long
clock_close(f)
	FILEPTR *f;
{
	return 0;
}

static long
clock_select()
{
	return 1;	/* we're always ready for I/O */
}

static void
clock_unselect()
{
	/* nothing for us to do here */
}
