/* Copyright (c)1994-2000 Begemot Computer Associates. All rights reserved.
 * See the file COPYRIGHT for details of redistribution and use. */

/*
 * TM11 tape
 *
 * Arguments:
 *	ctrl csr_base vector irq
 *	  tape-no file
 *	end
 */

# include "proc.h"
# include <netinet/in.h>

RCSID("$Id: dev_tm.c 512 2006-10-27 12:05:16Z brandt_h $")

# define D(X)	 X 
/* # define DEBUG */

# define TMS	8

/*
 * Status bits
 */
enum {
	MTS_ILLCMD	= 0100000,
	MTS_EOF		= 0040000,
	MTS_CRE		= 0020000,
	MTS_PAE		= 0010000,
	MTS_BGL		= 0004000,
	MTS_EOT		= 0002000,
	MTS_RLE		= 0001000,
	MTS_BTE		= 0000400,
	MTS_NXM		= 0000200,
	MTS_SELR	= 0000100,
	MTS_BOT		= 0000040,
	MTS_7CH		= 0000020,
	MTS_SDWN	= 0000010,
	MTS_WRL		= 0000004,
	MTS_RWS		= 0000002,
	MTS_TUR		= 0000001,

	MTC_ERR		= 0100000,
	MTC_DEN8	= 0040000,
	MTC_DEN5	= 0020000,
	MTC_PCLR	= 0010000,
	MTC_PEVN	= 0004000,
	MTC_SEL		= 0003400,
	MTC_CUR		= 0000200,
	MTC_IE		= 0000100,
	MTC_XBA		= 0000060,
	MTC_FUNC	= 0000016,
	MTC_GO		= 0000001,

	FUNC_OFFL	= 0000000,
	FUNC_READ	= 0000002,
	FUNC_WRITE	= 0000004,
	FUNC_WEOF	= 0000006,
	FUNC_SFWD	= 0000010,
	FUNC_SBWD	= 0000012,
	FUNC_WREXT	= 0000014,
	FUNC_REW	= 0000016,

	MTRD_GAP	= 0010000,
};

typedef struct TM TM;
typedef struct Tape Tape;

struct TM {
	u_int	csr_base;
	u_short	vector;
	u_short	irq_level;

	u_int	ba;		/* current buffer address */
	u_short	bc;		/* byte count register */
	u_int	mtc;		/* 14:13,12,11 */
	u_int	ie;		/* MTC 6 (interrupt enable) */
	u_int	func;		/* MTC 1:3 (function) */
	u_int	sel;		/* MTC 10:8 (select) */
	u_int	cur;		/* MTC 7 */

	struct Tape {
		char	*fn;		/* filename */
		int	fd;		/* file descriptor */
		int	stat;		/* MTS 15,14,10,9,7,6,5,4,2 */
		off_t	*offs;		/* record offsets in file */
		u_short	*rlen;		/* record lengths in file */
		u_int	offn;		/* no of records */
		u_int	pos;		/* current position */
	} 		tape[TMS];
};

void	tm_ctrl_create(iodev_t *, int, char **);
void	tm_dev_create(iodev_t *, int, char **);
void	tm_ctrl_complete(iodev_t *);
void	tm_info(iodev_t *);
u_short	tm_fetch(iodev_t *, u_int);
void	tm_store(iodev_t *, u_int, mmode_t, u_short);
void	tm_reset(iodev_t *);
void	tm_dma(iodev_t *);
u_short	tm_vector(iodev_t *);

static int tmc_info(iodev_t *, int, char **);
static int tmc_load(iodev_t *, int, char **);
static int tmc_unload(iodev_t *, int, char **);
static int tmc_online(iodev_t *, int, char **);
static int tmc_offline(iodev_t *, int, char **);

iocmd_t tm_cmds[] = {
	{ "?",		"[command] ...",	dev_help },
	{ "help",	"[command] ...",	dev_help },
	{ "info",	"",			tmc_info },
	{ "load",	"drive-no file",	tmc_load },
	{ "unload",	"drive-no",		tmc_unload },
	{ "online",	"drive-no",		tmc_online },
	{ "offline",	"drive-no",		tmc_offline },
	{ NULL,		NULL,			NULL }
};

ioops_t tm_ops = {
	tm_ctrl_create,		/* ctrl_create */
	tm_dev_create,		/* dev_create */
	tm_ctrl_complete,	/* ctrl_complete */
	tm_reset,		/* reset */
	tm_fetch,		/* fetch */
	tm_store,		/* store */
	tm_vector,		/* vector */
	tm_dma,			/* dma */
	0,			/* async */
	tm_info,		/* info */
	0,			/* flush */
	tm_cmds,		/* cmds */
};

enum {
	TM_MTS		= 000,
	TM_MTC		= 002,
	TM_BRC		= 004,
	TM_CMA		= 006,
	TM_D		= 010,
	TM_RD		= 012,
};

# ifdef DEBUG
static char *reg_names[] = {
	"TM_MTS",	"TM_MTC",	"TM_BRC",
	"TM_CMA",	"TM_D",		"TM_RD",
};
# endif

static void load(TM *t, int i, char *fn, int isconf);
static void unload(TM *t, int i);
static int get_offsets(int fd, Tape *t);
static void func_sfwd(TM *t);
static void func_sbwd(TM *t);

/*
 * Initialize controller
 */
void
tm_ctrl_create(iodev_t *dev, int argc, char **argv)
{
	TM *t;
	Tape *p;

	if(argc != 3)
		conf_panic("tm: missing args in controller configuration");

	dev->data = t = xalloc(sizeof(TM));
	(void)memset(dev->data, 0, sizeof(TM));

	t->csr_base = parse_csr(argv[0], "tm");
	t->vector = parse_vec(argv[1], "tm");
	t->irq_level = parse_irq(argv[2], "tm");

	proc.iopage[IOP(t->csr_base + TM_MTS)] = dev;
	proc.iopage[IOP(t->csr_base + TM_MTC)] = dev;
	proc.iopage[IOP(t->csr_base + TM_BRC)] = dev;
	proc.iopage[IOP(t->csr_base + TM_CMA)] = dev;
	proc.iopage[IOP(t->csr_base + TM_D  )] = dev;
	proc.iopage[IOP(t->csr_base + TM_RD )] = dev;

	for(p = t->tape; p < &t->tape[TMS]; p++) {
		p->fn = 0;
		p->fd = -1;
		p->stat = 0;
		p->offs = 0;
		p->offn = 0;
		p->rlen = 0;
	}
}

void
tm_dev_create(iodev_t *dev, int argc, char **argv)
{
	TM *t = (TM *)dev->data;
	int i;
	Tape *p;

	if(argc != 2)
		conf_panic("tm: bad args in device description");

	i = (int)strtol(argv[0], 0, 0);
	if(i > TMS)
		conf_panic("tm: controller supports %d disks only", TMS);

	p = &t->tape[i];

	if(p->fd >= 0)
		unload(t, i);
	load(t, i, argv[1], 1);
}

/*
 * Complete controller creation
 */
void
tm_ctrl_complete(iodev_t *dev UNUSED)
{
}

/*
 *  Unload tape from drive i
 */
static void
unload(TM *t, int i)
{
	Tape *p = &t->tape[i];

	if(p->fd < 0)
		return;

	free(p->fn);
	p->fn = 0;

	(void)close(p->fd);
	p->fd = -1;

	p->stat = 0;

	free(p->offs);
	p->offs = 0;

	free(p->rlen);
	p->rlen = 0;

	p->offn = 0;
}

/*
 * Load tape on drive i
 */
static void
load(TM *t, int i, char *fn, int isconf)
{
	typedef void (*pfunc)(char *, ...);
	pfunc ef = isconf ? (pfunc)conf_panic : (pfunc)printf;
	int fd;
	Tape *p = &t->tape[i];

	if((fd = open(fn, O_RDONLY, 0666)) < 0) {
		(*ef)("tm%d: cannot open %s: %s", i, fn, strerror(errno));
		return;
	}
	if(!get_offsets(fd, p)) {
		(*ef)("tm%d: illegal tape format %s", i, fn);
		close(fd);
		return;
	}

	p->fn = xalloc(strlen(fn) + 1);
	strcpy(p->fn, fn);
	p->fd = fd;
	p->stat = MTS_WRL|MTS_TUR|MTS_SELR;
	p->pos = 0;
}

/*
 * Print info
 */
void
tm_info(iodev_t *dev)
{
	TM *t = (TM *)dev->data;
	int i;

	printf("TM11 comtroller\n");
	printf("csr at %08o, vector %03o at level %d\n", t->csr_base,
		t->vector, t->irq_level);
	printf("sel=%d ba=%08o bc=%06o\n",
		t->sel, t->ba, t->bc);
	printf("UNIT\tStat\tNRec\tPos\tFile\n");
	for(i = 0; i < TMS; i++) {
		Tape *p = &t->tape[i];
		printf("%d\t%06o\t%d\t%d\t%s\n", i, p->stat, p->offn, p->pos,
			p->fn ? p->fn : "");
	}
}

/*
 * Reset controller
 */
void
tm_reset(iodev_t *dev)
{
	TM *t = (TM *)dev->data;
	Tape *p;

	t->ba = 0;
	t->bc = 0;
	t->sel = 0;
	t->mtc = 0;
	t->ie = 0;
	t->func = 0;
	t->cur = MTC_CUR;

	for(p = t->tape; p < &t->tape[TMS]; p++) {
		if(p->fd >= 0) {
			p->stat &= ~(MTS_EOF|MTS_RLE);
			p->stat |= MTS_WRL|MTS_TUR;
		} else {
			p->stat = 0;
		}
	}
}

/*
 * Fetch controller register
 */
u_short
tm_fetch(iodev_t *dev, u_int a)
{
	TM *t = (TM *)dev->data;
	u_short v;

	switch(a - t->csr_base) {

	  case TM_MTS:
		if(t->tape[t->sel].stat & MTS_SELR) {
			v = t->tape[t->sel].stat;
			if(t->tape[t->sel].pos == 0)
				v |= MTS_BOT;
		} else
			v = 0;
		break;

	  case TM_MTC:
		v = ((t->tape[t->sel].stat &
			(MTS_ILLCMD|MTS_EOF|MTS_CRE|MTS_PAE|MTS_BGL|MTS_EOT|
			 MTS_RLE|MTS_BTE|MTS_NXM)) ? MTC_ERR : 0)
		    | (t->sel << 8) | t->cur | t->ie
		    | (((t->ba >> 16) & 0x3) << 4) | t->func;
		break;

	  case TM_BRC:
		v = t->bc;
		break;

	  case TM_CMA:
		v = t->ba;
		break;

	  case TM_D:
	  case TM_RD:
		v = 0;
		break;

	  default:
		warn("tm_fetch(%o)", a);
		Trap4(020);
	}

# ifdef DEBUG
	printf("tm_fetch(%s)=%06o\n", reg_names[(a - t->csr_base) >> 1], v);
# endif
	return v;
}

void
tm_store(iodev_t *dev, u_int a, mmode_t mode, u_short v)
{
	TM *t = (TM *)dev->data;

# ifdef DEBUG
	printf("tm_store(%s,%o)=%06o\n", reg_names[(a - t->csr_base) >> 1], mode, v);
# endif

	switch(a - t->csr_base) {

	  case TM_MTS:
		/* read only */
		break;

	  case TM_MTC:
		if(mode == M_Word || mode == M_High) {
			if(v & MTC_PCLR)
				tm_reset(dev);
			t->mtc = v & (MTC_DEN8|MTC_DEN5|MTC_PEVN);
			t->sel = (v & MTC_SEL) >> 8;
		}
		if(mode == M_Word || mode == M_Low) {
			t->ba = (t->ba & ~0600000) | ((v & MTC_XBA) << 12);
			t->func = v & MTC_FUNC;
			if(!t->ie && (v & (MTC_IE | MTC_GO)) == MTC_IE) {
				/*
				 * Request interrupt if just the IE bit is
				 * flipped on (no GO). In that case don't
				 * do anything else. XXX Shouldn't this
				 * happend only if t->cur == MTC_CUR?.
				 */
				t->ie = MTC_IE;
				IRQ(dev, t->irq_level);
				break;
			}
			if(!(v & MTC_GO)) {
				/*
				 * XXX if we switch off IE and we have an
				 * interrupt pending shouldn't we stop
				 * that interrupt?
				 */
				t->ie = v & MTC_IE;
				break;
			}
			t->ie = v & MTC_IE;

			t->tape[t->sel].stat &= ~(MTS_ILLCMD|MTS_EOF|MTS_CRE|
				MTS_PAE|MTS_BGL|MTS_RLE|MTS_BTE|MTS_NXM);
			t->cur = 0;

			if(!(t->tape[t->sel].stat & MTS_SELR)) {
				t->tape[t->sel].stat |= MTS_ILLCMD;
				t->cur = MTC_CUR;

			} else {
				switch(t->func) {

				  case FUNC_OFFL:
					t->tape[t->sel].stat &= ~MTS_SELR;
					t->cur = MTC_CUR;
					break;

				  case FUNC_WRITE:
				  case FUNC_WEOF:
				  case FUNC_WREXT:
					t->tape[t->sel].stat |= MTS_ILLCMD;
					t->cur = MTC_CUR;
					break;

				  case FUNC_READ:
					IRQ(dev, DMAPRI);
					break;

				  case FUNC_SFWD:
					func_sfwd(t);
					t->cur = MTC_CUR;
					break;

				  case FUNC_SBWD:
					func_sbwd(t);
					t->cur = MTC_CUR;
					break;

				  case FUNC_REW:
					t->tape[t->sel].pos = 0;
					t->cur = MTC_CUR;
					break;
				}
			}
			if(t->ie && t->cur)
				IRQ(dev, t->irq_level);
		}
		break;

	  case TM_BRC:
		SETWORD(t->bc, mode, v);
		break;

	  case TM_CMA:
		switch(mode) {

		  case M_Low:
			t->ba = (t->ba & ~0377) | (v & 0377);
			break;

		  case M_High:
			t->ba = (t->ba & ~0177400) | (v & 0177400);
			break;

		  case M_Word:
			t->ba = (t->ba & ~0177777) | (v & 0177777);
			break;
		}
		break;

	  case TM_D:
	  case TM_RD:
		/* read only */
		break;

	  default:
		warn("tm_store(%o)", a);
		Trap4(020);
	}
}

/*
 * User commands
 */
static int chkdma(iodev_t *);
static int get_unit(TM *, char *);

/*
 * Print info
 */
static int
tmc_info(iodev_t *dev, int argc, char **argv UNUSED)
{
	if(argc != 0)
		return 1;
	tm_info(dev);
	return 0;
}

/*
 * Load new tape onto drive
 */
static int
tmc_load(iodev_t *dev, int argc, char **argv)
{
	TM *t = (TM *)dev->data;
	int unit;

	if(argc != 2)
		return 1;
	if(chkdma(dev))
		return 0;
	if((unit = get_unit(t, argv[0])) < 0)
		return 0;
	if(t->tape[unit].fd >= 0) {
		printf("tm%d: already loaded\n", unit);
		return 0;
	}
	load(t, unit, argv[1], 0);
	if(t->tape[unit].fd >= 0)
		printf("tm%d: loaded\n", unit);
	return 0;
}

/*
 * Unload tape
 */
static int
tmc_unload(iodev_t *dev, int argc, char **argv)
{
	TM *t = (TM *)dev->data;
	int unit;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((unit = get_unit(t, argv[0])) < 0)
		return 0;
	if(t->tape[unit].fd < 0) {
		printf("tm%d: not loaded\n", unit);
		return 0;
	}
	unload(t, unit);
	if(t->tape[unit].fd < 0)
		printf("tm%d: unloaded\n", unit);
	return 0;
}

static int
tmc_online(iodev_t *dev, int argc, char **argv)
{
	TM *t = (TM *)dev->data;
	int unit;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((unit = get_unit(t, argv[0])) < 0)
		return 0;
	if(t->tape[unit].fd < 0) {
		printf("tm%d: not loaded\n", unit);
		return 0;
	}
	if(t->tape[unit].stat & MTS_SELR) {
		printf("tm%d: already online\n", unit);
		return 0;
	}
	t->tape[unit].stat |= MTS_SELR;
	printf("tm%d: online\n", unit);
	return 0;
}

static int
tmc_offline(iodev_t *dev, int argc, char **argv)
{
	TM *t = (TM *)dev->data;
	int unit;

	if(argc != 1)
		return 1;
	if(chkdma(dev))
		return 0;
	if((unit = get_unit(t, argv[0])) < 0)
		return 0;
	if(t->tape[unit].fd < 0) {
		printf("tm%d: not loaded\n", unit);
		return 0;
	}
	if(!(t->tape[unit].stat & MTS_SELR)) {
		printf("tm%d: already offline\n", unit);
		return 0;
	}
	t->tape[unit].stat &= ~MTS_SELR;
	printf("tm%d: offline\n", unit);
	return 0;
}

/*
 * Disallow changes, if DMA is pending
 */
static int
chkdma(iodev_t *dev)
{
	if(dev->reqpri == DMAPRI) {
		printf("tm: DMA request pending -- try again\n");
		return 1;
	}
	return 0;
}

static int
get_unit(TM *t UNUSED, char *arg)
{
	long unit;
	char *end;

	unit = strtol(arg, &end, 0);

	if(*end || unit < 0 || unit > TMS) {
		printf("tm: bad unit number '%s'\n", arg);
		return -1;
	}
	return unit;
}

/*
 * Read through the file, record all records.
 */
static int
get_offsets(int fd, Tape *t)
{
	struct stat statb;
	off_t pos;
	u_int i;
	int ret;

	if(fstat(fd, &statb))
		return 0;
	if((statb.st_mode & S_IFMT) != S_IFREG)
		return 0;

	if(read(fd, &t->offn, sizeof(u_int)) != sizeof(u_int))
		return 0;
	t->offn = ntohl(t->offn);
	if(t->offn == 0)
		return 0;

	t->offs = xalloc(sizeof(off_t) * t->offn);
	t->rlen = xalloc(sizeof(u_short) * t->offn);

	if((ret = read(fd, t->rlen, sizeof(u_short)*t->offn)) < 0)
		goto err;
	if((u_int)ret != sizeof(u_short)*t->offn)
		goto err;

	pos = sizeof(u_int) + sizeof(u_short) * t->offn;
	for(i = 0; i < t->offn; i++) {
		t->offs[i] = pos;
		t->rlen[i] = ntohs(t->rlen[i]);
		pos += t->rlen[i];
	}
	if(pos != statb.st_size)
		goto err;

	return 1;

  err:
	free(t->offs);
	free(t->rlen);
	t->offn = 0;
	t->offs = 0;
	t->rlen = 0;

	return 0;
}

void
tm_dma(iodev_t *dev)
{
	TM *t = (TM *)dev->data;
	Tape *p = &t->tape[t->sel];
	u_char *a, *e;
	u_int b;
	int ret;

	t->cur = MTC_CUR;
	dev->reqpri = 0;
	if(t->ie)
		IRQ(dev, t->irq_level);

	a = (u_char *)proc.memops->addrphys(proc.mmg, t->ba);
	e = (u_char *)proc.memops->addrphys(proc.mmg, proc.physmem);
	b = 0200000 - t->bc;

	if(p->pos == p->offn) {
		p->stat |= MTS_EOT;
		return;
	}
	if(p->rlen[p->pos] == 0) {
		p->stat |= MTS_EOF;
		p->pos++;
		return;
	}
	if(p->rlen[p->pos] > b) {
		p->stat |= MTS_RLE;
	} else if(p->rlen[p->pos] < b) {
		b = p->rlen[p->pos];
	}
	if(lseek(p->fd, p->offs[p->pos], SEEK_SET) != p->offs[p->pos])
		panic("tm: lseek failed: %s", strerror(errno));

	p->pos++;

	if(a + b > e) {
		b -= (a + b) - e;
		p->stat |= MTS_NXM;
	}
	t->bc += b;
	t->ba += b;
	if((ret = read(p->fd, a, b)) < 0)
		panic("tm: %s: %s", p->fn, strerror(errno));
	if((u_int)ret != b)
		panic("tm: %s: bad read %d %u", p->fn, ret, b);
}

static void
func_sfwd(TM *t)
{
	Tape *p = &t->tape[t->sel]; 

	D(printf("SFWD %u: %u -> ", t->bc, p->pos));
	do {
		if(p->pos == p->offn) {
			p->stat |= MTS_EOT;
			break;
		}
		p->pos++;
		t->bc++;
		if(p->rlen[p->pos - 1] == 0) {
			p->stat |= MTS_EOF;
			break;
		}
	} while(t->bc != 0);
	D(printf("%u, stat=%06o\n", p->pos, p->stat));
}

static void
func_sbwd(TM *t)
{
	Tape *p = &t->tape[t->sel]; 

	D(printf("SFWD %u: %u -> ", t->bc, p->pos));
	do {
		if(p->pos == 0) {
			p->stat |= MTS_BOT;
			break;
		}
		p->pos--;
		t->bc++;
		if(p->rlen[p->pos] == 0) {
			p->stat |= MTS_EOF;
			break;
		}
	} while(t->bc != 0);
	D(printf("%u, stat=%06o\n", p->pos, p->stat));
}

/*
 * interrupt ack.
 */
u_short
tm_vector(iodev_t *dev)
{
	TM 	*t = (TM *)dev->data;

	dev->reqpri = 0;

	return t->vector;
}

