/* 
 * fsDevice.c --
 *
 *	Routines to manage devices.  
 *	The handle for a device is uniquified using the device type and
 *	unit number.  This ensures that different filesystem names for
 *	the same device map to the same device I/O handle.
 *
 *	There are two sets of routines here.  There are routines internal
 *	to the filesystem that are used to open, close, read, write, etc.
 *	a device stream.  Then there are some external routines exported
 *	for use by device drivers.  Fs_NotifyReader and Fs_NotifyWriter
 *	are used by interrupt handlers to indicate a device is ready.
 *	Then there are a handful of conversion routines for mapping
 *	from file system block numbers to disk addresses.
 *
 * Copyright 1987 Regents of the University of California
 * All rights reserved.
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /user5/kupfer/spriteserver/src/sprited/fsio/RCS/fsioDevice.c,v 1.3 92/03/23 14:44:05 kupfer Exp $ SPRITE (Berkeley)";
#endif not lint


#include <sprite.h>

#include <fs.h>
#include <fsutil.h>
#include <fsconsist.h>
#include <fsio.h>
#include <fsNameOps.h>
#include <fsdm.h>
#include <fsioLock.h>
#include <fsprefix.h>
#include <fsrmt.h>
#include <dev.h>
#include <rpc.h>
#include <fsStat.h>
#include <fsioDevice.h>
#include <devFsOpTable.h>
#include <devTypes.h>

/*
 * INET is defined so a file server can be used to open the
 * special device file corresponding to a kernel-based ipServer
 */
#undef INET
#ifdef INET
#include <netInet.h>
/*
 * DEV_PLACEHOLDER_2	is defined in devTypesInt.h, which is not exported.
 *	This is an ugly hack, anyway, so we just define it here.  3/89
 *	The best solution is to define a new disk file type and not
 *	use Fsio_DeviceNameOpen at all.
 */
#define DEV_PLACEHOLDER_2	3
static int sockCounter = 0;
#endif

static void ReadNotify _ARGS_((ClientData data, Proc_CallInfo *callInfoPtr));
static void WriteNotify _ARGS_((ClientData data, Proc_CallInfo *callInfoPtr));
static void ExceptionNotify _ARGS_((ClientData data, 
				Proc_CallInfo *callInfoPtr));

extern ReturnStatus FsioDeviceCloseInt _ARGS_((
		Fsio_DeviceIOHandle *devHandlePtr, int useFlags, int refs, 
		int writes));



/*
 *----------------------------------------------------------------------
 *
 * FsioDeviceHandleInit --
 *
 *	Initialize a handle for a local device.
 *
 * Results:
 *	TRUE if the handle was already found, FALSE if not.
 *
 * Side effects:
 *	Create and install a handle for the device.  It is returned locked
 *	and with its reference count incremented if SUCCESS is returned.
 *
 *----------------------------------------------------------------------
 */
Boolean
FsioDeviceHandleInit(fileIDPtr, name, newHandlePtrPtr)
    Fs_FileID		*fileIDPtr;
    char		*name;
    Fsio_DeviceIOHandle	**newHandlePtrPtr;
{
    register Boolean found;
    register Fsio_DeviceIOHandle *devHandlePtr;

    found = Fsutil_HandleInstall(fileIDPtr, sizeof(Fsio_DeviceIOHandle), name,
		    FALSE, (Fs_HandleHeader **)newHandlePtrPtr);
    if (!found) {
	devHandlePtr = *newHandlePtrPtr;
	List_Init(&devHandlePtr->clientList);
	devHandlePtr->use.ref = 0;
	devHandlePtr->use.write = 0;
	devHandlePtr->use.exec = 0;
	devHandlePtr->device.serverID = fileIDPtr->serverID;
	devHandlePtr->device.type = fileIDPtr->major;
	devHandlePtr->device.unit = fileIDPtr->minor;
	devHandlePtr->device.data = (ClientData)NIL;
	devHandlePtr->flags = 0;
	Fsio_LockInit(&devHandlePtr->lock);
	devHandlePtr->modifyTime = 0;
	devHandlePtr->accessTime = 0;
	List_Init(&devHandlePtr->readWaitList);
	List_Init(&devHandlePtr->writeWaitList);
	List_Init(&devHandlePtr->exceptWaitList);
	devHandlePtr->notifyFlags = 0;
	fs_Stats.object.devices++;
    }
    return(found);
}


/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceNameOpen --
 *
 *	This routine sets up an ioFileID for the device based on the
 *	device file found on the name server.  This is called on the name
 *	server in two cases, when a client is doing an open, and when
 *	it is doing a Get/Set attributes on a device file name.   At
 *	open time some additional attributes are returned to the client
 *	for caching at the I/O server, and a streamID is chosen.
 *	Note that no state is kept about the device client here on the
 *	name server.  The device client open routine sets up that state.
 *
 * Results:
 *	SUCCESS, *ioFileIDPtr has the I/O file ID, and *clientDataPtr
 *	references the device state.
 *
 * Side effects:
 *	Choose the fileID for the clients stream.
 *	Allocates memory to hold the stream data.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
Fsio_DeviceNameOpen(handlePtr, openArgsPtr, openResultsPtr)
    Fsio_FileIOHandle	*handlePtr;	/* A handle obtained by FslclLookup.
					 * Should be locked upon entry,
					 * is unlocked upon exit. */
     Fs_OpenArgs		*openArgsPtr;	/* Standard open arguments */
     Fs_OpenResults	*openResultsPtr;/* For returning ioFileID, streamID,
					 * and Fsio_DeviceState */
{
    register Fsdm_FileDescriptor *descPtr = handlePtr->descPtr;
    register Fsio_DeviceState *deviceDataPtr;
    register Fs_FileID *ioFileIDPtr = &openResultsPtr->ioFileID;

    ioFileIDPtr->serverID = descPtr->devServerID;
    if (ioFileIDPtr->serverID == FS_LOCALHOST_ID) {
	/*
	 * Map this "common" or "generic" device to the instance of
	 * the device on the process's logical home node.
	 */
	ioFileIDPtr->serverID = openArgsPtr->migClientID;
    }
    ioFileIDPtr->major = descPtr->devType;
    ioFileIDPtr->minor = descPtr->devUnit;
#ifdef INET
    /*
     * Hack in support for sockets by mapping a special device type
     * to sockets.  This needs to be replaced with a new type of disk file.
     */
    if (descPtr->devType == DEV_PLACEHOLDER_2) {
	ioFileIDPtr->major = rpc_SpriteID;
	ioFileIDPtr->minor = sockCounter++;
	switch(descPtr->devUnit) {
	    case NET_IP_PROTOCOL_IP:
		ioFileIDPtr->type = FSIO_RAW_IP_STREAM;
		break;
	    case NET_IP_PROTOCOL_UDP:
		ioFileIDPtr->type = FSIO_UDP_STREAM;
		break;
	    case NET_IP_PROTOCOL_TCP:
		ioFileIDPtr->type = FSIO_TCP_STREAM;
		break;
	    default:
		ioFileIDPtr->major = descPtr->devType;
		ioFileIDPtr->minor = descPtr->devUnit;
		if (ioFileIDPtr->serverID == openArgsPtr->clientID) {
		    ioFileIDPtr->type = FSIO_LCL_DEVICE_STREAM;
		} else {
		    ioFileIDPtr->type = FSIO_RMT_DEVICE_STREAM;
		}
		break;
	}
    } else
#endif
    if (ioFileIDPtr->serverID == openArgsPtr->clientID) {
	ioFileIDPtr->type = FSIO_LCL_DEVICE_STREAM;
    } else {
	ioFileIDPtr->type = FSIO_RMT_DEVICE_STREAM;
    }
    if (openArgsPtr->useFlags != 0) {
	/*
	 * Truely preparing for an open.
	 * Return the current modify/access times for the I/O server's cache.
	 */
	deviceDataPtr = mnew(Fsio_DeviceState);
	deviceDataPtr->modifyTime = descPtr->dataModifyTime;
	deviceDataPtr->accessTime = descPtr->accessTime;
	/*
	 * Choose a streamID for the client that points to the device server.
	 */
	Fsio_StreamCreateID(ioFileIDPtr->serverID, &openResultsPtr->streamID);
	deviceDataPtr->streamID = openResultsPtr->streamID;

	openResultsPtr->streamData = (ClientData)deviceDataPtr;
	openResultsPtr->dataSize = sizeof(Fsio_DeviceState);
    }
    Fsutil_HandleUnlock(handlePtr);
    return(SUCCESS);
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceIoOpen --
 *
 *	Complete opening of a local device.  This is called on the I/O
 *	server and sets up state concerning this client.  The device
 *	driver open routine is called to set up the device.  If that
 *	succeeds then the device's Handle is installed and set up.
 *	This includes incrementing client's use counts and the
 *	global use counts in the handle.
 *
 * Results:
 *	SUCCESS or a device dependent open error code.
 *
 * Side effects:
 *	Sets up and installs the device's ioHandle.  The device-type open
 *	routine is called on the I/O server.  The handle is unlocked
 *	upon exit, but its reference count has been incremented.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fsio_DeviceIoOpen(ioFileIDPtr, flagsPtr, clientID, streamData, name, ioHandlePtrPtr)
    register Fs_FileID	*ioFileIDPtr;	/* I/O fileID */
    int			*flagsPtr;	/* FS_READ | FS_WRITE ... */
    int			clientID;	/* Host doing the open */
    ClientData		streamData;	/* Reference to Fsio_DeviceState struct */
    char		*name;		/* File name for error msgs */
    Fs_HandleHeader	**ioHandlePtrPtr;/* Return - a locked handle set up for
					 * I/O to a device, NIL if failure. */
{
    ReturnStatus 		status;
    Boolean			found;
    register Fsio_DeviceState	*deviceDataPtr;
    register Fsio_DeviceIOHandle	*devHandlePtr;
    Fsio_DeviceIOHandle		*tDevHandlePtr;	/* tempory devHandlePtr */
    register Fs_Stream		*streamPtr;
    register int		flags = *flagsPtr;

    deviceDataPtr = (Fsio_DeviceState *)streamData;
    *ioHandlePtrPtr = (Fs_HandleHeader *)NIL;

    found = FsioDeviceHandleInit(ioFileIDPtr, name, &tDevHandlePtr);
    devHandlePtr = tDevHandlePtr;
    /*
     * The device driver open routine gets the device specification,
     * the useFlags, and a token passed to Fs_NotifyReader
     * or Fs_NotifyWriter when the device becomes ready for I/O.
     */
    if (DEV_TYPE_INDEX(devHandlePtr->device.type) >= devNumDevices) {
	status = FS_DEVICE_OP_INVALID;
    } else {
	status = (*devFsOpTable[DEV_TYPE_INDEX(devHandlePtr->device.type)].open)
		    (&devHandlePtr->device, flags, 
			 (Fs_NotifyToken)devHandlePtr, &devHandlePtr->flags);
    }
    if (status == SUCCESS) {
	if (!found) {
	    /*
	     * Absorb the I/O attributes returned from the SrvOpen routine
	     * on the file server.
	     */
	    devHandlePtr->modifyTime = deviceDataPtr->modifyTime;
	    devHandlePtr->accessTime = deviceDataPtr->accessTime;
	}
	/*
	 * Trace the client, and then update our overall use counts.
	 * The client is recorded at the stream level to support migration,
	 * and at the I/O handle level for regular I/O.
	 */
	(void)Fsconsist_IOClientOpen(&devHandlePtr->clientList, clientID, flags, FALSE);

	streamPtr = Fsio_StreamAddClient(&deviceDataPtr->streamID, clientID,
			(Fs_HandleHeader *)devHandlePtr, flags,
			name, (Boolean *)NIL, (Boolean *)NIL);
	Fsutil_HandleRelease(streamPtr, TRUE);

	devHandlePtr->use.ref++;
	if (flags & FS_WRITE) {
	    devHandlePtr->use.write++;
	}
	*ioHandlePtrPtr = (Fs_HandleHeader *)devHandlePtr;
	Fsutil_HandleUnlock(devHandlePtr);
    } else {
	Fsutil_HandleRelease(devHandlePtr, TRUE);
    }
    ckfree((Address) deviceDataPtr);
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceClose --
 *
 *	Close a stream to a device.  We just need to clean up our state with
 *	the device driver.  The file server that named the device file keeps
 *	no state about us, so we don't have to contact it.
 *
 * FIX ME: need to write back access/modify times to name server
 *	Can use fs_AttrOpTable and the nameInfoPtr->fileID as long
 *	if the shadow stream on the I/O server is set up enough.
 *
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	Calls the device type close routine.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
#ifndef SOSP91
ReturnStatus
Fsio_DeviceClose(streamPtr, clientID, procID, flags, size, data)
#else
ReturnStatus
Fsio_DeviceClose(streamPtr, clientID, procID, flags, size, data, offsetPtr,
    rwFlagsPtr)
#endif
    Fs_Stream		*streamPtr;	/* Stream to device */
    int			clientID;	/* HostID of client closing */
    Proc_PID		procID;		/* ID of closing process */
    int			flags;		/* Flags from the stream being closed */
    int			size;		/* Should be zero */
    ClientData		data;		/* IGNORED */
#ifdef SOSP91
    int			*offsetPtr;	/* Not used. */
    int			*rwFlagsPtr;	/* Not used. */
#endif
{
    ReturnStatus		status;
    register Fsio_DeviceIOHandle	*devHandlePtr =
	    (Fsio_DeviceIOHandle *)streamPtr->ioHandlePtr;
    Boolean			cache = FALSE;

    if (!Fsconsist_IOClientClose(&devHandlePtr->clientList, clientID, flags, &cache)) {
	printf("Fsio_DeviceClose, client %d unknown for device <%d,%d>\n",
		  clientID, devHandlePtr->hdr.fileID.major,
		  devHandlePtr->hdr.fileID.minor);
	Fsutil_HandleRelease(devHandlePtr, TRUE);
	return(FS_STALE_HANDLE);
    }
    /*
     * Clean up locks, then
     * clean up summary use counts and call driver's close routine.
     */
    Fsio_LockClose(&devHandlePtr->lock, &streamPtr->hdr.fileID);

    status = FsioDeviceCloseInt(devHandlePtr, flags, 1, (flags & FS_WRITE) != 0);
    /*
     * We don't bother to remove the handle here if the device isn't
     * being used.  Instead we let the handle get scavenged.
     */
    Fsutil_HandleRelease(devHandlePtr, TRUE);

    return(status);
}

/*
 * ----------------------------------------------------------------------------
 *
 * FsioDeviceCloseInt --
 *
 *	???
 *	
 *
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	Removes the client list entry for the client and adjusts the
 *	use counts on the file.  This unlocks the handle.
 *
 * ----------------------------------------------------------------------------
 *
 */
ReturnStatus
FsioDeviceCloseInt(devHandlePtr, useFlags, refs, writes)
    Fsio_DeviceIOHandle *devHandlePtr;	/* Device handle */
    int useFlags;			/* Flags from the stream */
    int refs;				/* Number of refs to close */
    int writes;				/* Number of these that were writing */
{
    if (refs > 0) {
	devHandlePtr->use.ref -= refs;
    }
    if (writes > 0) {
	devHandlePtr->use.write -= writes;
    }

    if (devHandlePtr->use.ref < 0 || devHandlePtr->use.write < 0) {
	panic("FsioDeviceCloseInt <%d,%d> ref %d, write %d\n",
	    devHandlePtr->hdr.fileID.major, devHandlePtr->hdr.fileID.minor,
	    devHandlePtr->use.ref, devHandlePtr->use.write);
    }

    return (*devFsOpTable[DEV_TYPE_INDEX(devHandlePtr->device.type)].close)
	    (&devHandlePtr->device, useFlags, devHandlePtr->use.ref,
		devHandlePtr->use.write);
}

/*
 * ----------------------------------------------------------------------------
 *
 * Fsio_DeviceClientKill --
 *
 *	Called when a client is assumed down.  This cleans up the
 *	references due to the client.
 *	
 *
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	Removes the client list entry for the client and adjusts the
 *	use counts on the file.  This unlocks the handle.
 *
 * ----------------------------------------------------------------------------
 *
 */
/*ARGSUSED*/
void
Fsio_DeviceClientKill(hdrPtr, clientID)
    Fs_HandleHeader	*hdrPtr;	/* File to clean up */
    int			clientID;	/* Host assumed down */
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)hdrPtr;
    int refs, writes, execs;

    /*
     * Remove the client from the list of users, and see what it was doing.
     */
    Fsconsist_IOClientKill(&devHandlePtr->clientList, clientID, &refs, &writes, &execs);

    Fsio_LockClientKill(&devHandlePtr->lock, clientID);

    if (refs > 0) {
	int useFlags = FS_READ;		/* Have to assume this,
					 * which might be wrong for syslog */
	if (writes > 0) {
	    useFlags |= FS_WRITE;
	}
	(void)FsioDeviceCloseInt(devHandlePtr, useFlags, refs, writes);
    }
    Fsutil_HandleUnlock(devHandlePtr);
}

/*
 * ----------------------------------------------------------------------------
 *
 * Fsio_DeviceScavenge --
 *
 *	Called periodically to see if this handle is still needed.
 *	
 *
 * Results:
 *	TRUE if the handle was removed.
 *
 * Side effects:
 *	Removes the handle if it isn't referenced anymore and there
 *	are no remote clients.
 *
 * ----------------------------------------------------------------------------
 *
 */
Boolean
Fsio_DeviceScavenge(hdrPtr)
    Fs_HandleHeader	*hdrPtr;	/* File to clean up */
{
    register Fsio_DeviceIOHandle *handlePtr = (Fsio_DeviceIOHandle *)hdrPtr;

    if (handlePtr->use.ref == 0) {
	/*
	 * Remove handles for devices with no users.
	 */
	Fsutil_WaitListDelete(&handlePtr->readWaitList);
	Fsutil_WaitListDelete(&handlePtr->writeWaitList);
	Fsutil_WaitListDelete(&handlePtr->exceptWaitList);
	Fsutil_HandleRemove(handlePtr);
	fs_Stats.object.devices--;
	return(TRUE);
    } else {
        Fsutil_HandleUnlock(handlePtr);
	return(FALSE);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_VanillaDevReopen --
 *
 *	This is a simplified device driver re-open procedure.  It is called
 *	from Fsrmt_DeviceReopen via the device operation switch.  It, in turn,
 *	calls back through the device switch to the regular device open
 *	procedure.  Form many simple devices this is sufficient for a reopen.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *	
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
Fsio_VanillaDevReopen(devicePtr, refs, writes, notifyToken)
    Fs_Device *devicePtr;	/* Identifies the device */
    int refs;			/* Number of streams to the device */
    int writes;			/* Number of those that are for writing */
    Fs_NotifyToken notifyToken;	/* Used with Fsio_DevNotifyReader */
{
    int devIndex = DEV_TYPE_INDEX(devicePtr->type);
    int useFlags = 0;
    int flags;

    if (refs > 0) {
	useFlags |= FS_READ;
    }
    if (writes > 0) {
	useFlags |= FS_WRITE;
    }
    return((*devFsOpTable[devIndex].open)
				(devicePtr, useFlags, notifyToken, &flags));
}

/*
 * ----------------------------------------------------------------------------
 *
 * Fsio_DeviceMigClose --
 *
 *	Release a reference from a Device I/O handle.
 *	
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	Release the I/O handle.
 *
 * ----------------------------------------------------------------------------
 *
 */
/*ARGSUSED*/
ReturnStatus
Fsio_DeviceMigClose(hdrPtr, flags)
    Fs_HandleHeader *hdrPtr;	/* File being encapsulated */
    int flags;			/* Use flags from the stream */
{
    panic( "Fsio_DeviceMigClose called\n");
    Fsutil_HandleRelease(hdrPtr, FALSE);
    return(SUCCESS);
}


/*
 * ----------------------------------------------------------------------------
 *
 * Fsio_DeviceMigrate --
 *
 *	This takes care of transfering references from one client to the other.
 *	A useful side-effect of this routine is	to properly set the type in
 *	the ioFileID, either FSIO_LCL_DEVICE_STREAM or FSIO_RMT_DEVICE_STREAM.
 *	In the latter case FsRmtDevoceMigrate is called to do all the work.
 *
 * Results:
 *	An error status if the I/O handle can't be set-up.
 *	Otherwise SUCCESS is returned, *flagsPtr may have the FS_RMT_SHARED
 *	bit set, and *sizePtr and *dataPtr are set to reference Fsio_DeviceState.
 *
 * Side effects:
 *	Sets the correct stream type on the ioFileID.
 *	Shifts client references from the srcClient to the destClient.
 *	Set up and return Fsio_DeviceState for use by the MigEnd routine.
 *
 * ----------------------------------------------------------------------------
 *
 */
/*ARGSUSED*/
ReturnStatus
Fsio_DeviceMigrate(migInfoPtr, dstClientID, flagsPtr, offsetPtr, sizePtr, dataPtr)
    Fsio_MigInfo	*migInfoPtr;	/* Migration state */
    int		dstClientID;	/* ID of target client */
    int		*flagsPtr;	/* In/Out Stream usage flags */
    int		*offsetPtr;	/* Return - new stream offset */
    int		*sizePtr;	/* Return - sizeof(Fsio_DeviceState) */
    Address	*dataPtr;	/* Return - pointer to Fsio_DeviceState */
{
#if !defined(SPRITED_MIGRATION)
    panic("Fsio_DeviceMigrate called.\n");
    return FAILURE;
#else
    Fsio_DeviceIOHandle			*devHandlePtr;
    Boolean				closeSrcClient;

    if (migInfoPtr->ioFileID.serverID != rpc_SpriteID) {
	/*
	 * The device was local, which is why we were called, but is now remote.
	 */
	migInfoPtr->ioFileID.type = FSIO_RMT_DEVICE_STREAM;
	return(FsrmtDeviceMigrate(migInfoPtr, dstClientID, flagsPtr, offsetPtr,
		sizePtr, dataPtr));
    }
    migInfoPtr->ioFileID.type = FSIO_LCL_DEVICE_STREAM;
    if (!FsioDeviceHandleInit(&migInfoPtr->ioFileID, (char *)NIL, &devHandlePtr)){
	printf(
		"Fsio_DeviceMigrate, I/O handle <%d,%d> not found\n",
		 migInfoPtr->ioFileID.major, migInfoPtr->ioFileID.minor);
	return(FS_FILE_NOT_FOUND);
    }
    /*
     * At the stream level, add the new client to the set of clients
     * for the stream, and check for any cross-network stream sharing.
     */
    Fsio_StreamMigClient(migInfoPtr, dstClientID, (Fs_HandleHeader *)devHandlePtr,
		    &closeSrcClient);
    /*
     * Adjust use counts on the I/O handle to reflect any new sharing.
     */
    Fsio_MigrateUseCounts(migInfoPtr->flags, closeSrcClient, &devHandlePtr->use);

    /*
     * Move the client at the I/O handle level.
     */
    Fsio_MigrateClient(&devHandlePtr->clientList, migInfoPtr->srcClientID,
			dstClientID, migInfoPtr->flags, closeSrcClient);

    *sizePtr = 0;
    *dataPtr = (Address)NIL;
    *flagsPtr = migInfoPtr->flags;
    *offsetPtr = migInfoPtr->offset;
    /*
     * We don't need this reference on the I/O handle; there is no change.
     */
    Fsutil_HandleRelease(devHandlePtr, TRUE);
    return(SUCCESS);
#endif /* SPRITED_MIGRATION */
}

/*
 * ----------------------------------------------------------------------------
 *
 * Fsio_DeviceMigOpen --
 *
 *	Complete setup of a FS_DEVICE_STREAM after migration to the I/O server.
 *	The migrate routine has done the work of shifting use counts
 *	at the stream and I/O handle level.  This routine's job is
 *	to increment the low level I/O handle reference count to reflect
 *	the existence of a new stream to the I/O handle.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 *
 */
/*ARGSUSED*/
ReturnStatus
Fsio_DeviceMigOpen(migInfoPtr, size, data, hdrPtrPtr)
    Fsio_MigInfo	*migInfoPtr;	/* Migration state */
    int		size;		/* Zero */
    ClientData	data;		/* NIL */
    Fs_HandleHeader **hdrPtrPtr;	/* Return - handle for the file */
{
    register Fsio_DeviceIOHandle *devHandlePtr;

    devHandlePtr = Fsutil_HandleFetchType(Fsio_DeviceIOHandle, &migInfoPtr->ioFileID);
    if (devHandlePtr == (Fsio_DeviceIOHandle *)NIL) {
	panic( "Fsio_DeviceMigOpen, no handlel\n");
	return(FAILURE);
    } else {
	Fsutil_HandleUnlock(devHandlePtr);
	*hdrPtrPtr = (Fs_HandleHeader *)devHandlePtr;
	return(SUCCESS);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceRead --
 *
 *      Read on a stream connected to a local peripheral device.
 *	This branches to the driver routine after setting up buffers.
 *	This is called from Fs_Read and from Fsrmt_RpcRead.
 *
 * Results:
 *	SUCCESS unless there was an address error or I/O error.
 *
 * Side effects:
 *	Read the device.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fsio_DeviceRead(streamPtr, readPtr, remoteWaitPtr, replyPtr)
    Fs_Stream		*streamPtr;	/* Open stream to the device. */
    Fs_IOParam		*readPtr;	/* Read parameter block. */
    Sync_RemoteWaiter	*remoteWaitPtr;	/* Process info for remote waiting */
    Fs_IOReply		*replyPtr;	/* Signal to return, if any,
					 * plus the amount read. */
{
    register Fsio_DeviceIOHandle	*devHandlePtr =
	    (Fsio_DeviceIOHandle *)streamPtr->ioHandlePtr;
    register ReturnStatus status;
    register Fs_Device	*devicePtr;
    int	     flags;
    Address userBuffer;
    Boolean copy;


    userBuffer = (Address) NIL;
    flags = devHandlePtr->flags;
    /*
     * Don't lock if the device driver informed us upon open that 
     * it doesn't want it.
     */
    if (!(flags & FS_DEV_DONT_LOCK)) { 
	Fsutil_HandleLock(devHandlePtr);
    }
    /*
     * Because the read could take a while and we aren't mapping in
     * buffers, we have to allocate an extra buffer here so the
     * buffer address is valid when the device's interrupt handler
     * does its DMA. Don't do this malloc and copy if the device
     * driver said it would handle it.
     */
    copy = (readPtr->flags & FS_USER) && !(flags & FS_DEV_DONT_COPY);
    if (copy) {
	userBuffer = readPtr->buffer;
	readPtr->buffer = (Address)ckalloc(readPtr->length);
    }

    /*
     * Put the process onto the read-wait list before attempting the read.
     * This is to prevent races with the device's notification which
     * happens from an interrupt handler.
     */
    Fsutil_WaitListInsert(&devHandlePtr->readWaitList, remoteWaitPtr);
    devicePtr = &devHandlePtr->device;
    status = (*devFsOpTable[DEV_TYPE_INDEX(devicePtr->type)].read)(devicePtr,
		readPtr, replyPtr);
    if (copy) {
        if (Vm_CopyOut(replyPtr->length, readPtr->buffer, userBuffer) != SUCCESS) {
	    if (status == SUCCESS) {
		status = SYS_ARG_NOACCESS;
	    }
	}
	ckfree(readPtr->buffer);
	readPtr->buffer = userBuffer;
    }
    if (status != FS_WOULD_BLOCK) {
	Fsutil_WaitListRemove(&devHandlePtr->readWaitList, remoteWaitPtr);
    }
    devHandlePtr->accessTime = Fsutil_TimeInSeconds();
    fs_Stats.gen.deviceBytesRead += replyPtr->length;
#ifdef SOSP91
    if (proc_RunningProcesses[0] != (Proc_ControlBlock *) NIL) {
	if ((proc_RunningProcesses[0]->state == PROC_MIGRATED) ||
		(proc_RunningProcesses[0]->genFlags &
		(PROC_FOREIGN | PROC_MIGRATING))) {
	    fs_SospMigStats.gen.deviceBytesRead += replyPtr->length; 
	}
    }
#endif SOSP91
    if (!(flags & FS_DEV_DONT_LOCK)) { 
	Fsutil_HandleUnlock(devHandlePtr);
    }
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceWrite --
 *
 *      Write on a stream connected to a local peripheral device.
 *	This is called from Fs_Write and Fsrmt_RpcWrite.
 *
 * Results:
 *	SUCCESS unless there was an address error or I/O error.
 *
 * Side effects:
 *	Write to the device.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fsio_DeviceWrite(streamPtr, writePtr, remoteWaitPtr, replyPtr)
    Fs_Stream		*streamPtr;	/* Open stream to the device. */
    Fs_IOParam		*writePtr;	/* Read parameter block */
    Sync_RemoteWaiter	*remoteWaitPtr;	/* Process info for remote waiting */
    Fs_IOReply		*replyPtr;	/* Signal to return, if any */
{
    register Fsio_DeviceIOHandle	*devHandlePtr =
	    (Fsio_DeviceIOHandle *)streamPtr->ioHandlePtr;
    ReturnStatus	status = SUCCESS;
    register Fs_Device	*devicePtr = &devHandlePtr->device;
    Address		userBuffer;
    int			flags;
    Boolean		copy;

    userBuffer = (Address) NIL;
    flags = devHandlePtr->flags;
    /*
     * Don't lock if the device driver informed us upon open that 
     * it doesn't want it.
     */
    if (!(flags & FS_DEV_DONT_LOCK)) { 
	Fsutil_HandleLock(devHandlePtr);
    }
    /*
     * Because the write could take a while and we aren't mapping in
     * buffers, we have to allocate an extra buffer here so the
     * buffer address is valid when the device's interrupt handler
     * does its DMA. Don't do this malloc and copy if the device
     * driver said it would handle it.
     */
    copy = ((writePtr->flags & FS_USER) && !(flags & FS_DEV_DONT_COPY));
    if (copy) {
	userBuffer = writePtr->buffer;
        writePtr->buffer = (Address)ckalloc(writePtr->length);
	if (Vm_CopyIn(writePtr->length, userBuffer, writePtr->buffer) != SUCCESS) {
	    status = SYS_ARG_NOACCESS;
	}
    }
    if (status == SUCCESS) {
	/*
	 * Put the process onto the write-wait list before attempting the write.
	 * This is to prevent races with the device's notification which
	 * happens from an interrupt handler.
	 */
	Fsutil_WaitListInsert(&devHandlePtr->writeWaitList, remoteWaitPtr);
	status = (*devFsOpTable[DEV_TYPE_INDEX(devicePtr->type)].write)(devicePtr,
		writePtr, replyPtr);
	if (status != FS_WOULD_BLOCK) {
	    Fsutil_WaitListRemove(&devHandlePtr->writeWaitList, remoteWaitPtr);
	}
	devHandlePtr->modifyTime = Fsutil_TimeInSeconds();
	fs_Stats.gen.deviceBytesWritten += replyPtr->length;
#ifdef SOSP91
	if (proc_RunningProcesses[0] != (Proc_ControlBlock *) NIL) {
	    if ((proc_RunningProcesses[0]->state == PROC_MIGRATED) ||
		    (proc_RunningProcesses[0]->genFlags &
		    (PROC_FOREIGN | PROC_MIGRATING))) {
		fs_SospMigStats.gen.deviceBytesWritten += replyPtr->length; 
	    }
	}
#endif SOSP91
    }

    if (copy) {
	ckfree(writePtr->buffer);
	writePtr->buffer = userBuffer;
    }
    if (!(flags & FS_DEV_DONT_LOCK)) { 
	Fsutil_HandleUnlock(devHandlePtr);
    }
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceSelect --
 *
 *      Select on a stream connected to a local peripheral device.  This
 *	ensures that the calling process is on a waiting list, then calls
 *	the device driver's select routine.  If the select succeeds, then
 *	the wait list items are removed.  The ordering of this is done to
 *	prevent races between the select routine and the notification that
 *	occurs at interrupt time.
 *
 * Results:
 *	A return from the driver, should be SUCCESS unless the
 *	device is offline or something.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fsio_DeviceSelect(hdrPtr, waitPtr, readPtr, writePtr, exceptPtr)
    Fs_HandleHeader	*hdrPtr;	/* Handle on device to select */
    Sync_RemoteWaiter	*waitPtr;	/* Process info for remote waiting */
    int 		*readPtr;	/* Bit to clear if non-readable */
    int 		*writePtr;	/* Bit to clear if non-writeable */
    int 		*exceptPtr;	/* Bit to clear if non-exceptable */
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)hdrPtr;
    register Fs_Device	*devicePtr = &devHandlePtr->device;
    register ReturnStatus status;

    Fsutil_HandleLock(devHandlePtr);
    if (waitPtr != (Sync_RemoteWaiter *)NIL) {
	if (*readPtr) {
	    Fsutil_WaitListInsert(&devHandlePtr->readWaitList, waitPtr);
	}
	if (*writePtr) {
	    Fsutil_WaitListInsert(&devHandlePtr->writeWaitList, waitPtr);
	}
	if (*exceptPtr) {
	    Fsutil_WaitListInsert(&devHandlePtr->exceptWaitList, waitPtr);
	}
    }
    status = (*devFsOpTable[DEV_TYPE_INDEX(devicePtr->type)].select)(devicePtr,
		    readPtr, writePtr, exceptPtr);

    if (waitPtr != (Sync_RemoteWaiter *)NIL) {
	if (*readPtr != 0) {
	    Fsutil_WaitListRemove(&devHandlePtr->readWaitList, waitPtr);
	}
	if (*writePtr != 0) {
	    Fsutil_WaitListRemove(&devHandlePtr->writeWaitList, waitPtr);
	}
	if (*exceptPtr != 0) {
	    Fsutil_WaitListRemove(&devHandlePtr->exceptWaitList, waitPtr);
	}
    }
    Fsutil_HandleUnlock(devHandlePtr);
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceIOControl --
 *
 *      Write on a stream connected to a peripheral device.  Called from
 *	FsDomainWrite.
 *
 * Results:
 *	SUCCESS unless there was an address error or I/O error.
 *
 * Side effects:
 *	Write to the device.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fsio_DeviceIOControl(streamPtr, ioctlPtr, replyPtr)
    Fs_Stream *streamPtr;		/* Stream to a device. */
    Fs_IOCParam *ioctlPtr;		/* I/O Control parameter block */
    Fs_IOReply *replyPtr;		/* Return length and signal */
{
    register Fsio_DeviceIOHandle *devHandlePtr =
	    (Fsio_DeviceIOHandle *)streamPtr->ioHandlePtr;
    register Fs_Device	*devicePtr = &devHandlePtr->device;
    register ReturnStatus status = SUCCESS;

    switch (ioctlPtr->command) {
	case IOC_LOCK:
	case IOC_UNLOCK:
	    Fsutil_HandleLock(devHandlePtr);
	    status = Fsio_IocLock(&devHandlePtr->lock, ioctlPtr,
			&streamPtr->hdr.fileID);
	    Fsutil_HandleUnlock(devHandlePtr);
	    break;
	case IOC_PREFIX:
	    break;
	default:
	    if (!(devHandlePtr->flags & FS_DEV_DONT_LOCK)) { 
		Fsutil_HandleLock(devHandlePtr);
	    }
	    status = (*devFsOpTable[DEV_TYPE_INDEX(devicePtr->type)].ioctl)
		    (devicePtr, ioctlPtr, replyPtr);
	    if (!(devHandlePtr->flags & FS_DEV_DONT_LOCK)) { 
		Fsutil_HandleUnlock(devHandlePtr);
	    }
	    break;
    }
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceGetIOAttr --
 *
 *      Get the I/O attributes for a device.  A copy of the access and
 *	modify times are kept at the I/O server.  This routine is called
 *	either from Fs_GetAttrStream or Fsrmt_RpcGetIOAttr to update
 *	the initial copy of the attributes obtained from the name server.
 *
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
Fsio_DeviceGetIOAttr(fileIDPtr, clientID, attrPtr)
    Fs_FileID			*fileIDPtr;	/* FileID of device */
    int 			clientID;	/* IGNORED */
    register Fs_Attributes	*attrPtr;	/* Attributes to update */
{
    register Fsio_DeviceIOHandle *devHandlePtr;

    devHandlePtr = Fsutil_HandleFetchType(Fsio_DeviceIOHandle, fileIDPtr);
    if (devHandlePtr != (Fsio_DeviceIOHandle *)NIL) {
	if (devHandlePtr->accessTime > attrPtr->accessTime.seconds) {
	    attrPtr->accessTime.seconds = devHandlePtr->accessTime;
	}
	if (devHandlePtr->modifyTime > attrPtr->dataModifyTime.seconds) {
	    attrPtr->dataModifyTime.seconds = devHandlePtr->modifyTime;
	}
	Fsutil_HandleRelease(devHandlePtr, TRUE);
    }
    return(SUCCESS);
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceSetIOAttr --
 *
 *      Set the I/O attributes for a device.  A copy of the access and
 *	modify times are kept at the I/O server.  This routine is called
 *	either from Fs_SetAttrStream or Fsrmt_RpcSetIOAttr to update
 *	the cached copy of the attributes.
 *
 * Results:
 *	SUCCESS.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
Fsio_DeviceSetIOAttr(fileIDPtr, attrPtr, flags)
    Fs_FileID			*fileIDPtr;	/* FileID of device */
    register Fs_Attributes	*attrPtr;	/* Attributes to copy */
    int				flags;		/* What attrs to set */
{
    register Fsio_DeviceIOHandle *devHandlePtr;

    if (flags & FS_SET_TIMES) {
	devHandlePtr = Fsutil_HandleFetchType(Fsio_DeviceIOHandle, fileIDPtr);
	if (devHandlePtr != (Fsio_DeviceIOHandle *)NIL) {
	    devHandlePtr->accessTime = attrPtr->accessTime.seconds;
	    devHandlePtr->modifyTime = attrPtr->dataModifyTime.seconds;
	    Fsutil_HandleRelease(devHandlePtr, TRUE);
	}
    }
    return(SUCCESS);
}


/*
 *----------------------------------------------------------------------
 *
 * Fsio_DevNotifyReader --
 *
 *	Fsio_DevNotifyReader is available to device driver interrupt handlers
 *	that need to notify waiting processes that the device is readable.
 *	It schedules a process level call to ReadNotify, which
 *	in turn iterates down the list of handles for the device waking up
 *	all read waiters.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Schedules a call to DevListNotify, which in turn calls
 *	Fsutil_WaitListNotify to schedule any waiting readers.
 *
 *----------------------------------------------------------------------
 */
void
Fsio_DevNotifyReader(notifyToken)
    Fs_NotifyToken notifyToken;
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)notifyToken;

    if ((devHandlePtr == (Fsio_DeviceIOHandle *)NIL) ||
	(devHandlePtr->notifyFlags & FS_READABLE)) {
	return;
    }
    if (devHandlePtr->hdr.fileID.type != FSIO_LCL_DEVICE_STREAM) {
	printf("Fsio_DevNotifyReader, bad handle\n");
    }
    devHandlePtr->notifyFlags |= FS_READABLE;
    Proc_CallFunc(ReadNotify, (ClientData) devHandlePtr, time_ZeroSeconds);
}

static void
ReadNotify(data, callInfoPtr)
    ClientData		data;
    Proc_CallInfo	*callInfoPtr;
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)data;
    if (devHandlePtr->hdr.fileID.type != FSIO_LCL_DEVICE_STREAM) {
	printf("ReadNotify, lost device handle\n");
    } else {
	devHandlePtr->notifyFlags &= ~FS_READABLE;
	Fsutil_WaitListNotify(&devHandlePtr->readWaitList);
    }
    callInfoPtr->interval = time_ZeroSeconds;
}


/*
 *----------------------------------------------------------------------
 *
 * Fsio_DevNotifyWriter --
 *
 *	Fsio_DevNotifyWriter is available to device driver interrupt handlers
 *	that need to notify waiting processes that the device is writeable.
 *	It schedules a process level call to Fs_WaitListNotifyStub on the
 *	devices's write wait list.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Schedules a call to Fs_WaitListNotifyStub.
 *
 *----------------------------------------------------------------------
 */
void
Fsio_DevNotifyWriter(notifyToken)
    Fs_NotifyToken notifyToken;
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)notifyToken;

    if ((devHandlePtr == (Fsio_DeviceIOHandle *)NIL) ||
	(devHandlePtr->notifyFlags & FS_WRITABLE)) {
	return;
    }
    if (devHandlePtr->hdr.fileID.type != FSIO_LCL_DEVICE_STREAM) {
	printf("Fsio_DevNotifyWriter, bad handle\n");
	return;
    }
    devHandlePtr->notifyFlags |= FS_WRITABLE;
    Proc_CallFunc(WriteNotify, (ClientData) devHandlePtr, time_ZeroSeconds);
}

static void
WriteNotify(data, callInfoPtr)
    ClientData		data;
    Proc_CallInfo	*callInfoPtr;
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)data;
    if (devHandlePtr->hdr.fileID.type != FSIO_LCL_DEVICE_STREAM) {
	printf( "WriteNotify, lost device handle\n");
    } else {
	devHandlePtr->notifyFlags &= ~FS_WRITABLE;
	Fsutil_WaitListNotify(&devHandlePtr->writeWaitList);
    }
    callInfoPtr->interval = time_ZeroSeconds;
}



/*
 *----------------------------------------------------------------------
 *
 * Fsio_DevNotifyException --
 *
 *	This is available to device driver interrupt handlers
 *	that need to notify waiting processes that there is an exception
 *	on the device.  This is only useful for processes waiting on
 *	exceptions in select.  This is not currently used.
 *	It schedules a process level call to Fs_WaitListNotifyStub on the
 *	devices's exception wait list.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Schedules a call to Fs_WaitListNotifyStub.
 *
 *----------------------------------------------------------------------
 */
void
Fsio_DevNotifyException(notifyToken)
    Fs_NotifyToken notifyToken;
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)notifyToken;

    if (devHandlePtr == (Fsio_DeviceIOHandle *)NIL) {
	return;
    }
    Proc_CallFunc(ExceptionNotify, (ClientData) devHandlePtr,
		  time_ZeroSeconds);
}

static void
ExceptionNotify(data, callInfoPtr)
    ClientData		data;
    Proc_CallInfo	*callInfoPtr;
{
    register Fsio_DeviceIOHandle *devHandlePtr = (Fsio_DeviceIOHandle *)data;
    Fsutil_WaitListNotify(&devHandlePtr->exceptWaitList);
    callInfoPtr->interval = time_ZeroSeconds;
}


/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceRecovTestUseCount --
 *
 *	For recovery testing, return the use count of the io handle.	
 *
 * Results:
 *	The use count.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
int
Fsio_DeviceRecovTestUseCount(handlePtr)
    Fsio_DeviceIOHandle *handlePtr;
{
    return handlePtr->use.ref;
}

/*
 *----------------------------------------------------------------------
 *
 * Fsio_BootTimeTtyOpen --
 *
 *	This is a stripped down version of Fsio_DeviceIoOpen
 *	that is used to invoke the tty driver open routine
 *	at boot time.  The important thing to get right is
 *	that the tty driver has to be passed the real FS
 *	I/O handle for the device so notifications work.
 *
 * Results:
 *	SUCCESS or a device dependent open error code.
 *
 * Side effects:
 *	Sets up and installs the console's ioHandle.  The tty open
 *	routine is called so L1/break/F1 key processing for
 *	Dev_InvokeConsole command is enabled.
 *	The associated FS handle is unlocked
 *	upon exit, but its reference count has been incremented.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fsio_BootTimeTtyOpen()
{
    ReturnStatus 	status;
    Fs_FileID		ttyFileID;
    Fsio_DeviceIOHandle	*devHandlePtr;

    /*
     * Set up the console's fileID.  This sets the console
     * unit number to 0, although this is cleverly mapped by the
     * ttyDriver to a serial line if the EEPROM is properly configured.
     */
    ttyFileID.type = FSIO_LCL_DEVICE_STREAM;
    ttyFileID.serverID = rpc_SpriteID;
    ttyFileID.major = DEV_TERM;
    ttyFileID.minor = 0;
    (void)FsioDeviceHandleInit(&ttyFileID, "/dev/console", &devHandlePtr);
    /*
     * The device driver open routine gets the device specification,
     * the useFlags, and a token passed to Fs_NotifyReader
     * or Fs_NotifyWriter when the device becomes ready for I/O.
     */
    if (DEV_TYPE_INDEX(devHandlePtr->device.type) >= devNumDevices) {
	status = FS_DEVICE_OP_INVALID;
    } else {
	int flags = 0;
/* XXX */ printf("Fsio_BootTimeTtyOpen: spriteID %d devHandle <%x>\n",
			rpc_SpriteID, devHandlePtr);
	status = (*devFsOpTable[DEV_TYPE_INDEX(devHandlePtr->device.type)].open)
		    (&devHandlePtr->device, FS_READ|FS_WRITE, 
		      (Fs_NotifyToken)devHandlePtr, &flags);
    }
    /*
     * Unlock the handle.  This leaves an extra reference just
     * to make sure the file system handle doesn't get scavenged.
     */
    Fsutil_HandleUnlock(devHandlePtr);
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * Fsio_DeviceMmap --
 *
 *	Do a device-specific mmap operation.
 *
 * Results:
 *	SUCCESS or FAILURE.
 *
 * Side effects:
 *	Kernel memory mapped into user space.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fsio_DeviceMmap(streamPtr, startAddr, length, offset, newAddrPtr)
    Fs_Stream		*streamPtr;	/* Stream to device. */
    Address		startAddr;	/* Requested starting virt. addr. */
    int			length;		/* Length of mapped segment. */
    int			offset;		/* Offset into mapped file. */
    Address		*newAddrPtr;
{
    Fs_Device		*devicePtr;
    Fsio_DeviceIOHandle	*ioHandlePtr;
    ReturnStatus	status;

    if (streamPtr->ioHandlePtr->fileID.type != FSIO_LCL_DEVICE_STREAM) {
	printf("Fsio_DeviceMmap passed something that wasn't a device.\n");
	return FAILURE;
    }
    ioHandlePtr = (Fsio_DeviceIOHandle *) streamPtr->ioHandlePtr;
    devicePtr = &(ioHandlePtr->device);
    if (DEV_TYPE_INDEX(devicePtr->type) >= devNumDevices) {
	return  FS_DEVICE_OP_INVALID;
    }

    status = (*devFsOpTable[DEV_TYPE_INDEX(devicePtr->type)].mmap)
		(devicePtr, startAddr, length, offset, newAddrPtr);

    return status;
}

