/*
 * This file has been modified as part of the MH-MiNT project. See
 * the file Changes.MH for details and dates.
 */

/*
Copyright 1991,1992 Eric R. Smith.
Copyright 1993,1994 Atari Corporation.
All rights reserved.
 */

/* simple biosfs.c */

#include "mint.h"

extern struct kerinfo kernelinfo;	/* see main.c */

static long	ARGS_ON_STACK bios_root	P_((int drv, fcookie *fc));
static long	ARGS_ON_STACK bios_lookup	P_((fcookie *dir, const char *name, fcookie *fc));
static long	ARGS_ON_STACK bios_getxattr	P_((fcookie *fc, XATTR *xattr));
static long	ARGS_ON_STACK bios_chattr	P_((fcookie *fc, int attrib));
static long	ARGS_ON_STACK bios_chown	P_((fcookie *fc, int uid, int gid));
static long	ARGS_ON_STACK bios_chmode	P_((fcookie *fc, unsigned mode));
static long	ARGS_ON_STACK bios_rmdir	P_((fcookie *dir, const char *name));
static long	ARGS_ON_STACK bios_remove	P_((fcookie *dir, const char *name));
static long	ARGS_ON_STACK bios_getname	P_((fcookie *root, fcookie *dir, char *pathname, int size));
static long	ARGS_ON_STACK bios_rename	P_((fcookie *olddir, char *oldname,
				    fcookie *newdir, const char *newname));
static long	ARGS_ON_STACK bios_opendir	P_((DIR *dirh, int flags));
static long	ARGS_ON_STACK bios_readdir	P_((DIR *dirh, char *nm, int nmlen, fcookie *fc));
static long	ARGS_ON_STACK bios_rewinddir	P_((DIR *dirh));
static long	ARGS_ON_STACK bios_closedir	P_((DIR *dirh));
static long	ARGS_ON_STACK bios_pathconf	P_((fcookie *dir, int which));
static long	ARGS_ON_STACK bios_dfree	P_((fcookie *dir, long *buf));
static DEVDRV *	ARGS_ON_STACK bios_getdev	P_((fcookie *fc, long *devspecial));
static long	ARGS_ON_STACK bios_fscntl	P_((fcookie *, const char *, int, long));
static long	ARGS_ON_STACK bios_symlink	P_((fcookie *, const char *, const char *));
static long	ARGS_ON_STACK bios_readlink	P_((fcookie *, char *, int));

static long	ARGS_ON_STACK bios_topen	P_((FILEPTR *f));
static long	ARGS_ON_STACK bios_twrite	P_((FILEPTR *f, const char *buf, long bytes));
static long	ARGS_ON_STACK bios_tread	P_((FILEPTR *f, char *buf, long bytes));
static long	ARGS_ON_STACK bios_writeb	P_((FILEPTR *f, const char *buf, long bytes));
static long	ARGS_ON_STACK bios_readb	P_((FILEPTR *f, char *buf, long bytes));
static long	ARGS_ON_STACK bios_nwrite	P_((FILEPTR *f, const char *buf, long bytes));
static long	ARGS_ON_STACK bios_nread	P_((FILEPTR *f, char *buf, long bytes));
static long	ARGS_ON_STACK bios_ioctl	P_((FILEPTR *f, int mode, void *buf));
static long	ARGS_ON_STACK bios_select	P_((FILEPTR *f, long p, int mode));
static void	ARGS_ON_STACK bios_unselect	P_((FILEPTR *f, long p, int mode));
static long	ARGS_ON_STACK bios_tseek	P_((FILEPTR *f, long where, int whence));
static long	ARGS_ON_STACK bios_close	P_((FILEPTR *f, int pid));

long	ARGS_ON_STACK null_open	P_((FILEPTR *f));
long	ARGS_ON_STACK null_write	P_((FILEPTR *f, const char *buf, long bytes));
long	ARGS_ON_STACK null_read	P_((FILEPTR *f, char *buf, long bytes));
long	ARGS_ON_STACK null_lseek	P_((FILEPTR *f, long where, int whence));
long	ARGS_ON_STACK null_ioctl	P_((FILEPTR *f, int mode, void *buf));
long	ARGS_ON_STACK null_datime	P_((FILEPTR *f, short *time, int rwflag));
long	ARGS_ON_STACK null_close	P_((FILEPTR *f, int pid));
long	ARGS_ON_STACK null_select	P_((FILEPTR *f, long p, int mode));
void	ARGS_ON_STACK null_unselect	P_((FILEPTR *f, long p, int mode));

static long ARGS_ON_STACK mouse_open	P_((FILEPTR *f));
static long ARGS_ON_STACK mouse_read	P_((FILEPTR *f, char *buf, long nbytes));
static long ARGS_ON_STACK mouse_ioctl P_((FILEPTR *f, int mode, void *buf));
static long ARGS_ON_STACK mouse_close P_((FILEPTR *f, int pid));
static long ARGS_ON_STACK mouse_select P_((FILEPTR *f, long p, int mode));
static void ARGS_ON_STACK mouse_unselect P_((FILEPTR *f, long p, int mode));

/* device driver for BIOS terminals */

DEVDRV bios_tdevice = {
	bios_topen, bios_twrite, bios_tread, bios_tseek, bios_ioctl,
	null_datime, bios_close, bios_select, bios_unselect,
	bios_writeb, bios_readb
};

/* device driver for BIOS devices that are not terminals */

DEVDRV bios_ndevice = {
	null_open, bios_nwrite, bios_nread, null_lseek, bios_ioctl,
	null_datime, bios_close, bios_select, bios_unselect
};

DEVDRV null_device = {
	null_open, null_write, null_read, null_lseek, null_ioctl,
	null_datime, null_close, null_select, null_unselect
};

DEVDRV mouse_device = {
	mouse_open, null_write, mouse_read, null_lseek, mouse_ioctl,
	null_datime, mouse_close, mouse_select, mouse_unselect
};

/* this special driver is checked for in dosfile.c, and indicates that
 * a dup operation is actually wanted rather than an open
 */
DEVDRV fakedev;

#ifdef FASTTEXT
extern DEVDRV screen_device;	/* see fasttext.c */
#endif

FILESYS bios_filesys = {
	(FILESYS *)0,
	FS_LONGPATH | FS_NO_C_CACHE,
	bios_root,
	bios_lookup, nocreat, bios_getdev, bios_getxattr,
	bios_chattr, bios_chown, bios_chmode,
	nomkdir, bios_rmdir, bios_remove, bios_getname, bios_rename,
	bios_opendir, bios_readdir, bios_rewinddir, bios_closedir,
	bios_pathconf, bios_dfree, nowritelabel, noreadlabel,
	bios_symlink, bios_readlink, nohardlink, bios_fscntl, nodskchng
};


struct tty con_tty, aux_tty, midi_tty;
struct tty sccb_tty, scca_tty, ttmfp_tty;

struct bios_file BDEV[] = {

/* "real" bios devices present on all machines */
	{"centr", &bios_ndevice, 0, 0, 0, 0},
	{"console", &bios_tdevice, 2, O_TTY, &con_tty, 0},
	{"midi", &bios_tdevice, 3, O_TTY, &midi_tty, 0},
	{"kbd", &bios_ndevice, 4, 0, 0, 0},
/* devices that duplicate handles */
	{"prn", &fakedev, -3, 0, 0, 0}, /* handle -3 (printer) */
	{"aux", &fakedev, -2, 0, 0, 0}, /* handle -2 (aux. terminal) */
	{"con", &fakedev, -1, 0, 0, 0}, /* handle -1 (control terminal) */
	{"tty", &fakedev, -1, 0, 0, 0}, /* the Unix name for it */
	{"stdin", &fakedev, 0, 0, 0, 0},  /* handle 0 (stdin) */
	{"stdout", &fakedev, 1, 0, 0, 0}, /* handle 1 (stdout) */
	{"stderr", &fakedev, 2, 0, 0, 0}, /* handle 2 (stderr) */
	{"fd", &fakedev, S_IFDIR, 0, 0, 0}, /* file descriptor directory */

/* other miscellaneous devices */
	{"mouse", &mouse_device, 0, 0, 0, 0},
	{"null", &null_device, 0, 0, 0, 0},

#ifdef FASTTEXT
/* alternate console driver */
	{"fasttext", &screen_device, 2, O_TTY, &con_tty, 0},
#endif

/* serial port things *must* come last, because not all of these
 * are present on all machines (except for modem1, which does however
 * have a different device number on TTs and STs)
 */
	{"modem1", &bios_tdevice, 6, O_TTY, &aux_tty, 0},
	{"modem2", &bios_tdevice, 7, O_TTY, &sccb_tty, 0},
	{"serial1", &bios_tdevice, 8, O_TTY, &ttmfp_tty, 0},
	{"serial2", &bios_tdevice, 9, O_TTY, &scca_tty, 0},
	{"", 0, 0, 0, 0, 0}
};

#define xconstat ((long *)0x51eL)
#define xconin 	((long *)0x53eL)
#define xcostat ((long *)0x55eL)
#define xconout	((long *)0x57eL)

extern BCONMAP2_T *bconmap2;		/* bconmap struct */
#define MAPTAB (bconmap2->maptab)

#define MAXBAUD 16

/* keep these sorted in descending order */
static long baudmap[MAXBAUD] = {
19200L, 9600L, 4800L, 3600L, 2400L, 2000L, 1800L, 1200L,
600L, 300L, 200L, 150L, 134L, 110L, 75L, 50L
};

INLINE static void scc_set5 P_((volatile char *control, int setp,
	unsigned bits, IOREC_T *iorec));
static void ARGS_ON_STACK wakewrite P_ ((PROC *p));
INLINE static long iocsflagsb P_ ((int bdev, unsigned long flags, 
				   unsigned long mask, 
				   struct tty *tty, 
				   struct bios_tty *t));

/* set/reset bits in SCC w5 */
INLINE static void scc_set5 (control, setp, bits, iorec)
volatile char *control;
int setp;
unsigned bits;
IOREC_T *iorec;
{
	volatile char dummy;

	short sr = spl7();

#if 1
/* sanity check: if the w5 copy at offset 1d has bit 3 off something is wrong */
	if (!(((char *) iorec)[0x1d] & 8)) {
		spl(sr);
		ALERT ("scc_set5: iorec %lx w5 copy has sender enable bit off, w5 not changed", iorec);
		return;
	}
#endif
	dummy = *((volatile char *) 0xfffffa01L);
	*control = 5;
	dummy = *((volatile char *) 0xfffffa01L);
	if (setp)
		*control = (((char *) iorec)[0x1d] |= bits);
	else
		*control = (((char *) iorec)[0x1d] &= ~bits);
	spl(sr);
#ifdef __TURBOC__
	setp = dummy; /* jr: get rid of warning regarding dummy */
#endif
}

#define	MAX_BTTY	4	/* 4 bios_tty structs */

/* find bios_tty struct for a FILEPTR
 */
#define BTTY(f) ((((struct tty *)(f)->devinfo) == &aux_tty) ? bttys : \
		 ((has_bconmap && (unsigned)(f)->fc.aux-6 < btty_max) ? \
		    bttys+(f)->fc.aux-6 : 0))

struct bios_tty bttys[MAX_BTTY], midi_btty;
short	btty_max;

/* RSVF cookie value (read in main.c) */
long rsvf;

/* try to get a fd for a BIOS tty to pass some ioctls to... */

INLINE static short rsvf_open P_((int bdev));
INLINE static long rsvf_close P_((int f));

INLINE static short
rsvf_open (int bdev)
{
	long ret = EUNDEV;
	struct rsvfdev {
		union {
			char *name;
			struct rsvfdev *next;
		} f;
		char flags, unused1, bdev, unused2;
	} *r = (struct rsvfdev *)rsvf;

	while (r) {
		if (r->flags >= 0) {
			r = r->f.next;
			continue;
		}
		if ((r->flags & 0xe0) == 0xe0 && r->bdev == bdev) {
			char rname[0x80] = "u:\\dev\\";

			strncpy (rname + sizeof "u:\\dev\\" - 1, r->f.name,
				(int) (sizeof rname - sizeof "u:\\dev\\"));
			ret = Fopen (rname, O_RDWR);
			if (ret < MIN_HANDLE || ret > 0x8000) {
				ALERT ("rsvf_open(%d): Fopen %s returned %lx",
					bdev, rname, ret);
				return EUNDEV;
			}
			break;
		}
		++r;
	}
	return ret;
}

INLINE static long
rsvf_close (int f)
{
	long ret = EIHNDL;
	if (f != EUNDEV) {
		ret = Fclose (f);
		if (ret)
			ALERT ("rsvf_close(%d): Fclose %x returned %lx", f, ret);
	}
	return ret;
}

INLINE long
rsvf_ioctl(f, arg, mode)
	int f;
	void *arg;
	int mode;
{
	if (f == EUNDEV)
		return EINVFN;
	TRACE(("rsvf_ioctl: passing ioctl %x (tosfd=0x%x)", mode, f));
	/* is there a more direct way than this? */
	return Fcntl (f, (long)arg, mode);
}

extern int tosvers;	/* from main.c */

/* Does the fcookie fc refer to the \dev\fd directory? */
#define IS_FD_DIR(fc) ((fc)->aux == S_IFDIR)
/* Does the fcookie fc refer to a file in the \dev\fd directory? */
#define IS_FD_ENTRY(fc) ((fc)->index > 0 && (fc)->index <= MAX_OPEN-MIN_HANDLE)

struct bios_file *broot, *bdevlast;

/* a file pointer for BIOS device 1, provided only for insurance
 * in case a Bconmap happens and we can't allocate a new FILEPTR;
 * in most cases, we'll want to build a FILEPTR in the usual
 * way.
 */

FILEPTR *defaultaux;

/* ts: a xattr field used for the root directory, 'cause there's no
 * bios_file structure for it.
 */
XATTR rxattr;
XATTR fdxattr;

/* ts: a small utility function to set up a xattr structure
 */

static void set_xattr P_((XATTR *xp, ushort mode, int rdev));

void set_xattr(xp, mode, rdev)
	XATTR *xp;
	ushort mode;
	int rdev;
{
	xp->mode = mode;
	xp->index = 0L;
	xp->dev = BIOSDRV;
	xp->rdev = rdev;
	xp->nlink = 1;
	xp->uid = curproc->euid;
	xp->gid = curproc->egid;
	xp->size = 0L;
	xp->blksize = 1024L;
	xp->nblocks = 0L;

	xp->mtime = xp->atime = xp->ctime = timestamp;
	xp->mdate = xp->adate = xp->cdate = datestamp;

/* root directory only */
	if ((mode & S_IFMT) == S_IFDIR)
		xp->attr = FA_DIR;
	else
		xp->attr = 0;
	xp->reserved2 = 0;
	xp->reserved3[0] = 0L;
	xp->reserved3[1] = 0L;
}

void
biosfs_init()
{
	struct bios_file *b, *c;
	int majdev, mindev;
	int i;

	broot = BDEV;

	c = (struct bios_file *)0;
	for (b = broot; b->name[0]; b++) {
		b->next = b+1;

	/* Save a pointer to the first serial port */
		if (b->private == 6)
			c = b;
		if (b->device->readb && b->tty != &con_tty)
	/* device has DEVDRV calls beyond unselect */
			b->drvsize = offsetof (DEVDRV, readb) + sizeof (long);

	/* if not a TT or Mega STE, adjust the MODEM1 device to be BIOS
	 * device 1
	 * and ignore the remaining devices, since they're not present
	 */
		if (b->private == 6 &&
		    (!has_bconmap || bconmap2->maptabsize == 1)) {
			if (!has_bconmap)
				b->private = 1;
			b->next = 0;
			break;
		}
	/* SERIAL1(!) is not present on the Mega STe or Falcon,
	 * device 8 is SCC channel A
	 */
		if (mch != TT && b->private == 8) {
			b->name[6] = '2';	/* "serial2" */
			b->tty = &scca_tty;
			b->next = 0;
			break;
		}
	}
	bdevlast = b;
	if (b->name[0] == 0) {
		--b;
		b->next = 0;
	}
	/* Initialize bios_tty structures */
	for (i=0;c && i<MAX_BTTY;c=c->next, i++) {
		if (has_bconmap)
			bttys[i].irec = MAPTAB[c->private-6].iorec;
		else
			bttys[i].irec = Iorec(0);
		bttys[i].orec = bttys[i].irec+1;
		bttys[i].rsel = &(c->tty->rsel);
		bttys[i].wsel = &(c->tty->wsel);
		bttys[i].baudmap = baudmap;
		bttys[i].maxbaud = MAXBAUD;
		bttys[i].baudx = NULL;
		*c->tty = default_tty;
		bttys[i].tty = c->tty;
		bttys[i].clocal = 1;	/* default off would be better but
					   likely confuses old programs... :/ */
		bttys[i].brkint = 1;
		bttys[i].tosfd = EUNDEV;
		bttys[i].bdev = c->private;
	}
	btty_max = i;

	midi_btty.irec = Iorec(2);
	midi_btty.rsel = &midi_tty.rsel;
	midi_btty.wsel = &midi_tty.wsel;
	midi_btty.tty = &midi_tty;
	midi_tty = default_tty;
	midi_btty.clocal = 1;
	midi_btty.tosfd = EUNDEV;
	midi_btty.bdev = 3;

	defaultaux = new_fileptr();
	defaultaux->links = 1;		/* so it never gets freed */
	defaultaux->flags = O_RDWR;
	defaultaux->pos = 0;
	defaultaux->devinfo = 0;
	defaultaux->fc.fs = &bios_filesys;
	defaultaux->fc.index = 0;
	defaultaux->fc.aux = 1;
	defaultaux->fc.dev = BIOSDRV;
	defaultaux->dev = &bios_ndevice;

/* set up XATTR fields */
	set_xattr(&rxattr, S_IFDIR|DEFAULT_DIRMODE, BIOSDRV);
	set_xattr(&fdxattr, S_IFDIR|DEFAULT_DIRMODE, BIOSDRV);

	for (b = BDEV; b; b = b->next) {
		if (b->device == &bios_ndevice || b->device == &bios_tdevice) {
			majdev = BIOS_RDEV;
			mindev = b->private;
		} else if (b->device == &fakedev) {
			majdev = FAKE_RDEV;
			mindev = b->private;
		} else {
			majdev = UNK_RDEV;
			mindev = b->private;
		}
		set_xattr(&b->xattr, S_IFCHR|DEFAULT_MODE,
			  majdev | (mindev & 0x00ff) );
	}
}

static long ARGS_ON_STACK 
bios_root(drv, fc)
	int drv;
	fcookie *fc;
{
	if (drv == BIOSDRV) {
		fc->fs = &bios_filesys;
		fc->dev = drv;
		fc->index = 0L;
		return 0;
	}
	fc->fs = 0;
	return EINTRN;
}

static long ARGS_ON_STACK 
bios_lookup(dir, name, fc)
	fcookie *dir;
	const char *name;
	fcookie *fc;
{
	struct bios_file *b;

	if (dir->index != 0) {
	/* check for \dev\fd directory */
		if (!IS_FD_DIR(dir)) {
			DEBUG(("bios_lookup: bad directory"));
			return EPTHNF;
		}
		if (!*name || (name[0] == '.' && name[1] == 0)) {
			*fc = *dir;
			return 0;
		}
		if (!strcmp(name, "..")) {
		/* root directory */
			fc->fs = &bios_filesys;
			fc->dev = dir->dev;
			fc->index = 0L;
			return 0;
		}
		if (isdigit(*name) || *name == '-') {
			int fd = (int) atol(name);
			if (fd >= MIN_HANDLE && fd < MAX_OPEN) {
				fc->fs = &bios_filesys;
				fc->dev = dir->dev;
				fc->aux = fd;
				fc->index = fd - MIN_HANDLE + 1;
				return 0;
			}
		}
		DEBUG(("bios_lookup: name (%s) not found", name));
		return EFILNF;
	}

/* special case: an empty name in a directory means that directory */
/* so does "." */
	if (!*name || (name[0] == '.' && name[1] == 0)) {
		*fc = *dir;
		return 0;
	}

/* another special case: ".." could be a mount point */
	if (!strcmp(name, "..")) {
		*fc = *dir;
		return EMOUNT;
	}

	for (b = broot; b; b = b->next) {
		if (!stricmp(b->name, name)) {
			fc->fs = &bios_filesys;
			fc->index = (long)b;
			fc->aux = b->private;
			fc->dev = dir->dev;
			return 0;
		}
	}
	DEBUG(("bios_lookup: name(%s) not found", name));
	return EFILNF;
}

static long ARGS_ON_STACK 
bios_getxattr(fc, xattr)
	fcookie *fc;
	XATTR *xattr;
{
#ifdef FOLLOW_XATTR_CHAIN
	FILEPTR *f;
	long r;
#endif
	struct bios_file *b = (struct bios_file *)fc->index;
	int majdev, mindev;

	majdev = UNK_RDEV;
	mindev = 0;

	if (fc->index == 0) {			/* root directory? */
		*xattr = rxattr;
		xattr->index = fc->index;
		xattr->dev = fc->dev;
	} else if (IS_FD_DIR(fc)) {		/* fd directory? */
		*xattr = fdxattr;
		xattr->index = fc->index;
		xattr->dev = fc->dev;
	} else if (IS_FD_ENTRY(fc)) {
		/* u:\dev\fd\n */
#ifdef FOLLOW_XATTR_CHAIN
		f = curproc->handle[(int)fc->aux];
		if (f) {
			r = (*f->fc.fs->getxattr)(&f->fc, xattr);
			if (r < 0)
				return r;
		} else {
#endif
			majdev = FAKE_RDEV;
			mindev = ((int)fc->aux) & 0x00ff;
			set_xattr(xattr, S_IFCHR | DEFAULT_MODE, majdev|mindev);
#ifndef FOLLOW_XATTR_CHAIN
			xattr->index = fc->index;
#else
		}
#endif
	} else if (b->device == &fakedev) {
#ifdef FOLLOW_XATTR_CHAIN
		if ((f = curproc->handle[b->private]) != 0) {
		    /* u:\dev\stdin, u:\dev\stdout, etc. */
		    r = (*f->fc.fs->getxattr) (&f->fc, xattr);
		    if (r < 0) return r;
		} else {
#endif
			majdev = FAKE_RDEV;
			mindev = ((int)b->private) & 0x00ff;
			set_xattr(xattr, S_IFCHR|DEFAULT_MODE, majdev|mindev);
#ifndef FOLLOW_XATTR_CHAIN
			xattr->index = fc->index;
#else
		}
#endif
	} else {
		*xattr = b->xattr;
		xattr->index = fc->index;
		xattr->dev = fc->dev;
	}
	return 0;
}

static long ARGS_ON_STACK 
bios_chattr(fc, attrib)
	fcookie *fc;
	int attrib;
{
	UNUSED(fc); UNUSED(attrib);
	return EACCDN;
}

static long ARGS_ON_STACK 
bios_chown(fc, uid, gid)
	fcookie *fc;
	int uid, gid;
{
	struct bios_file *b = (struct bios_file *)fc->index;

	if (!(curproc->euid)) {
		if (!b) {
			/* a directory */
			rxattr.uid = uid;
			rxattr.gid = gid;
		} else if (IS_FD_DIR(fc)) {
			fdxattr.uid = uid;
			fdxattr.gid = gid;
		} else if (!IS_FD_ENTRY(fc)) {
			/* any other entry */
			b->xattr.uid = uid;
			b->xattr.gid = gid;
		}
		return 0;
	}

	return EACCDN;
}

static long ARGS_ON_STACK 
bios_chmode(fc, mode)
	fcookie *fc;
	unsigned mode;
{
	struct bios_file *b = (struct bios_file *)fc->index;

	if (!b) {
		/* root directory */
		if (!curproc->euid || (curproc->euid == rxattr.uid)) {
			rxattr.mode = (rxattr.mode & S_IFMT) | mode;
			return 0;
		}
	} else if (IS_FD_DIR(fc)) {
		if (!curproc->euid || (curproc->euid == fdxattr.uid)) {
			fdxattr.mode = (fdxattr.mode & S_IFMT) | mode;
			return 0;
		}
	} else if (!IS_FD_ENTRY(fc)) {
		if (!curproc->euid || (curproc->euid == b->xattr.uid)) {
			b->xattr.mode = (b->xattr.mode & S_IFMT) | mode;
			return 0;
		}
	}

	return EACCDN;
}

long ARGS_ON_STACK 
nomkdir(dir, name, mode)
	fcookie *dir;
	const char *name;
	unsigned mode;
{
	UNUSED(dir); UNUSED(name);
	UNUSED(mode);
	return EACCDN;
}

static long ARGS_ON_STACK 
bios_rmdir(dir, name)
	fcookie *dir;
	const char *name;
{
	return bios_remove(dir, name);
}

/*
 * MAJOR BUG: we don't check here for removal of devices for which there
 * are still open files
 */

static long ARGS_ON_STACK 
bios_remove(dir, name)
	fcookie *dir;
	const char *name;
{
	struct bios_file *b, **lastb;

	if (curproc->euid)
		return EACCDN;

/* don't allow removal in the fd directory */
	if (IS_FD_DIR(dir))
		return EACCDN;

	lastb = &broot;
	for (b = broot; b; b = *(lastb = &b->next)) {
		if (!stricmp(b->name, name)) break;
	}
	if (!b) return EFILNF;

/* don't allow removal of the device if we don't own it */
	if (curproc->euid && (curproc->euid != b->xattr.uid)) {
		return EACCDN;
	}

/* don't allow removal of the basic system devices */
	if (b >= BDEV && b <= bdevlast) {
		return EACCDN;
	}
	*lastb = b->next;

	if (b->device == 0 || b->device == &bios_tdevice)
		kfree(b->tty);

	kfree(b);
	return 0;
}

static long ARGS_ON_STACK 
bios_getname(root, dir, pathname, size)
	fcookie *root, *dir; char *pathname;
	int size;
{
	char *foo;

	if (size == 0)
	  return ERANGE;
	if (root->index == dir->index) {
	    *pathname = 0;
	    return 0;
	}
	/* DIR must point to the fd directory */
	if (!IS_FD_DIR (dir))
		return EINTRN;
	*pathname++ = '\\';
	size--;
	foo = ((struct bios_file *)dir->index)->name;
	if (strlen(foo) < size)
		strcpy(pathname, foo);
	else
		return ERANGE;
	return 0;
}

static long ARGS_ON_STACK 
bios_rename(olddir, oldname, newdir, newname)
	fcookie *olddir;
	char *oldname;
	fcookie *newdir;
	const char *newname;
{
	struct bios_file *b, *be = 0;

	UNUSED(olddir); UNUSED(newdir);

	if (curproc->euid)
		return EACCDN;

	for (b = broot; b; b = b->next) {
		if (!stricmp(b->name, newname)) {
			return EACCDN;
		}
		if (!stricmp(b->name, oldname)) {
			be = b;
		}
	}
	if (be) {
		strncpy(be->name, newname, BNAME_MAX);
		return 0;
	}
	return EFILNF;
}

static long ARGS_ON_STACK 
bios_opendir(dirh, flags)
	DIR *dirh;
	int flags;
{
	UNUSED(flags);

	if (dirh->fc.index != 0 && !IS_FD_DIR(&dirh->fc)) {
		DEBUG(("bios_opendir: bad directory"));
		return EPTHNF;
	}
	return 0;
}

static long ARGS_ON_STACK 
bios_readdir(dirh, name, namelen, fc)
	DIR *dirh;
	char *name;
	int namelen;
	fcookie *fc;
{
	struct bios_file *b;
	int giveindex = dirh->flags == 0;
	int i;
	char buf[5];

	if (IS_FD_DIR(&dirh->fc)) {
		i = dirh->index++;
		if (i+MIN_HANDLE >= MAX_OPEN)
			return ENMFIL;
		fc->fs = &bios_filesys;
		fc->index = i+1;
		fc->aux = i+MIN_HANDLE;
		fc->dev = dirh->fc.dev;
		if (giveindex) {
			namelen -= (int)sizeof(long);
			if (namelen <= 0)
				return ERANGE;
			*(long *)name = (long)i + 1;
			name += sizeof(long);
		}
		ksprintf(buf, "%d", i+MIN_HANDLE);
		strncpy(name, buf, namelen - 1);
		if (strlen(buf) >= namelen)
			return ENAMETOOLONG;
		return 0;
	}

	b = broot;
	i = dirh->index++;
	while(i-- > 0) {
		if (!b) break;
		b = b->next;
	}
	if (!b) {
		return ENMFIL;
	}
	fc->fs = &bios_filesys;
	fc->index = (long)b;
	fc->aux = b->private;
	fc->dev = dirh->fc.dev;
	if (giveindex) {
		namelen -= (int)sizeof(long);
		if (namelen <= 0)
			return ERANGE;
		*((long *)name) = (long) b;
		name += sizeof(long);
	}
	strncpy(name, b->name, namelen-1);
	if (strlen(b->name) >= namelen)
		return ENAMETOOLONG;
	return 0;
}

static long ARGS_ON_STACK 
bios_rewinddir(dirh)
	DIR *dirh;
{
	dirh->index = 0;
	return 0;
}

static long ARGS_ON_STACK 
bios_closedir(dirh)
	DIR *dirh;
{
	UNUSED(dirh);
	return 0;
}

static long ARGS_ON_STACK 
bios_pathconf(dir, which)
	fcookie *dir;
	int which;
{
	UNUSED(dir);

	switch(which) {
	case -1:
		return DP_MAXREQ;
	case DP_IOPEN:
		return UNLIMITED;	/* no limit on BIOS file descriptors */
	case DP_MAXLINKS:
		return 1;		/* no hard links available */
	case DP_PATHMAX:
		return PATH_MAX;
	case DP_NAMEMAX:
		return BNAME_MAX;
	case DP_ATOMIC:
		return 1;		/* no atomic writes */
	case DP_TRUNC:
		return DP_AUTOTRUNC;	/* names are truncated */
	case DP_CASE:
		return DP_CASEINSENS;	/* not case sensitive */
	case DP_MODEATTR:
		return (0777L << 8)|
				DP_FT_DIR|DP_FT_CHR;
	case DP_XATTRFIELDS:
		return DP_INDEX|DP_DEV|DP_RDEV|DP_NLINK|DP_UID|DP_GID;
	default:
		return EINVFN;
	}
}

static long ARGS_ON_STACK 
bios_dfree(dir, buf)
	fcookie *dir;
	long *buf;
{
	UNUSED(dir);

	buf[0] = 0;	/* number of free clusters */
	buf[1] = 0;	/* total number of clusters */
	buf[2] = 1;	/* sector size (bytes) */
	buf[3] = 1;	/* cluster size (sectors) */
	return 0;
}

/*
 * BIOS Dcntl() calls:
 * Dcntl(0xde02, "U:\DEV\FOO", &foo_descr): install a new device called
 *     "FOO", which is described by the dev_descr structure "foo_descr".
 *     this structure has the following fields:
 *         DEVDRV *driver		the device driver itself
 *	   short  dinfo			info for the device driver
 *	   short  flags			flags for the file (e.g. O_TTY)
 *	   struct tty *tty		tty structure, if appropriate
 *	   long   drvsize;		size of driver struct
 *	   long   fmode			XATTR mode the device should have
 *
 * Dcntl(0xde00, "U:\DEV\BAR", n): install a new BIOS terminal device, with
 *     BIOS device number "n".
 * Dcntl(0xde01, "U:\DEV\BAR", n): install a new non-tty BIOS device, with
 *     BIOS device number "n".
 */

static long ARGS_ON_STACK
bios_fscntl(dir, name, cmd, arg)
	fcookie *dir;
	const char *name;
	int cmd;
	long arg;
{
	struct bios_file *b;
	static int devindex = 0;

	if (curproc->euid) {
		DEBUG(("biosfs: Dcntl() by non-privileged process"));
		return ((unsigned)cmd == DEV_INSTALL) ? 0 : EACCDN;
	}

	if (IS_FD_DIR(dir))
		return EACCDN;

	/* ts: let's see if such an entry already exists */
	for (b=broot; b; b=b->next)
		if (!stricmp(b->name, name))
			break;

	switch((unsigned)cmd) {
	case DEV_INSTALL:
	    {
		struct dev_descr *d = (struct dev_descr *)arg;

		if (!b) {
			b = kmalloc(SIZEOF(struct bios_file));
			if (!b) return 0;
			b->next = broot;
			broot = b;
			strncpy(b->name, name, BNAME_MAX);
			b->name[BNAME_MAX] = 0;
		}
		b->device = d->driver;
		b->private = d->dinfo;
		b->flags = d->flags;
		b->tty = d->tty;
		b->drvsize = d->drvsize;
		set_xattr(&(b->xattr), S_IFCHR|DEFAULT_MODE, UNK_RDEV|devindex);
		if (d->fmode)
			b->xattr.mode = (short)d->fmode & 0177777;
		devindex = (devindex+1) & 0x00ff;
		return (long)&kernelinfo;
	    }
	case DEV_NEWTTY:
		if (!b) {
			b = kmalloc(SIZEOF(struct bios_file));
			if (!b)
				return ENSMEM;
			strncpy(b->name, name, BNAME_MAX);
			b->name[BNAME_MAX] = 0;
			b->tty = kmalloc(SIZEOF(struct tty));
			if (!b->tty) {
				kfree(b);
				return ENSMEM;
			}
			b->next = broot;
			broot = b;
		} else {
			/*  ts: it's probably better to use a new tty
			 * structure here, but don't touch the old
			 * pointers until we know we've got enough
			 * memory to do it!
			 */
			struct tty *ttyptr;

			if ((ttyptr = kmalloc(SIZEOF(struct tty))) == 0)
				return ENSMEM;
			b->tty = ttyptr;
		}
		b->drvsize = 0;
		b->device = &bios_tdevice;
		b->private = arg;
		b->flags = O_TTY;
		*b->tty = default_tty;
		set_xattr(&(b->xattr), S_IFCHR|DEFAULT_MODE, BIOS_RDEV|(b->private&0x00ff));
		return 0;

	case DEV_NEWBIOS:
		if (!b) {
			b = kmalloc(SIZEOF(struct bios_file));
			if (!b) return ENSMEM;
			b->next = broot;
			broot = b;
			strncpy(b->name, name, BNAME_MAX);
			b->name[BNAME_MAX] = 0;
		}
		b->drvsize = 0;
		/*  ts: it's probably better not to free an old tty
		 * structure here, cause we don't know if any process
		 * who didn't recognize this change is still using it.
		 */
		b->tty = 0;
		b->device = &bios_ndevice;
		b->private = arg;
		b->flags = 0;
		set_xattr(&(b->xattr), S_IFCHR|DEFAULT_MODE, BIOS_RDEV|(b->private&0x00ff));
		return 0;
	default:
		return EINVFN;
	}
}

static long ARGS_ON_STACK 
bios_symlink(dir, name, to)
	fcookie *dir;
	const char *name, *to;
{
	struct bios_file *b;
	long r;
	fcookie fc;

	if (curproc->euid)
		return EACCDN;

	if (IS_FD_DIR(dir))
		return EACCDN;

	r = bios_lookup(dir, name, &fc);
	if (r == 0) return EACCDN;	/* file already exists */
	if (r != EFILNF) return r;	/* some other error */

	b = kmalloc(SIZEOF(struct bios_file));
	if (!b) return EACCDN;

	strncpy(b->name, name, BNAME_MAX);
	b->name[BNAME_MAX] = 0;
	b->device = 0;
	b->private = EINVFN;
	b->flags = 0;
	b->tty = kmalloc((long)strlen(to)+1);
	if (!b->tty) {
		kfree(b);
		return EACCDN;
	}
	strcpy((char *)b->tty, to);

	set_xattr(&b->xattr, S_IFLNK|DEFAULT_DIRMODE, BIOSDRV);
	b->xattr.size = strlen(to)+1;

	b->next = broot;
	broot = b;
	return 0;
}

static long ARGS_ON_STACK 
bios_readlink(fc, buf, buflen)
	fcookie *fc;
	char *buf;
	int buflen;
{
	struct bios_file *b = (struct bios_file *)fc->index;

	if (IS_FD_DIR(fc) || IS_FD_ENTRY(fc))
		return EINVFN;
	if (!b) return EINVFN;
	if (b->device) return EINVFN;

	strncpy(buf, (char *)b->tty, buflen);
	if (strlen((char *)b->tty) >= buflen)
		return ENAMETOOLONG;
	return 0;
}


/*
 * routines for file systems that don't support volume labels
 */

long ARGS_ON_STACK 
nowritelabel(dir, name)
	fcookie *dir;
	const char *name;
{
	UNUSED(dir);
	UNUSED(name);
	return EACCDN;
}

long ARGS_ON_STACK 
noreadlabel(dir, name, namelen)
	fcookie *dir;
	char *name;
	int namelen;
{
	UNUSED(dir);
	UNUSED(name);
	UNUSED(namelen);
	return EFILNF;
}

/*
 * routines for file systems that don't support links
 */

long ARGS_ON_STACK 
nosymlink(dir, name, to)
	fcookie *dir;
	const char *name, *to;
{
	UNUSED(dir); UNUSED(name);
	UNUSED(to);
	return EINVFN;
}

long ARGS_ON_STACK 
noreadlink(dir, buf, buflen)
	fcookie *dir;
	char *buf;
	int buflen;
{
	UNUSED(dir); UNUSED(buf);
	UNUSED(buflen);
	return EINVFN;
}

long ARGS_ON_STACK 
nohardlink(fromdir, fromname, todir, toname)
	fcookie *fromdir, *todir;
	const char *fromname, *toname;
{
	UNUSED(fromdir); UNUSED(todir);
	UNUSED(fromname); UNUSED(toname);
	return EINVFN;
}

/* dummy routine for file systems with no Fscntl commands */

long ARGS_ON_STACK 
nofscntl(dir, name, cmd, arg)
	fcookie *dir;
	const char *name;
	int cmd;
	long arg;
{
	UNUSED(dir); UNUSED(name);
	UNUSED(cmd); UNUSED(arg);
	return EINVFN;
}

/*
 * Did the disk change? Not on this drive!
 * However, we have to do Getbpb anyways, because someone has decided
 * to force a media change on our (non-existent) drive.
 */
long ARGS_ON_STACK 
nodskchng(drv)
	int drv;
{
	(void)getbpb(drv);
	return 0;
}

long ARGS_ON_STACK 
nocreat(dir, name, mode, attrib, fc)
	fcookie *dir, *fc;
	const char *name;
	unsigned mode;
	int attrib;
{
	UNUSED(dir); UNUSED(fc);
	UNUSED(name); UNUSED(mode);
	UNUSED(attrib);
	return EACCDN;
}

static DEVDRV * ARGS_ON_STACK 
bios_getdev(fc, devsp)
	fcookie *fc;
	long *devsp;
{
	struct bios_file *b;

	/* Check for \dev\fd\... */
	if (IS_FD_ENTRY(fc)) {
	    *devsp = (int) fc->aux;
	    return &fakedev;
	}

	b = (struct bios_file *)fc->index;

	if (b->device && b->device != &fakedev)
		*devsp = (long)b->tty;
	else
		*devsp = b->private;

	return b->device;	/* return the device driver */
}

/*
 * NULL device driver
 */

long ARGS_ON_STACK 
null_open(f)
	FILEPTR *f;
{
	UNUSED(f);
	return 0;
}

long ARGS_ON_STACK 
null_write(f, buf, bytes)
	FILEPTR *f; const char *buf; long bytes;
{
	UNUSED(f); UNUSED(buf);
	return bytes;
}

long ARGS_ON_STACK 
null_read(f, buf, bytes)
	FILEPTR *f; char *buf; long bytes;
{
	UNUSED(f); UNUSED(buf);
	UNUSED(bytes);
	return 0;
}

long ARGS_ON_STACK 
null_lseek(f, where, whence)
	FILEPTR *f; long where; int whence;
{
	UNUSED(f); UNUSED(whence);
	return (where == 0) ? 0 : ERANGE;
}

long ARGS_ON_STACK 
null_ioctl(f, mode, buf)
	FILEPTR *f; int mode; void *buf;
{
	UNUSED(f);

	switch(mode) {
	case FIONREAD:
		*((long *)buf) = 0;
		break;
	case FIONWRITE:
		*((long *)buf) = 1;
		break;
	case FIOEXCEPT:
		*((long *)buf) = 0;
	default:
		return EINVFN;
	}
	return 0;
}

long ARGS_ON_STACK 
null_datime(f, timeptr, rwflag)
	FILEPTR *f;
	short *timeptr;
	int rwflag;
{
	UNUSED(f);
	if (rwflag)
		return EACCDN;
	*timeptr++ = timestamp;
	*timeptr = datestamp;
	return 0;
}

long ARGS_ON_STACK 
null_close(f, pid)
	FILEPTR *f;
	int pid;
{
	UNUSED(f);
	UNUSED(pid);
	return 0;
}

long ARGS_ON_STACK 
null_select(f, p, mode)
	FILEPTR *f; long p;
	int mode;
{
	UNUSED(f); UNUSED(p);
	if ((mode == O_RDONLY) || (mode == O_WRONLY))
		return 1;	/* we're always ready to read/write */

	return 0;	/* other things we don't care about */
}

void ARGS_ON_STACK 
null_unselect(f, p, mode)
	FILEPTR *f;
	long p;
	int mode;
{
	UNUSED(f); UNUSED(p);
	UNUSED(mode);
	/* nothing to do */
}

/*
 * BIOS terminal device driver
 */

static long ARGS_ON_STACK 
bios_topen(f)
	FILEPTR *f;
{
	struct tty *tty = (struct tty *)f->devinfo;
	int bdev = f->fc.aux;
	struct bios_tty *b;

	f->flags |= O_TTY;
	if (!tty->use_cnt && NULL != (b = BTTY(f)) && b->tosfd == EUNDEV)
		b->tosfd = rsvf_open (bdev);
	return 0;
}

/*
 * Note: when a BIOS device is a terminal (i.e. has the O_TTY flag
 * set), bios_read and bios_write will only ever be called indirectly, via
 * tty_read and tty_write. That's why we can afford to play a bit fast and
 * loose with the pointers ("buf" is really going to point to a long) and
 * why we know that "bytes" is divisible by 4.
 */

static long ARGS_ON_STACK 
bios_twrite(f, buf, bytes)
	FILEPTR *f; const char *buf; long bytes;
{
	long *r;
	long ret = 0;
	int bdev = f->fc.aux;
	struct bios_file *b = (struct bios_file *)f->fc.index;

	r = (long *)buf;

/* Check for control characters on any newline output.
 * Note that newlines are always output through tty_putchar,
 * so they'll always be the first thing in the buffer (at least,
 * for cooked TTY output they will, which is the only sort that
 * control characters affect anyways).
 */
	if (bdev == 2 && bytes > 0 && (*r & 0x000000ffL) == '\n')
 		(void) checkkeys();

	if (f->flags & O_NDELAY) {
		while (bytes > 0) {
		    if (!bcostat(bdev)) break;
		    if (bconout(bdev, (int)*r) == 0)
			break;
		    r++; bytes -= 4; ret+= 4;
		}
	} else {
		while (bytes > 0) {
		    if (bconout(bdev, (int)*r) == 0)
			break;
		    r++; bytes -= 4; ret+= 4;
		}
	}

	if (ret > 0) {
		b->xattr.mtime = b->xattr.atime = timestamp;
		b->xattr.mdate = b->xattr.adate = datestamp;
	}
	return ret;
}

static long ARGS_ON_STACK 
bios_tread(f, buf, bytes)
	FILEPTR *f; char *buf; long bytes;
{
	long *r, ret = 0;
	int bdev = f->fc.aux;
	struct  bios_file *b = (struct bios_file *)f->fc.index;

	r = (long *)buf;

	if ((f->flags & O_NDELAY)) {
		while (bytes > 0) {
		    if ( !bconstat(bdev) )
			break;
		    *r++ = bconin(bdev) & 0x7fffffffL;
		    bytes -= 4; ret += 4;
		}
	} else {
		while (bytes > 0) {
		    *r++ = bconin(bdev) & 0x7fffffffL;
		    bytes -= 4; ret += 4;
		}
	}
	if (ret > 0) {
		b->xattr.atime = timestamp;
		b->xattr.adate = datestamp;
	}
	return ret;
}

/*
 * wakewrite(p): wake process p sleeping in write (timeout)
 */

static void
wakewrite (PROC *p)
{
	short s;

	s = spl7();	/* block interrupts */
	p->wait_cond = 0;
	if (p->wait_q == IO_Q) {
		rm_q(IO_Q, p);
		add_q(READY_Q, p);
	}
	spl(s);
}

/*
 * fast RAW byte IO for BIOS ttys
 * without this a RAW tty read goes thru bios_tread for every single
 * char, calling BIOS 3 times per byte at least...  a poor 8 MHz ST
 * just can't move real 19200 bits per second that way, before a
 * byte crawled thru all this the next has already arrived.
 * if the device has xcon* calls and a `normal' iorec these functions
 * access the buffers directly using as little CPU time as possible,
 * for other devices they return EUNDEV (== do the slow thing then).
 * yes it is a hack but better one hack here than hacks in every
 * user process that wants good RAW IO performance...
 */

static long ARGS_ON_STACK 
bios_writeb(f, buf, bytes)
	FILEPTR *f; const char *buf; long bytes;
{
	int bdev = f->fc.aux;
	struct bios_file *b = (struct bios_file *)f->fc.index;

/* do the slow thing if tty is not in RAW mode until serial lines
 * handle control chars properly
 */
	if (!(((struct tty *)f->devinfo)->sg.sg_flags & T_RAW))
		return EUNDEV;
	return iwrite (bdev, buf, bytes, (f->flags & O_NDELAY), b);
}

/* FILEPTR-less entrypoint for bflush etc. */

long
iwrite(bdev, buf, bytes, ndelay, b)
	int bdev; const char *buf; long bytes; int ndelay; struct bios_file *b;
{
	IOREC_T *ior = 0;
	long *cout = 0;	/* keep compiler happy */
	long *ospeed;
	struct tty *tty = 0; /* still not happy yet? */
	const char *p = buf;
	int slept = 0;

#if 1
#define _hz_200 (*((long *)0x4baL))

	if (bdev == 3 && tosvers >= 0x0102) {
		/* midi */
		long ret = 0, tick = _hz_200 + 1;

		cout = &xconout[3];
		while (bytes > 0) {
			while (!(int)callout1(xcostat[4], 4)) {
				if (ndelay)
					break;
				if (_hz_200 - tick > 0) {
					yield();
					tick = _hz_200 + 1;
				}
			}
			(void) callout2(*cout, bdev, (unsigned char) *p++);
			bytes--;
		}
		if (ret > 0) {
			b->xattr.mtime = b->xattr.atime = timestamp;
			b->xattr.mdate = b->xattr.adate = datestamp;
		}
		return p - buf;
	}
#endif

	if (has_bconmap) {
		if ((unsigned)bdev-6 < btty_max) {
			ior = MAPTAB[bdev-6].iorec + 1;
			cout = &MAPTAB[bdev-6].bconout;
			ospeed = &bttys[bdev-6].ospeed;
			tty = bttys[bdev-6].tty;
		}
	} else if (bdev == 1 && tosvers >= 0x0102) {
		ior = bttys[0].orec;
		cout = &xconout[1];
		ospeed = &bttys[0].ospeed;
		tty = bttys[0].tty;
	}

/* no iorec, fall back to the slow way... */
	if (!ior)
		return EUNDEV;

	if (!buf) {
		/* flush send buffer...  should be safe to just set the
		   tail pointer. */
		ior->tail = ior->head;
		return 0;
	}

	if (!bytes)
		/* nothing to do... */
		return 0;

	do {
		char ch;
		unsigned short tail, bsize, wrap, newtail;
		long free;

		if (tty->state & TS_BLIND)
			/* line disconnected... */
			return p - buf;

		tail = ior->tail;
		bsize = ior->buflen;
		if ((free = (long)ior->head - tail - 1) < 0)
			free += bsize;

		/* if buffer is full or we're blocking and can't write enuf */
		if ((unsigned long)free < 2 ||
		    (!ndelay && free < bytes && free < bsize/2)) {
			long sleepchars;
			unsigned long isleep = 0;

			/* if the write should not block thats it. */
			if (ndelay)
				return p - buf;

			/* else sleep the (minimum) time it takes until
			   the buffer is either half-empty or has space
			   enough for the write, wichever is smaller. */
			if ((sleepchars = bsize/2) > bytes)
				sleepchars = bytes;
			sleepchars -= free;

			if (*ospeed > 0)
				isleep = (unsigned long)
					((sleepchars * 10000L) / *ospeed);

			/* except that if we already slept and the buffer
			   still was full we sleep for at least 60
			   milliseconds. (driver must be waiting for
			   some handshake signal and we don't want to
			   hog the processor.) */
			if (slept && isleep < 60)
				isleep = 60;

			if (isleep < 5)
				/* if it still would be less than 5
				   milliseconds then just give up this
				   timeslice */
				yield();
			else if (isleep < 20)
				nap ((unsigned) isleep);
			else {
				TIMEOUT *t;

				if (isleep > 200)
					isleep = 200;
				curproc->wait_cond = (long)&tty->state;
				t = addtimeout((long)isleep, wakewrite);
				if (t) {
					TRACE(("sleeping in iwrite"));
					sleep(IO_Q|0x100, (long)&tty->state);
					canceltimeout(t);
				}
			}

			/* loop and try again. */
			slept = (unsigned long)free < 2;
			continue;
		}
		slept = 0;

		/* save the 1st char, we could need it later. */
		ch = *p;
		wrap = bsize - tail;
		if (--free > bytes)
			free = bytes;
		bytes -= free;

		/* now copy to buffer.  if its just a few then do it here... */
		if (free < 5) {
			char *q = ior->bufaddr + tail;

			while (free--) {
				if (!--wrap)
					q -= bsize;
				*++q = *p++;
			}
			newtail = q - ior->bufaddr;

		/* else use memcpy.  */
		} else {
			/* --wrap and tail+1 because tail is `inc before access' */
			if (--wrap < free) {
				memcpy (ior->bufaddr + tail + 1, p, wrap);
				memcpy (ior->bufaddr, p + wrap, free - wrap);
				newtail = free - wrap - 1;
			} else {
				memcpy (ior->bufaddr + tail + 1, p, free);
				newtail = tail + free;
			}
			p += free;
		}

		/* the following has to be done with interrupts off to avoid
		   race conditions. */
		{
			short sr = spl7();

			/* if the buffer is empty there might be no
			   interrupt that sends the next char, so we
			   send it thru the xcon* vector. */
			if (ior->head == ior->tail) {
				(void) callout2(*cout, bdev, (unsigned char) ch);

				/* if the buffer now is still empty set
				   the head pointer to skip the 1st char
				   (that we just sent). */
				if (ior->head == ior->tail) {
					if (++tail >= bsize)
						tail = 0;
					ior->head = tail;
				}
			}
			ior->tail = newtail;

			spl(sr);
			if (b) {
				b->xattr.mtime = b->xattr.atime = timestamp;
				b->xattr.mdate = b->xattr.adate = datestamp;
			}
		}
	/* if we're blocking loop until everything is written */
	} while (bytes && !ndelay);

	return p - buf;
}

/*
 * fast RAW BIOS tty read
 * this really works like a RAW tty read i.e. only blocks until _some_
 * data is ready.
 */

static long ARGS_ON_STACK 
bios_readb(f, buf, bytes)
	FILEPTR *f; char *buf; long bytes;
{
	int bdev = f->fc.aux;
	struct bios_file *b = (struct bios_file *)f->fc.index;
	struct tty *tty = (struct tty *)f->devinfo;

	if (!(tty->sg.sg_flags & T_RAW) || (tty->sg.sg_flags & T_ECHO))
		return EUNDEV;
	/* if VTIME is set tty_read already select()ed the tty
	 * so from here on the read should not block anymore */
	return iread (bdev, buf, bytes, (f->flags & O_NDELAY) || tty->vtime, b);
}

long
iread(bdev, buf, bytes, ndelay, b)
	int bdev; char *buf; long bytes; int ndelay; struct bios_file *b;
{
	IOREC_T *ior = 0;
	long *cin = 0;	/* keep compiler happy */
	long *cinstat;
	struct bios_tty *t = 0;
	char *p;
	unsigned short head, bsize, wrap;
	long left;

	if (bdev == 3 && tosvers >= 0x0102) {
		/* midi */
		t = &midi_btty;
		ior = t->irec;
		cin = &xconin[3];
		cinstat = &xconstat[3];
	} else if (has_bconmap) {
		if ((unsigned)bdev-6 < btty_max) {
			t = bttys+bdev-6;
			ior = MAPTAB[bdev-6].iorec;
			cin = &MAPTAB[bdev-6].bconin;
			cinstat = &MAPTAB[bdev-6].bconstat;
		}
	} else if (bdev == 1 && tosvers >= 0x0102) {
		t = bttys;
		ior = t->irec;
		cin = &xconin[1];
		cinstat = &xconstat[1];
	}

/* no iorec, fall back to the slow way... */
	if (!ior)
		return EUNDEV;

	if (buf && !bytes)
		/* nothing to do... */
		return 0;

	/* if the read should block sleep until VMIN chars ready (or disconnect)
	 */
	if (buf && !ndelay) {
		while (t ? (!(t->tty->state & TS_BLIND) &&
			    (bdev == 3 ? ionread(ior) :
					btty_ionread(t)) < (long)t->tty->vmin)
			 : !(int)callout1(*cinstat, bdev)) {
			if (t)
				sleep(IO_Q, (long)t);
			else
				nap(60);
		}
	}

	if (t && (t->tty->state & TS_BLIND))
		/* line disconnected... */
		return 0;

	head = ior->head;
	if (0 == (left = ((unsigned long) ior->tail) - head)) {
		/* if the buffer is still empty we're finished... */
		return 0;
	}

	/* now copy the data out of the buffer */
	bsize = ior->buflen;
	if (left < 0)
		left += bsize;
	wrap = bsize - head;

	/* if we should flush input pretend we want to read it all */
	if (!buf && !bytes)
		bytes = left;

	if (left > bytes)
		left = bytes;

	/* if its just a few then do it here... */
	if (buf && left <= 5) {
		char *q = ior->bufaddr + head;

		/* the --left in the while() makes us get one char less
		   because we want to get the last one thru the driver
		   so that it gets a chance to raise RTS or send XON... */
		p = buf;
		while (--left) {
			if (!--wrap)
				q -= bsize;
			*p++ = *++q;
		}
		ior->head = q - ior->bufaddr;

	/* else memcpy is faster. */
	} else {
		/* --wrap and head+1 because head is `inc before access' */
		if (--wrap < --left) {
			if (buf) {
				memcpy (buf, ior->bufaddr + head + 1, wrap);
				memcpy (buf + wrap, ior->bufaddr, left - wrap);
			}
			ior->head = left - wrap - 1;
		} else {
			if (buf)
				memcpy (buf, ior->bufaddr + head + 1, left);
			ior->head = head + left;
		}
		/* p points to last char */
		p = buf + left;
	}

	{
		short sr = spl7();

		/* xconin[] are always blocking, and we don't want to
		   hang at ipl7 even if something impossible like a
		   `magically' empty buffer happens.  so check again. */
		if (ior->tail != ior->head)
			if (buf)
				*p++ = callout1(*cin, bdev);
			else
				(void) callout1(*cin, bdev);
		spl(sr);
		if (b) {
			b->xattr.atime = timestamp;
			b->xattr.adate = datestamp;
		}
	}
	if (!buf)
		return 0;
	return p - buf;
}

/*
 * read/write routines for BIOS devices that aren't terminals (like the
 * printer & IKBD devices)
 */

static long ARGS_ON_STACK 
bios_nwrite(f, buf, bytes)
	FILEPTR *f; const char *buf; long bytes;
{
	long ret = 0;
	int bdev = f->fc.aux;
	int c;
	struct bios_file *b = (struct bios_file *)f->fc.index;

	while (bytes > 0) {
		if ( (f->flags & O_NDELAY) && !bcostat(bdev) )
			break;

		c = *buf++ & 0x00ff;

		if (bconout(bdev, c) == 0)
			break;

		bytes--; ret++;
	}
	if (ret > 0) {
		b->xattr.mtime = b->xattr.atime = timestamp;
		b->xattr.mdate = b->xattr.adate = datestamp;
	}
	return ret;
}

static long ARGS_ON_STACK 
bios_nread(f, buf, bytes)
	FILEPTR *f; char *buf; long bytes;
{
	long ret = 0;
	int bdev = f->fc.aux;
	struct bios_file *b = (struct bios_file *)f->fc.index;

	while (bytes > 0) {
		if ( (f->flags & O_NDELAY) && !bconstat(bdev) )
			break;
		*buf++ = bconin(bdev) & 0xff;
		bytes--; ret++;
	}
	if (ret > 0) {
		b->xattr.atime = timestamp;
		b->xattr.adate = datestamp;
	}
	return ret;
}

/*
 * BIOS terminal seek code -- this has to match the documented
 * way to do isatty()
 */

static long ARGS_ON_STACK 
bios_tseek(f, where, whence)
	FILEPTR *f;
	long where;
	int whence;
{
	UNUSED(f); UNUSED(where);
	UNUSED(whence);
/* terminals always are at position 0 */
	return 0;
}

/*
 * ioctl TIOC[CS]BRK, checkbtty calls it without a FILEPTR
 */

long
iocsbrk (int bdev, int mode, struct bios_tty *t)
{
	unsigned long bits;
	int oldmap = curproc->bconmap;

	if (has_bconmap) {
/* YABB (Yet Another BIOS Bug):
 * SCC Rsconf looks for the break bit in the wrong place...  if this
 * dev's Rsconf is in ROM do it ourselves, otherwise assume the user
 * has installed a fix.
 */
		if (t && (bdev == 7 || t->tty == &scca_tty) &&
		    MAPTAB[bdev-6].rsconf > 0xe00000L &&
		    MAPTAB[bdev-6].rsconf < 0xefffffL) {
			scc_set5 ((volatile char *) (bdev == 7 ?
				    0xffff8c85L : 0xffff8c81L),
				  (mode == TIOCSBRK), (1 << 4), t->irec);
			return 0;
		}
		if (bdev >= 6)
			curproc->bconmap = bdev;
	}
	bits = rsconf(-1, -1, -1, -1, -1, -1);	/* get settings */
	bits = (bits >> 8) & 0x0ff;		/* isolate TSR byte */
	if (mode == TIOCCBRK)
		bits &= ~8;
	else
		bits |= 8;
	(void)rsconf(-1, -1, -1, -1, (int)bits, -1);
	curproc->bconmap = oldmap;
	return 0;
}

/*
 * ioctl TIOCSFLAGSB, combined TIOC[GS]FLAGS with bitmask to leave
 * parts of flags alone...
 */

INLINE static long
iocsflagsb (int bdev, unsigned long flags, unsigned long mask,
	struct tty *tty, struct bios_tty *t)
/*	unsigned long flags;	new flags, hi bit set == read only */
/*	unsigned long mask;		what flags to change/read */
{
	unsigned long oflags;
	unsigned short *sgflags;
	unsigned long bits;
	unsigned char ucr, oucr;
	short flow;

	sgflags = &tty->sg.sg_flags;
	if (t)
		oflags = (((char *) t->irec)[0x20] << 12) & (T_TANDEM|T_RTSCTS);
	else
		oflags = *sgflags & (T_TANDEM|T_RTSCTS);
	if ((long)flags >= 0)
		/* clear unused bits */
		flags &= (TF_STOPBITS|TF_CHARBITS|TF_BRKINT|TF_CAR|
			T_RTSCTS|T_TANDEM|T_EVENP|T_ODDP);
	if (t && (mask & (TF_BRKINT|TF_CAR))) {
		if (t->brkint)
			oflags |= TF_BRKINT;
		if (!t->clocal)
			oflags |= TF_CAR;
		if ((long)flags >= 0) {
			if (mask & TF_CAR) {
				t->clocal = !(flags & TF_CAR);
/* update TS_BLIND without signalling */
				checkbtty_nsig (t);
				if ((bdev == 7 || tty == &scca_tty) &&
				    !(mask & (T_RTSCTS|T_TANDEM))) {
/* force rsconf be called so it can adjust w3 bit 5 (see xbios.c) */
					mask |= (T_RTSCTS|T_TANDEM);
					flags = (flags & ~(T_RTSCTS|T_TANDEM)) |
						(oflags & (T_RTSCTS|T_TANDEM));
				}
			} else {
				flags = (flags & ~TF_CAR) |
					(oflags & TF_CAR);
			}
			if (mask & TF_BRKINT) {
				t->brkint = flags & TF_BRKINT;
			} else {
				flags = (flags & ~TF_BRKINT) |
					(oflags & TF_BRKINT);
			}
		}
	} else {
		flags &= ~(TF_BRKINT|TF_CAR);
	}
	if (mask & (TF_FLAGS|TF_STOPBITS|TF_CHARBITS)) {
		int oldmap = curproc->bconmap;

		if (has_bconmap && bdev >= 6)
			curproc->bconmap = bdev;
		bits = rsconf(-1, -1, -1, -1, -1, -1);	/* get settings */
		oucr = ucr = (bits >> 24L) & 0x0ff;	/* isolate UCR byte */
		oflags |= (ucr >> 3) & (TF_STOPBITS|TF_CHARBITS);
		if (ucr & 0x4) {			/* parity on? */
			oflags |= (ucr & 0x2) ? T_EVENP : T_ODDP;
		}
		if ((long)flags >= 0) {
			if ((mask & (T_RTSCTS|T_TANDEM)) == (T_RTSCTS|T_TANDEM)) {
				flow = (flags & (T_RTSCTS|T_TANDEM)) >> 12L;
			} else {
				flow = -1;
				flags = (flags & ~(T_RTSCTS|T_TANDEM)) |
					(oflags & (T_RTSCTS|T_TANDEM));
			}
			if ((mask & (T_EVENP|T_ODDP)) == (T_EVENP|T_ODDP)) {
				if (flags & T_EVENP) {
					ucr |= 0x6;
					flags &= ~T_ODDP;
				} else if (flags & T_ODDP) {
					ucr &= ~2;
					ucr |= 0x4;
				} else {
					ucr &= ~6;
				}
			} else {
				flags = (flags & ~(T_EVENP|T_ODDP)) |
					(oflags & (T_EVENP|T_ODDP));
			}
			if ((mask & TF_STOPBITS) == TF_STOPBITS &&
			    (flags & TF_STOPBITS)) {
				ucr &= ~(0x18);
				ucr |= (flags & TF_STOPBITS) << 3;
			} else {
				flags = (flags & ~TF_STOPBITS) |
					(oflags & TF_STOPBITS);
			}
			if ((mask & TF_CHARBITS) == TF_CHARBITS) {
				ucr &= ~(0x60);
				ucr |= (flags & TF_CHARBITS) << 3;
			} else {
				flags = (flags & ~TF_CHARBITS) |
					(oflags & TF_CHARBITS);
			}
			if (ucr != oucr)
				rsconf(-1, flow, ucr, -1, -1, -1);
			else if (flow >= 0)
				rsconf(-1, flow, -1, -1, -1, -1);
			if (flow >= 0) {
				if (t)
					flags = (flags & ~(T_RTSCTS|T_TANDEM)) |
						((((char *) t->irec)[0x20] << 12) &
						 (T_RTSCTS|T_TANDEM));
				*sgflags &= ~(T_RTSCTS|T_TANDEM);
				*sgflags |= flags & (T_RTSCTS|T_TANDEM);
			}
		}
		curproc->bconmap = oldmap;
	}
	return (long)flags >= 0 ? flags : oflags;
}

static long ARGS_ON_STACK 
bios_ioctl(f, mode, buf)
	FILEPTR *f; int mode; void *buf;
{
	long *r = (long *)buf;
	struct winsize *ws;
	char *aline;
	short dev;
	int i;
	struct bios_file *b;
	struct bios_tty *t = BTTY(f);

	switch(mode) {
	case FIONREAD:
		if (t)
			*r = btty_ionread(t);
		else if (f->fc.aux == 3)
			*r = ionread(midi_btty.irec);
		else if (bconstat(f->fc.aux))
			*r = 1;
		else
			*r = 0;
		break;
	case FIONWRITE:
		if (t)
			*r = ionwrite (t->orec);
		else if (bcostat(f->fc.aux))
			*r = 1;
		else
			*r = 0;
		break;
	case FIOEXCEPT:
		*r = 0;
		break;
	case TIOCFLUSH:
	    {
		IOREC_T *ior;
		int flushtype;
		short sr;

		dev = f->fc.aux;

		if ((!r) || (!(*r & 3))) {
			flushtype = 3;
		} else {
			flushtype = (int) *r;
		}
		if (dev == 1 || dev >= 6) {
			b = (struct bios_file *)f->fc.index;
			if (t)
				ior = t->irec;
			else
				ior = (IOREC_T *) uiorec(0);
	/* just resetting iorec pointers here can hang a flow controlled port,
	 * iread can do better...
	 */
			if ((flushtype & 1) &&
			    iread (dev, (char *) NULL, 0, 1, b) == EUNDEV) {
				sr = spl7();
				ior->head = ior->tail = 0;
				spl(sr);
			}
	/* sender should be ok but iwrite also sets the dev's ctime */
			if ((flushtype & 2) &&
			    iwrite (dev, (char *) NULL, 0, 1, b) == EUNDEV) {
				ior++; /* output record */
				sr = spl7();
				ior->head = ior->tail = 0;
				spl(sr);
			}
		} else if (dev == 3 || dev == 2 || dev == 5) {
			if (dev == 3) {
				/* midi */
				ior = midi_btty.irec;
			} else {
				/* ikbd */
				ior = (IOREC_T *) uiorec(1);
			}
			if (flushtype & 1) {
				sr = spl7();
				ior->head = ior->tail = 0;
				spl(sr);
			}
		}
		return 0;
	    }
#if 1
#ifndef TIONOTSEND
#define TIONOTSEND (('T'<<8) | 134)
#endif
	case TIONOTSEND:
#endif
	case TIOCOUTQ:
	    {
		IOREC_T *ior;

		dev = f->fc.aux;

		if (dev == 1 || dev >= 6) {
			long ret;
#ifdef TIONOTSEND
/* try trap #1 first, to know about the last char too :) */
			if (t &&
			    (ret = rsvf_ioctl (t->tosfd, r, TIONOTSEND)) != EINVFN)
				return ret;
#endif
			if (t)
				ior = t->orec;
			else
				ior = (IOREC_T *) uiorec(0) + 1;
			*r = ior->tail - ior->head;
			if (*r < 0)
				*r += ior->buflen;
		}
 		else
			*r = 0;
		break;
	    }
	case TIOCGWINSZ:
		if (f->fc.aux == 2) {
			aline = lineA0();
			ws = (struct winsize *)buf;
			ws->ws_row = *((short *)(aline - 42)) + 1;
			ws->ws_col = *((short *)(aline - 44)) + 1;
		} else {
			return EINVFN;
		}
		break;
	case TIOCIBAUD:
	case TIOCOBAUD:
	    {
		long oldbaud, newbaud;
		int oldmap;

		dev = f->fc.aux;

		newbaud = *r;
		if (dev == 1 || dev >= 6) {
/* can we pass it to trap #1 and set speeds rsconf doesn't know about? */
			if (t &&
			    (oldbaud = rsvf_ioctl (t->tosfd, r, mode)) != EINVFN) {
/* looks good...  save result and return */
				if (newbaud < 0)
					newbaud = *r;
				if (newbaud && !oldbaud) {
					if (mode == TIOCIBAUD)
						t->ispeed = newbaud;
					else
						t->ospeed = newbaud;
				}
				t->vticks = 0;
				return oldbaud;
			}
/* trick rsconf into setting the correct port (it uses curproc->bconmap) */
			oldmap = curproc->bconmap;
			if (has_bconmap && dev >= 6)
				curproc->bconmap = dev;
			i = (int)rsconf(-2, -1, -1, -1, -1, -1);

			if (i < 0 || i >= MAXBAUD)
				oldbaud = -1L;
			else
				oldbaud = baudmap[i];
			*r = oldbaud;
			if (newbaud > 0) {
	/* assert DTR works only on modem1 and SCC lines */
				if (dev == 1 || dev == 6) {
					Offgibit(0xef);
				} else if (t && (dev == 7 ||
					   ((struct tty *)f->devinfo) == &scca_tty)) {
					scc_set5 ((volatile char *) (dev == 7 ?
						    0xffff8c85L : 0xffff8c81L),
						  1, (1 << 7), t->irec);
				}
				if (newbaud == oldbaud ||
				    ((struct tty *)f->devinfo)->hup_ospeed) {
					curproc->bconmap = oldmap;
					return 0;
				}
				for (i = 0; i < MAXBAUD; i++) {
					if (baudmap[i] == newbaud) {
						rsconf(i, -1, -1, -1, -1, -1);
						curproc->bconmap = oldmap;
						return 0;
					} else if (baudmap[i] < newbaud) {
						*r = baudmap[i];
						break;
					}
				}
				curproc->bconmap = oldmap;
				return ERANGE;
			} else if (newbaud == 0L) {
	/* drop DTR: works only on modem1 and SCC lines */
				if (dev == 1 || dev == 6) {
					Ongibit(0x10);
				} else if (t && (dev == 7 ||
					   ((struct tty *)f->devinfo) == &scca_tty)) {
					scc_set5 ((volatile char *) (dev == 7 ?
						    0xffff8c85L : 0xffff8c81L),
						  0, (1 << 7), t->irec);
				}
			}
			curproc->bconmap = oldmap;
			return 0;
		} else if (dev == 2 || dev == 5) {
			/* screen: assume 9600 baud */
			oldbaud = 9600L;
		} else if (dev == 3) {
			/* midi */
			oldbaud = 31250L;
		} else {
			oldbaud = -1L;	/* unknown speed */
		}
		*r = oldbaud;
		if (newbaud > 0 && newbaud != oldbaud)
			return ERANGE;
		break;
	    }
	case TIOCCBRK:
	case TIOCSBRK:
		dev = f->fc.aux;
		if (dev != 1 && dev < 6)
			return EINVFN;
		return iocsbrk (dev, mode, t);
	case TIOCSFLAGSB:
		dev = f->fc.aux;
		if (dev != 1 && dev < 6)
			return EINVFN;
		*((long *)buf) = iocsflagsb (dev, ((long *)buf)[0], ((long *)buf)[1],
					(struct tty *)f->devinfo, t);
		break;
	case TIOCGVMIN:
	case TIOCSVMIN:
	    {
		unsigned short *v = buf;
		struct tty *tty = (struct tty *)f->devinfo;

		if (f->fc.aux == 3)
			t = &midi_btty;
		if (!tty || !t)
			return EINVFN;
		if (mode == TIOCGVMIN) {
			v[0] = tty->vmin;
			v[1] = tty->vtime;
		} else {
			if (v[0] > t->irec->buflen/2)
				v[0] = t->irec->buflen/2;
			tty->vmin = v[0];
			tty->vtime = v[1];
			t->vticks = 0;
		}
		return 0;
	    }
	case TIOCWONLINE:
	    {
		struct tty *tty = (struct tty *)f->devinfo;

		if (!tty)
			return EINVFN;
		while (tty->state & TS_BLIND)
			sleep (IO_Q, (long)&tty->state);
		return 0;
	    }
	case TCURSOFF:
	case TCURSON:
	case TCURSBLINK:
	case TCURSSTEADY:
		if (f->fc.aux != 2)
			return EINVFN;
		return Cursconf(mode - TCURSOFF, 0);
	case TCURSSRATE:
	case TCURSGRATE:
	    {
		long r;

		if (f->fc.aux != 2)
			return EINVFN;
		r = Cursconf(mode - TCURSOFF, *((short *)buf));
		if (r >= 0 && mode == TCURSGRATE) {
			*(short *)buf = r;
			r = 0;
		}
		return r;
	    }
	case F_SETLK:
	case F_SETLKW:
	    {
		struct flock *lck = (struct flock *)buf;

		b = (struct bios_file *)f->fc.index;
		while (b->lockpid && b->lockpid != curproc->pid) {
			if (mode == F_SETLKW && lck->l_type != F_UNLCK)
				sleep(IO_Q, (long)b);
			else
				return ELOCKED;
		}
		if (lck->l_type == F_UNLCK) {
			if (!(f->flags & O_LOCK)) {
				DEBUG(("bios_ioctl: wrong file descriptor for UNLCK"));
				return ENSLOCK;
			}
			if (b->lockpid != curproc->pid)
				return ENSLOCK;
			b->lockpid = 0;
			f->flags &= ~O_LOCK;
			wake(IO_Q, (long)b);	/* wake anyone waiting for this lock */
		} else {
			b->lockpid = curproc->pid;
			f->flags |= O_LOCK;
		}
		break;
	    }
	case F_GETLK:
	    {
		struct flock *lck = (struct flock *)buf;

		b = (struct bios_file *)f->fc.index;
		if (b->lockpid) {
			lck->l_type = F_WRLCK;
			lck->l_start = lck->l_len = 0;
			lck->l_pid = b->lockpid;
		} else {
			lck->l_type = F_UNLCK;
		}
		break;
	    }
#ifndef TIOCCTLMAP
#define TIOCCTLMAP (('T'<<8) | 129)
#define TIOCCTLGET (('T'<<8) | 130)
#define TIOCCTLSET (('T'<<8) | 131)
#define TIONOTSEND (('T'<<8) | 134)
#define TIOCERROR (('T'<<8) | 135)
#endif
	case TIOCCTLMAP:
	case TIOCCTLGET:
	case TIOCCTLSET:
	case TIOCERROR:
	    {
		long ret;

		if (t &&
		    (ret = rsvf_ioctl (t->tosfd, r, mode)) != EINVFN) {
			if (mode == TIOCCTLMAP)
/* user processes can get signals but not callbacks from real interrupts... */
				r[1] = r[2] = 0;
			return ret;
		}
		/*FALLTHRU*/
	    }
	default:
	/* Fcntl will automatically call tty_ioctl to handle
	 * terminal calls that we didn't deal with
	 */
		return EINVFN;
	}
	return 0;
}

static long ARGS_ON_STACK
bios_select(f, p, mode)
	FILEPTR *f; long p; int mode;
{
	struct tty *tty = (struct tty *)f->devinfo;
	int dev = f->fc.aux;

	if (mode == O_RDONLY) {
		struct bios_tty *t = &midi_btty;
		if (tty && (dev == 3 || NULL != (t = BTTY(f)))) {
			if (!(tty->state & TS_BLIND) &&
			    (dev == 3 ? ionread(t->irec) :
					btty_ionread(t)) >= (long)tty->vmin) {
				return 1;
			}
		} else if (bconstat(dev)) {
			TRACE(("bios_select: data present for device %d", dev));
			return 1;
		}
		if (tty) {
		/* avoid collisions with other processes */
			if (tty->rsel)
				return 2;	/* collision */
			tty->rsel = p;
		}
		return 0;
	} else if (mode == O_WRONLY) {
		if ((!tty || !(tty->state & (TS_BLIND|TS_HOLD))) &&
		    (dev == 3 || bcostat(dev))) {
			TRACE(("bios_select: ready to output on %d", dev));
			return 1;
		}
		if (tty) {
			if (tty->wsel)
				return 2;	/* collision */
			tty->wsel = p;
		}
		return 0;
	}
	/* default -- we don't know this mode, return 0 */
	return 0;
}

static void ARGS_ON_STACK 
bios_unselect(f, p, mode)
	FILEPTR *f;
	long p;
	int mode;
{
	struct tty *tty = (struct tty *)f->devinfo;

	if (tty) {
		if (mode == O_RDONLY && tty->rsel == p)
			tty->rsel = 0;
		else if (mode == O_WRONLY && tty->wsel == p)
			tty->wsel = 0;
	}
}

static long ARGS_ON_STACK 
bios_close(f, pid)
	FILEPTR *f;
	int pid;
{
	struct tty *tty = (struct tty *)f->devinfo;
	struct bios_file *b;
	struct bios_tty *t;

	b = (struct bios_file *)f->fc.index;
	if ((f->flags & O_LOCK) && (b->lockpid == pid)) {
		b->lockpid = 0;
		f->flags &= ~O_LOCK;
		wake(IO_Q, (long)b);	/* wake anyone waiting for this lock */
	}
	if (tty && f->links <= 0 && f->pos)
/* f->pos used as flag that f came from Bconmap (/dev/aux) */
		tty->aux_cnt--;
	if (tty && !tty->use_cnt && (NULL != (t = BTTY(f)) && t->tosfd != EUNDEV)) {
		rsvf_close (t->tosfd);
		t->tosfd = EUNDEV;
	}
	return 0;
}

/*
 * mouse device driver
 */

#define MOUSESIZ 128*3
static unsigned char mousebuf[MOUSESIZ];
static int mousehead, mousetail;

long mousersel;	/* is someone calling select() on the mouse? */

char mshift;		/* shift key status; set by checkkeys() in bios.c */
short *gcurx = 0,
      *gcury = 0;	/* mouse pos. variables; used by big screen emulators */

void ARGS_ON_STACK 
mouse_handler(buf)
	const char *buf;	/* must be a *signed* character */
{
	unsigned char *mbuf, buttons;
	int newmtail;
	short dx, dy;

/* the Sun mouse driver has 0=down, 1=up, while the atari hardware gives
   us the reverse. also, we have the "middle" button and the "left"
   button reversed; so we use this table to convert (and also to add the
   0x80 to indicate a mouse packet)
 */
	static int _cnvrt[8] = {
		0x87, 0x86, 0x83, 0x82, 0x85, 0x84, 0x81, 0x80
	};

	mbuf = &mousebuf[mousetail];
	newmtail = mousetail + 3;
	if (newmtail >= MOUSESIZ)
		newmtail = 0;
	if (newmtail == mousehead)
		return;			/* buffer full */

	buttons = *buf++ & 0x7;		/* convert to SUN format */
	if (mshift & 0x3) {		/* a shift key held down? */
	/* if so, convert shift+button to a "middle" button */
		if (buttons == 0x1 || buttons == 0x2)
			buttons = 0x4;
		else if (buttons == 0x3)
			buttons = 0x7;
	}
	*mbuf++ = _cnvrt[buttons];	/* convert to Sun format */
	dx = *buf++;
	*mbuf++ = dx;			/* copy X delta */
	dy = *buf;
	*mbuf = -dy;			/* invert Y delta for Sun format */
	mousetail = newmtail;
	*gcurx += dx;			/* update line A variables */
	*gcury += dy;
/*
 * if someone has called select() waiting for mouse input, wake them
 * up
 */
	if (mousersel) {
		wakeselect(mousersel);
	}
}

extern void newmvec(), newjvec();	/* in intr.s */
static long oldvec = 0;
long oldjvec = 0;

static long ARGS_ON_STACK 
mouse_open(f)
	FILEPTR *f;
{
	char *aline;

	static char parameters[] = {
		0, 		/* Y=0 in lower corner */
		0,		/* normal button handling */
		1, 1		/* X, Y scaling factors */
	};

	UNUSED(f);

	if (oldvec)		/* mouse in use */
		return EACCDN;

/* initialize pointers to line A variables */
	if (!gcurx) {
		aline = lineA0();
		if (aline == 0)	{	/* should never happen */
			ALERT("unable to read line A variables");
			return -1;
		}
		gcurx = (short *)(aline - 0x25a);
		gcury = (short *)(aline - 0x258);
		*gcurx = *gcury = 32;	/* magic number -- what MGR uses */
	}

	oldvec = syskey->mousevec;
	oldjvec = syskey->joyvec;	/* jr: save old joystick vector */
	Initmous(1, parameters, newmvec);
	syskey->joyvec = (long)newjvec;	/* jr: set up new joystick handler */
	mousehead = mousetail = 0;
	return 0;
}

static long ARGS_ON_STACK 
mouse_close(f, pid)
	FILEPTR *f;
	int pid;
{
	static char parameters[] = {
		0, 		/* Y=0 in lower corner */
		0,		/* normal button handling */
		1, 1		/* X, Y scaling factors */
	};

	UNUSED(pid);
	if (!f) return EIHNDL;
	if (f->links <= 0) {
		if (!oldvec) {
			DEBUG(("Mouse not open!!"));
			return -1;
		}
		Initmous(1, parameters, (void *)oldvec);	/* gratuitous (void *) for Lattice */
		syskey->joyvec = oldjvec;	/* jr: restore old joystick handler */
		oldvec = 0;
	}
	return 0;
}

static long ARGS_ON_STACK 
mouse_read(f, buf, nbytes)
	FILEPTR *f;
	char *buf;
	long nbytes;
{
	long count = 0;
	int mhead;
	unsigned char *foo;
	struct bios_file *b = (struct bios_file *)f->fc.index;

	mhead = mousehead;
	foo = &mousebuf[mhead];

	if (mhead == mousetail) {
		if (f->flags & O_NDELAY)
			return 0;
		do {
			yield();
		} while (mhead == mousetail);
	}

	while ( (mhead != mousetail) && (nbytes > 0)) {
		*buf++ = *foo++;
		mhead++;
		if (mhead >= MOUSESIZ) {
			mhead = 0;
			foo = mousebuf;
		}
		count++;
		--nbytes;
	}
	mousehead = mhead;
	if (count > 0) {
		b->xattr.atime = timestamp;
		b->xattr.adate = datestamp;
	}
	return count;
}

static long ARGS_ON_STACK 
mouse_ioctl(f, mode, buf)
	FILEPTR *f;
	int mode;
	void *buf;
{
	long r;

	UNUSED(f);
	if (mode == FIONREAD) {
		r = mousetail - mousehead;
		if (r < 0) r += MOUSESIZ;
		*((long *)buf) = r;
	} else if (mode == FIONWRITE)
		*((long *)buf) = 0;
	else if (mode == FIOEXCEPT)
		*((long *)buf) = 0;
	else
		return EINVFN;
	return 0;
}

static long ARGS_ON_STACK 
mouse_select(f, p, mode)
	FILEPTR *f;
	long p;
	int mode;
{
	UNUSED(f);

	if (mode != O_RDONLY) {
		if (mode == O_WRONLY)
			return 1;	/* we can always take output :-) */
		else
			return 0;	/* but don't care for anything else */
	}

	if (mousetail - mousehead)
		return 1;	/* input waiting already */

	if (mousersel)
		return 2;	/* collision */
	mousersel = p;
	return 0;
}

static void ARGS_ON_STACK 
mouse_unselect(f, p, mode)
	FILEPTR *f;
	long p;
	int mode;
{
	UNUSED(f);

	if (mode == O_RDONLY && mousersel == p)
		mousersel = 0;
}


/*
 * UTILITY ROUTINE called by Bconmap() in xbios.c:
 * this sets handle -1 of process p to a file handle
 * that has BIOS device "dev". Returns 0 on failure,
 * non-zero on success.
 */

int
set_auxhandle(p, dev)
	PROC *p;
	int dev;
{
	FILEPTR *f;
	struct bios_file *b;

	f = new_fileptr();
	if (f) {
		struct tty *tty;
		f->links = 1;
		f->flags = O_RDWR;
		f->pos = 0;
		f->devinfo = 0;
		f->fc.fs = &bios_filesys;
		f->fc.aux = dev;
		f->fc.dev = BIOSDRV;
		for (b = broot; b; b = b->next) {
			if (b->private == dev &&
			    (b->device == &bios_tdevice ||
			     b->device == &bios_ndevice)) {
				f->fc.index = (long)b;
				f->dev = b->device;
				if (b->device != &fakedev)
					f->devinfo = (long)b->tty;
#if 1
/* don't close and reopen the same device again
 */
				if (p->aux && p->aux->fc.fs == &bios_filesys &&
				    p->aux->fc.index == f->fc.index) {
					f->links = 0;
					dispose_fileptr(f);
					return 1;
				}
#endif
				goto found_device;
			}
		}
		f->fc.index = 0;
		f->dev = &bios_ndevice;
found_device:
		if ((*f->dev->open)(f) < 0) {
			f->links = 0;
			dispose_fileptr(f);
			return 0;
		}
/* special code for opening a tty */
		if (NULL != (tty = (struct tty *)f->devinfo)) {
			extern struct tty default_tty;	/* in tty.c */

			tty->use_cnt++;
			while (tty->hup_ospeed) {
				sleep (IO_Q, (long)&tty->state);
			}
			/* first open for this device? */
			if (tty->use_cnt == 1) {
				short s = tty->state & TS_BLIND;
				*tty = default_tty;
				tty->state = s;
				tty->use_cnt = 1;
				tty_ioctl(f, TIOCSTART, 0);
			}
			tty->aux_cnt++;
			f->pos = 1;	/* flag for close to --aux_cnt */
		}
	} else {
/* no memory! use the fake FILEPTR we
 * set up in biosfs_init
 */
		f = defaultaux;
		f->links++;
	}

	(void)do_pclose(p, p->aux);
	p->aux = f;

	return 1;
}
