/*
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009
 *	The President and Fellows of Harvard College.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Emulator passthrough filesystem.
 *
 * The idea is that this appears as a filesystem in the VFS layer, and
 * passes VFS operations through a somewhat complicated "hardware"
 * interface to some simulated "hardware" in System/161 that accesses
 * the filesystem System/161 is running in.
 *
 * This makes it unnecessary to copy the system files to the simulated
 * disk, although we recommend doing so and trying running without this
 * device as part of testing your filesystem.
 */

#include <types.h>
#include <kern/errno.h>
#include <kern/fcntl.h>
#include <stat.h>
#include <lib.h>
#include <array.h>
#include <uio.h>
#include <synch.h>
#include <lamebus/emu.h>
#include <platform/bus.h>
#include <vfs.h>
#include <emufs.h>
#include "autoconf.h"

/* Register offsets */
#define REG_HANDLE    0
#define REG_OFFSET    4
#define REG_IOLEN     8
#define REG_OPER      12
#define REG_RESULT    16

/* I/O buffer offset */
#define EMU_BUFFER    32768

/* Operation codes for REG_OPER */
#define EMU_OP_OPEN          1
#define EMU_OP_CREATE        2
#define EMU_OP_EXCLCREATE    3
#define EMU_OP_CLOSE         4
#define EMU_OP_READ          5
#define EMU_OP_READDIR       6
#define EMU_OP_WRITE         7
#define EMU_OP_GETSIZE       8
#define EMU_OP_TRUNC         9

/* Result codes for REG_RESULT */
#define EMU_RES_SUCCESS      1
#define EMU_RES_BADHANDLE    2
#define EMU_RES_BADOP        3
#define EMU_RES_BADPATH      4
#define EMU_RES_BADSIZE      5
#define EMU_RES_EXISTS       6
#define EMU_RES_ISDIR        7
#define EMU_RES_MEDIA        8
#define EMU_RES_NOHANDLES    9
#define EMU_RES_NOSPACE      10
#define EMU_RES_NOTDIR       11
#define EMU_RES_UNKNOWN      12
#define EMU_RES_UNSUPP       13

////////////////////////////////////////////////////////////
//
// Hardware ops
//

/*
 * Shortcut for reading a register
 */
static
inline
uint32_t
emu_rreg(struct emu_softc *sc, uint32_t reg)
{
	return bus_read_register(sc->e_busdata, sc->e_buspos, reg);
}

/*
 * Shortcut for writing a register
 */
static
inline
void
emu_wreg(struct emu_softc *sc, uint32_t reg, uint32_t val)
{
	bus_write_register(sc->e_busdata, sc->e_buspos, reg, val);
}

/*
 * Called by the underlying bus code when an interrupt happens
 */
void
emu_irq(void *dev)
{
	struct emu_softc *sc = dev;

	sc->e_result = emu_rreg(sc, REG_RESULT);
	emu_wreg(sc, REG_RESULT, 0);

	V(sc->e_sem);
}

/*
 * Convert the error codes reported by the "hardware" to errnos.
 * Or, on cases that indicate a programming error in emu.c, panic.
 */
static
uint32_t
translate_err(struct emu_softc *sc, uint32_t code)
{
	switch (code) {
	    case EMU_RES_SUCCESS: return 0;
	    case EMU_RES_BADHANDLE: 
	    case EMU_RES_BADOP: 
	    case EMU_RES_BADSIZE: 
		panic("emu%d: got fatal result code %d\n", sc->e_unit, code);
	    case EMU_RES_BADPATH: return ENOENT;
	    case EMU_RES_EXISTS: return EEXIST;
	    case EMU_RES_ISDIR: return EISDIR;
	    case EMU_RES_MEDIA: return EIO;
	    case EMU_RES_NOHANDLES: return ENFILE;
	    case EMU_RES_NOSPACE: return ENOSPC;
	    case EMU_RES_NOTDIR: return ENOTDIR;
	    case EMU_RES_UNKNOWN: return EIO;
	    case EMU_RES_UNSUPP: return EUNIMP;
	}
	kprintf("emu%d: Unknown result code %d\n", sc->e_unit, code);
	return EAGAIN;
}

/*
 * Wait for an operation to complete, and return an errno for the result.
 */
static
int
emu_waitdone(struct emu_softc *sc)
{
	P(sc->e_sem);
	return translate_err(sc, sc->e_result);
}

/*
 * Common file open routine (for both VOP_LOOKUP and VOP_CREATE).  Not
 * for VOP_OPEN. At the hardware level, we need to "open" files in
 * order to look at them, so by the time VOP_OPEN is called the
 * files are already open.
 */
static
int
emu_open(struct emu_softc *sc, uint32_t handle, const char *name,
	 bool create, bool excl, mode_t mode,
	 uint32_t *newhandle, int *newisdir)
{
	uint32_t op;
	int result;

	if (strlen(name)+1 > EMU_MAXIO) {
		return ENAMETOOLONG;
	}

	if (create && excl) {
		op = EMU_OP_EXCLCREATE;
	}
	else if (create) {
		op = EMU_OP_CREATE;
	}
	else {
		op = EMU_OP_OPEN;
	}

	/* mode isn't supported (yet?) */
	(void)mode;

	lock_acquire(sc->e_lock);

	strcpy(sc->e_iobuf, name);
	emu_wreg(sc, REG_IOLEN, strlen(name));
	emu_wreg(sc, REG_HANDLE, handle);
	emu_wreg(sc, REG_OPER, op);
	result = emu_waitdone(sc);

	if (result==0) {
		*newhandle = emu_rreg(sc, REG_HANDLE);
		*newisdir = emu_rreg(sc, REG_IOLEN)>0;
	}

	lock_release(sc->e_lock);
	return result;
}

/*
 * Routine for closing a file we opened at the hardware level.
 * This is not necessarily called at VOP_CLOSE time; it's called
 * at VOP_RECLAIM time.
 */
static
int
emu_close(struct emu_softc *sc, uint32_t handle)
{
	int result;
	bool mine;
	int retries = 0;

	mine = lock_do_i_hold(sc->e_lock);
	if (!mine) {
		lock_acquire(sc->e_lock);
	}

	while (1) {
		/* Retry operation up to 10 times */

		emu_wreg(sc, REG_HANDLE, handle);
		emu_wreg(sc, REG_OPER, EMU_OP_CLOSE);
		result = emu_waitdone(sc);

		if (result==EIO && retries < 10) {
			kprintf("emu%d: I/O error on close, retrying\n", 
				sc->e_unit);
			retries++;
			continue;
		}
		break;
	}

	if (!mine) {
		lock_release(sc->e_lock);
	}
	return result;
}

/*
 * Common code for read and readdir.
 */
static
int
emu_doread(struct emu_softc *sc, uint32_t handle, uint32_t len,
	   uint32_t op, struct uio *uio)
{
	int result;

	KASSERT(uio->uio_rw == UIO_READ);

	lock_acquire(sc->e_lock);

	emu_wreg(sc, REG_HANDLE, handle);
	emu_wreg(sc, REG_IOLEN, len);
	emu_wreg(sc, REG_OFFSET, uio->uio_offset);
	emu_wreg(sc, REG_OPER, op);
	result = emu_waitdone(sc);
	if (result) {
		goto out;
	}
	
	result = uiomove(sc->e_iobuf, emu_rreg(sc, REG_IOLEN), uio);

	uio->uio_offset = emu_rreg(sc, REG_OFFSET);

 out:
	lock_release(sc->e_lock);
	return result;
}

/*
 * Read from a hardware-level file handle.
 */
static
int
emu_read(struct emu_softc *sc, uint32_t handle, uint32_t len,
	 struct uio *uio)
{
	return emu_doread(sc, handle, len, EMU_OP_READ, uio);
}

/*
 * Read a directory entry from a hardware-level file handle.
 */
static
int
emu_readdir(struct emu_softc *sc, uint32_t handle, uint32_t len,
	    struct uio *uio)
{
	return emu_doread(sc, handle, len, EMU_OP_READDIR, uio);
}

/*
 * Write to a hardware-level file handle.
 */
static
int
emu_write(struct emu_softc *sc, uint32_t handle, uint32_t len,
	  struct uio *uio)
{
	int result;

	KASSERT(uio->uio_rw == UIO_WRITE);

	lock_acquire(sc->e_lock);

	emu_wreg(sc, REG_HANDLE, handle);
	emu_wreg(sc, REG_IOLEN, len);
	emu_wreg(sc, REG_OFFSET, uio->uio_offset);

	result = uiomove(sc->e_iobuf, len, uio);
	if (result) {
		goto out;
	}

	emu_wreg(sc, REG_OPER, EMU_OP_WRITE);
	result = emu_waitdone(sc);

 out:
	lock_release(sc->e_lock);
	return result;
}

/*
 * Get the file size associated with a hardware-level file handle.
 */
static
int
emu_getsize(struct emu_softc *sc, uint32_t handle, off_t *retval)
{
	int result;

	lock_acquire(sc->e_lock);

	emu_wreg(sc, REG_HANDLE, handle);
	emu_wreg(sc, REG_OPER, EMU_OP_GETSIZE);
	result = emu_waitdone(sc);
	if (result==0) {
		*retval = emu_rreg(sc, REG_IOLEN);
	}

	lock_release(sc->e_lock);
	return result;
}

/*
 * Truncate a hardware-level file handle.
 */
static
int
emu_trunc(struct emu_softc *sc, uint32_t handle, off_t len)
{
	int result;

	lock_acquire(sc->e_lock);

	emu_wreg(sc, REG_HANDLE, handle);
	emu_wreg(sc, REG_IOLEN, len);
	emu_wreg(sc, REG_OPER, EMU_OP_TRUNC);
	result = emu_waitdone(sc);

	lock_release(sc->e_lock);
	return result;
}

//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
//
// vnode functions 
//

// at bottom of this section

static int emufs_loadvnode(struct emufs_fs *ef, uint32_t handle, int isdir,
			   struct emufs_vnode **ret);

/*
 * VOP_OPEN on files
 */
static
int
emufs_open(struct vnode *v, int openflags)
{
	/*
	 * At this level we do not need to handle O_CREAT, O_EXCL, or O_TRUNC.
	 * We *would* need to handle O_APPEND, but we don't support it.
	 *
	 * Any of O_RDONLY, O_WRONLY, and O_RDWR are valid, so we don't need
	 * to check that either.
	 */

	if (openflags & O_APPEND) {
		return EUNIMP;
	}

	(void)v;

	return 0;
}

/*
 * VOP_OPEN on directories
 */
static
int
emufs_opendir(struct vnode *v, int openflags)
{
	switch (openflags & O_ACCMODE) {
	    case O_RDONLY:
		break;
	    case O_WRONLY:
	    case O_RDWR:
	    default:
		return EISDIR;
	}
	if (openflags & O_APPEND) {
		return EISDIR;
	}

	(void)v;
	return 0;
}

/*
 * VOP_CLOSE
 */
static
int
emufs_close(struct vnode *v)
{
	(void)v;
	return 0;
}

/*
 * VOP_RECLAIM
 *
 * Reclaim should make an effort to returning errors other than EBUSY.
 */
static
int
emufs_reclaim(struct vnode *v)
{
	struct emufs_vnode *ev = v->vn_data;
	struct emufs_fs *ef = v->vn_fs->fs_data;
	unsigned ix, i, num;
	int result;

	/*
	 * Need both of these locks, e_lock to protect the device
	 * and vfs_biglock to protect the fs-related material.
	 */

	vfs_biglock_acquire();
	lock_acquire(ef->ef_emu->e_lock);

	if (ev->ev_v.vn_refcount != 1) {
		lock_release(ef->ef_emu->e_lock);
		vfs_biglock_release();
		return EBUSY;
	}

	/* emu_close retries on I/O error */
	result = emu_close(ev->ev_emu, ev->ev_handle);
	if (result) {
		lock_release(ef->ef_emu->e_lock);
		vfs_biglock_release();
		return result;
	}

	num = vnodearray_num(ef->ef_vnodes);
	ix = num;
	for (i=0; i<num; i++) {
		struct vnode *vx;

		vx = vnodearray_get(ef->ef_vnodes, i);
		if (vx == v) {
			ix = i;
			break;
		}
	}
	if (ix == num) {
		panic("emu%d: reclaim vnode %u not in vnode pool\n",
		      ef->ef_emu->e_unit, ev->ev_handle);
	}

	vnodearray_remove(ef->ef_vnodes, ix);
	VOP_CLEANUP(&ev->ev_v);

	lock_release(ef->ef_emu->e_lock);
	vfs_biglock_release();

	kfree(ev);
	return 0;
}

/*
 * VOP_READ
 */
static
int
emufs_read(struct vnode *v, struct uio *uio)
{
	struct emufs_vnode *ev = v->vn_data;
	uint32_t amt;
	size_t oldresid;
	int result;

	KASSERT(uio->uio_rw==UIO_READ);

	while (uio->uio_resid > 0) {
		amt = uio->uio_resid;
		if (amt > EMU_MAXIO) {
			amt = EMU_MAXIO;
		}

		oldresid = uio->uio_resid;

		result = emu_read(ev->ev_emu, ev->ev_handle, amt, uio);
		if (result) {
			return result;
		}
		
		if (uio->uio_resid == oldresid) {
			/* nothing read - EOF */
			break;
		}
	}

	return 0;
}

/*
 * VOP_READDIR
 */
static
int
emufs_getdirentry(struct vnode *v, struct uio *uio)
{
	struct emufs_vnode *ev = v->vn_data;
	uint32_t amt;

	KASSERT(uio->uio_rw==UIO_READ);

	amt = uio->uio_resid;
	if (amt > EMU_MAXIO) {
		amt = EMU_MAXIO;
	}

	return emu_readdir(ev->ev_emu, ev->ev_handle, amt, uio);
}

/*
 * VOP_WRITE
 */
static
int
emufs_write(struct vnode *v, struct uio *uio)
{
	struct emufs_vnode *ev = v->vn_data;
	uint32_t amt;
	size_t oldresid;
	int result;

	KASSERT(uio->uio_rw==UIO_WRITE);

	while (uio->uio_resid > 0) {
		amt = uio->uio_resid;
		if (amt > EMU_MAXIO) {
			amt = EMU_MAXIO;
		}

		oldresid = uio->uio_resid;

		result = emu_write(ev->ev_emu, ev->ev_handle, amt, uio);
		if (result) {
			return result;
		}

		if (uio->uio_resid == oldresid) {
			/* nothing written...? */
			break;
		}
	}

	return 0;
}

/*
 * VOP_IOCTL
 */
static
int
emufs_ioctl(struct vnode *v, int op, userptr_t data)
{
	/*
	 * No ioctls.
	 */

	(void)v;
	(void)op;
	(void)data;

	return EINVAL;
}

/*
 * VOP_STAT
 */
static
int
emufs_stat(struct vnode *v, struct stat *statbuf)
{
	struct emufs_vnode *ev = v->vn_data;
	int result;

	bzero(statbuf, sizeof(struct stat));

	result = emu_getsize(ev->ev_emu, ev->ev_handle, &statbuf->st_size);
	if (result) {
		return result;
	}

	result = VOP_GETTYPE(v, &statbuf->st_mode);
	if (result) {
		return result;
	}
	statbuf->st_mode |= 0644; /* possibly a lie */
	statbuf->st_nlink = 1;    /* might be a lie, but doesn't matter much */
	statbuf->st_blocks = 0;   /* almost certainly a lie */

	return 0;
}

/*
 * VOP_GETTYPE for files
 */
static
int
emufs_file_gettype(struct vnode *v, uint32_t *result)
{
	(void)v;
	*result = S_IFREG;
	return 0;
}

/*
 * VOP_GETTYPE for directories
 */
static
int
emufs_dir_gettype(struct vnode *v, uint32_t *result)
{
	(void)v;
	*result = S_IFDIR;
	return 0;
}

/*
 * VOP_TRYSEEK
 */
static
int
emufs_tryseek(struct vnode *v, off_t pos)
{
	if (pos<0) {
		return EINVAL;
	}

	/* Allow anything else */
	(void)v;

	return 0;
}

/*
 * VOP_FSYNC
 */
static
int
emufs_fsync(struct vnode *v)
{
	(void)v;
	return 0;
}

/*
 * VOP_TRUNCATE
 */
static
int
emufs_truncate(struct vnode *v, off_t len)
{
	struct emufs_vnode *ev = v->vn_data;
	return emu_trunc(ev->ev_emu, ev->ev_handle, len);
}

/*
 * VOP_CREAT
 */
static
int
emufs_creat(struct vnode *dir, const char *name, bool excl, mode_t mode,
	    struct vnode **ret)
{
	struct emufs_vnode *ev = dir->vn_data;
	struct emufs_fs *ef = dir->vn_fs->fs_data;
	struct emufs_vnode *newguy;
	uint32_t handle;
	int result;
	int isdir;

	result = emu_open(ev->ev_emu, ev->ev_handle, name, true, excl, mode,
			  &handle, &isdir);
	if (result) {
		return result;
	}

	result = emufs_loadvnode(ef, handle, isdir, &newguy);
	if (result) {
		emu_close(ev->ev_emu, handle);
		return result;
	}

	*ret = &newguy->ev_v;
	return 0;
}

/*
 * VOP_LOOKUP
 */
static
int
emufs_lookup(struct vnode *dir, char *pathname, struct vnode **ret)
{
	struct emufs_vnode *ev = dir->vn_data;
	struct emufs_fs *ef = dir->vn_fs->fs_data;
	struct emufs_vnode *newguy;
	uint32_t handle;
	int result;
	int isdir;

	result = emu_open(ev->ev_emu, ev->ev_handle, pathname, false, false, 0,
			  &handle, &isdir);
	if (result) {
		return result;
	}

	result = emufs_loadvnode(ef, handle, isdir, &newguy);
	if (result) {
		emu_close(ev->ev_emu, handle);
		return result;
	}

	*ret = &newguy->ev_v;
	return 0;
}

/*
 * VOP_LOOKPARENT
 */
static
int
emufs_lookparent(struct vnode *dir, char *pathname, struct vnode **ret,
		 char *buf, size_t len)
{
	char *s;

	s = strrchr(pathname, '/');
	if (s==NULL) {
		/* just a last component, no directory part */
		if (strlen(pathname)+1 > len) {
			return ENAMETOOLONG;
		}
		VOP_INCREF(dir);
		*ret = dir;
		strcpy(buf, pathname);
		return 0;
	}

	*s = 0;
	s++;
	if (strlen(s)+1 > len) {
		return ENAMETOOLONG;
	}
	strcpy(buf, s);

	return emufs_lookup(dir, pathname, ret);
}

/*
 * VOP_NAMEFILE
 */
static
int
emufs_namefile(struct vnode *v, struct uio *uio)
{
	struct emufs_vnode *ev = v->vn_data;
	struct emufs_fs *ef = v->vn_fs->fs_data;

	if (ev == ef->ef_root) {
		/*
		 * Root directory - name is empty string
		 */
		return 0;
	}

	(void)uio;
	
	return EUNIMP;
}

/*
 * VOP_MMAP
 */
static
int
emufs_mmap(struct vnode *v)
{
	(void)v;
	return EUNIMP;
}

//////////////////////////////

/*
 * Bits not implemented at all on emufs
 */

static
int
emufs_dir_tryseek(struct vnode *v, off_t pos)
{
	(void)v;
	(void)pos;
	return EUNIMP;
}

static
int
emufs_symlink(struct vnode *v, const char *contents, const char *name)
{
	(void)v;
	(void)contents;
	(void)name;
	return EUNIMP;
}

static
int
emufs_mkdir(struct vnode *v, const char *name, mode_t mode)
{
	(void)v;
	(void)name;
	(void)mode;
	return EUNIMP;
}

static
int
emufs_link(struct vnode *v, const char *name, struct vnode *target)
{
	(void)v;
	(void)name;
	(void)target;
	return EUNIMP;
}

static
int
emufs_remove(struct vnode *v, const char *name)
{
	(void)v;
	(void)name;
	return EUNIMP;
}

static
int
emufs_rmdir(struct vnode *v, const char *name)
{
	(void)v;
	(void)name;
	return EUNIMP;
}

static
int
emufs_rename(struct vnode *v1, const char *n1,
	     struct vnode *v2, const char *n2)
{
	(void)v1;
	(void)n1;
	(void)v2;
	(void)n2;
	return EUNIMP;
}

//////////////////////////////

/*
 * Routines that fail
 *
 * It is kind of silly to write these out each with their particular
 * arguments; however, portable C doesn't let you cast function
 * pointers with different argument signatures even if the arguments
 * are never used.
 *
 * The BSD approach (all vnode ops take a vnode pointer and a void
 * pointer that's cast to a op-specific args structure) avoids this
 * problem but is otherwise not very appealing.
 */

static
int
emufs_void_op_isdir(struct vnode *v)
{
	(void)v;
	return EISDIR;
}

static
int
emufs_uio_op_isdir(struct vnode *v, struct uio *uio)
{
	(void)v;
	(void)uio;
	return EISDIR;
}

static
int
emufs_uio_op_notdir(struct vnode *v, struct uio *uio)
{
	(void)v;
	(void)uio;
	return ENOTDIR;
}

static
int
emufs_name_op_notdir(struct vnode *v, const char *name)
{
	(void)v;
	(void)name;
	return ENOTDIR;
}

static
int
emufs_readlink_notlink(struct vnode *v, struct uio *uio)
{
	(void)v;
	(void)uio;
	return EINVAL;
}

static
int
emufs_creat_notdir(struct vnode *v, const char *name, bool excl, mode_t mode,
		   struct vnode **retval)
{
	(void)v;
	(void)name;
	(void)excl;
	(void)mode;
	(void)retval;
	return ENOTDIR;
}

static
int
emufs_symlink_notdir(struct vnode *v, const char *contents, const char *name)
{
	(void)v;
	(void)contents;
	(void)name;
	return ENOTDIR;
}

static
int
emufs_mkdir_notdir(struct vnode *v, const char *name, mode_t mode)
{
	(void)v;
	(void)name;
	(void)mode;
	return ENOTDIR;
}

static
int
emufs_link_notdir(struct vnode *v, const char *name, struct vnode *target)
{
	(void)v;
	(void)name;
	(void)target;
	return ENOTDIR;
}

static
int
emufs_rename_notdir(struct vnode *v1, const char *n1,
		    struct vnode *v2, const char *n2)
{
	(void)v1;
	(void)n1;
	(void)v2;
	(void)n2;
	return ENOTDIR;
}

static
int
emufs_lookup_notdir(struct vnode *v, char *pathname, struct vnode **result)
{
	(void)v;
	(void)pathname;
	(void)result;
	return ENOTDIR;
}

static
int
emufs_lookparent_notdir(struct vnode *v, char *pathname, struct vnode **result,
			char *buf, size_t len)
{
	(void)v;
	(void)pathname;
	(void)result;
	(void)buf;
	(void)len;
	return ENOTDIR;
}


static
int
emufs_truncate_isdir(struct vnode *v, off_t len)
{
	(void)v;
	(void)len;
	return ENOTDIR;
}

//////////////////////////////

/*
 * Function table for emufs files.
 */
static const struct vnode_ops emufs_fileops = {
	VOP_MAGIC,	/* mark this a valid vnode ops table */

	emufs_open,
	emufs_close,
	emufs_reclaim,

	emufs_read,
	emufs_readlink_notlink,
	emufs_uio_op_notdir, /* getdirentry */
	emufs_write,
	emufs_ioctl,
	emufs_stat,
	emufs_file_gettype,
	emufs_tryseek,
	emufs_fsync,
	emufs_mmap,
	emufs_truncate,
	emufs_uio_op_notdir, /* namefile */

	emufs_creat_notdir,
	emufs_symlink_notdir,
	emufs_mkdir_notdir,
	emufs_link_notdir,
	emufs_name_op_notdir, /* remove */
	emufs_name_op_notdir, /* rmdir */
	emufs_rename_notdir,

	emufs_lookup_notdir,
	emufs_lookparent_notdir,
};

/*
 * Function table for emufs directories.
 */
static const struct vnode_ops emufs_dirops = {
	VOP_MAGIC,	/* mark this a valid vnode ops table */

	emufs_opendir,
	emufs_close,
	emufs_reclaim,

	emufs_uio_op_isdir,   /* read */
	emufs_uio_op_isdir,   /* readlink */
	emufs_getdirentry,
	emufs_uio_op_isdir,   /* write */
	emufs_ioctl,
	emufs_stat,
	emufs_dir_gettype,
	emufs_dir_tryseek,
	emufs_void_op_isdir,  /* fsync */
	emufs_void_op_isdir,  /* mmap */
	emufs_truncate_isdir,
	emufs_namefile,

	emufs_creat,
	emufs_symlink,
	emufs_mkdir,
	emufs_link,
	emufs_remove,
	emufs_rmdir,
	emufs_rename,

	emufs_lookup,
	emufs_lookparent,
};

/*
 * Function to load a vnode into memory.
 */
static
int
emufs_loadvnode(struct emufs_fs *ef, uint32_t handle, int isdir,
		struct emufs_vnode **ret)
{
	struct vnode *v;
	struct emufs_vnode *ev;
	unsigned i, num;
	int result;

	vfs_biglock_acquire();
	lock_acquire(ef->ef_emu->e_lock);

	num = vnodearray_num(ef->ef_vnodes);
	for (i=0; i<num; i++) {
		v = vnodearray_get(ef->ef_vnodes, i);
		ev = v->vn_data;
		if (ev->ev_handle == handle) {
			/* Found */

			VOP_INCREF(&ev->ev_v);

			lock_release(ef->ef_emu->e_lock);
			vfs_biglock_release();
			*ret = ev;
			return 0;
		}
	}

	/* Didn't have one; create it */

	ev = kmalloc(sizeof(struct emufs_vnode));
	if (ev==NULL) {
		lock_release(ef->ef_emu->e_lock);
		return ENOMEM;
	}

	ev->ev_emu = ef->ef_emu;
	ev->ev_handle = handle;

	result = VOP_INIT(&ev->ev_v, isdir ? &emufs_dirops : &emufs_fileops,
			   &ef->ef_fs, ev);
	if (result) {
		lock_release(ef->ef_emu->e_lock);
		vfs_biglock_release();
		kfree(ev);
		return result;
	}

	result = vnodearray_add(ef->ef_vnodes, &ev->ev_v, NULL);
	if (result) {
		/* note: VOP_CLEANUP undoes VOP_INIT - it does not kfree */
		VOP_CLEANUP(&ev->ev_v);
		lock_release(ef->ef_emu->e_lock);
		vfs_biglock_release();
		kfree(ev);
		return result;
	}

	lock_release(ef->ef_emu->e_lock);
	vfs_biglock_release();

	*ret = ev;
	return 0;
}

//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
//
// Whole-filesystem functions 
//

/*
 * FSOP_SYNC
 */
static
int
emufs_sync(struct fs *fs)
{
	(void)fs;
	return 0;
}

/*
 * FSOP_GETVOLNAME
 */
static
const char *
emufs_getvolname(struct fs *fs)
{
	/* We don't have a volume name beyond the device name */
	(void)fs;
	return NULL;
}

/*
 * FSOP_GETROOT
 */
static
struct vnode *
emufs_getroot(struct fs *fs)
{
	struct emufs_fs *ef;

	KASSERT(fs != NULL);

	ef = fs->fs_data;

	KASSERT(ef != NULL);
	KASSERT(ef->ef_root != NULL);

	VOP_INCREF(&ef->ef_root->ev_v);
	return &ef->ef_root->ev_v;
}

/*
 * FSOP_UNMOUNT
 */
static
int
emufs_unmount(struct fs *fs)
{
	/* Always prohibit unmount, as we're not really "mounted" */
	(void)fs;
	return EBUSY;
}

/*
 * Routine for "mounting" an emufs - we're not really mounted in the
 * sense that the VFS understands that term, because we're not
 * connected to a block device.
 *
 * Basically, we just add ourselves to the name list in the VFS layer.
 */
static
int
emufs_addtovfs(struct emu_softc *sc, const char *devname)
{
	struct emufs_fs *ef;
	int result;

	ef = kmalloc(sizeof(struct emufs_fs));
	if (ef==NULL) {
		return ENOMEM;
	}

	ef->ef_fs.fs_sync = emufs_sync;
	ef->ef_fs.fs_getvolname = emufs_getvolname;
	ef->ef_fs.fs_getroot = emufs_getroot;
	ef->ef_fs.fs_unmount = emufs_unmount;
	ef->ef_fs.fs_data = ef;

	ef->ef_emu = sc;
	ef->ef_root = NULL;
	ef->ef_vnodes = vnodearray_create();
	if (ef->ef_vnodes == NULL) {
		kfree(ef);
		return ENOMEM;
	}

	result = emufs_loadvnode(ef, EMU_ROOTHANDLE, 1, &ef->ef_root);
	if (result) {
		kfree(ef);
		return result;
	}

	KASSERT(ef->ef_root!=NULL);

	result = vfs_addfs(devname, &ef->ef_fs);
	if (result) {
		VOP_DECREF(&ef->ef_root->ev_v);
		kfree(ef);
	}
	return result;
}

//
////////////////////////////////////////////////////////////

/*
 * Config routine called by autoconf stuff.
 *
 * Initialize our data, then add ourselves to the VFS layer.
 */
int
config_emu(struct emu_softc *sc, int emuno)
{
	char name[32];

	sc->e_lock = lock_create("emufs-lock");
	if (sc->e_lock == NULL) {
		return ENOMEM;
	}
	sc->e_sem = sem_create("emufs-sem", 0);
	if (sc->e_sem == NULL) {
		lock_destroy(sc->e_lock);
		sc->e_lock = NULL;
		return ENOMEM;
	}
	sc->e_iobuf = bus_map_area(sc->e_busdata, sc->e_buspos, EMU_BUFFER);

	snprintf(name, sizeof(name), "emu%d", emuno);

	return emufs_addtovfs(sc, name);
}
