/*
 * Copyright (c) 1999 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute 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 INSTITUTE 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 INSTITUTE 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.
 */


#define INITGUID /* XXX why */

#include "xfs_locl.h"

#define XFS_FS_NAME	L"\\Device\\XFS"


struct xfs_channel XFSGlobalData;

/*
 * Add a new device
 */

static NTSTATUS
xfs_adddevice(PDRIVER_OBJECT driver,
	      PDEVICE_OBJECT PhysicalDeviceObject)
{
    int 		RC;
    UNICODE_STRING	devname;


    /* Create device */
    RtlInitUnicodeString(&devname, XFS_FS_NAME);
    RC = IoCreateDevice(driver,
			0,
			&devname,
			FILE_DEVICE_NETWORK_FILE_SYSTEM,
			0,
			FALSE,
			&XFSGlobalData.DeviceObject);


    if (!NT_SUCCESS (RC)) {
	xfs_debug (XDEBLKM, "IoCreateDevice failed with %d\n", (int)RC);
	return RC;
    }

    /*
     * Init message queue
     */

    xfs_initq (&XFSGlobalData.messageq);
    xfs_initq (&XFSGlobalData.sleepq);

    /*
     * Init nodes
     */

    XFSGlobalData.nodes.next = &XFSGlobalData.nodes;
    XFSGlobalData.nodes.prev = &XFSGlobalData.nodes;

    /*
     * Tell io manager that we have initialized.
     */

    XFSGlobalData.DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    IoRegisterFileSystem (XFSGlobalData.DeviceObject);

#if 0
    RC = IoRegisterShutdownNotification(XFSGlobalData.DeviceObject);
    if (!NT_SUCCESS(RC))
	try_return (RC);
    RegShutdown = TRUE;
#endif

    xfs_debug (XDEBLKM, "IoCreateDevice done\n");

    return RC;
}


/*
 *
 */

NTSTATUS
xfs_start_device(PDEVICE_OBJECT driver, PIRP irp)
{
    xfs_debug (XDEBLKM, "xfs_start_device\n");
    IoCompleteRequest (irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

/*
 *
 */

NTSTATUS
xfs_remove_device(PDEVICE_OBJECT driver, PIRP irp)
{
    xfs_debug (XDEBLKM, "xfs_remove_device\n");
    IoCompleteRequest (irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

/*
 *
 */

static NTSTATUS
xfs_pnp(PDEVICE_OBJECT driver, PIRP irp)
{
    IO_STACK_LOCATION *io_stack;
    int RC;

    xfs_debug (XDEBLKM, "xfs_pnp\n");

    io_stack = IoGetCurrentIrpStackLocation(irp);
    ASSERT(io_stack);

    switch (io_stack->MinorFunction) {
    case IRP_MN_START_DEVICE:
	RC = xfs_start_device (driver, irp);
	break;
    case IRP_MN_REMOVE_DEVICE:
	RC = xfs_remove_device (driver, irp);
	break;
    case IRP_MN_DEVICE_USAGE_NOTIFICATION:
    default:
	IoCompleteRequest (irp, IO_NO_INCREMENT);
	RC = STATUS_SUCCESS;
    }
    xfs_debug (XDEBLKM, "xfs_pnp: returns %d\n", (int)RC);

    return RC;
}

/*
 *
 */

static NTSTATUS
xfs_power(PDEVICE_OBJECT driver, PIRP irp)
{
    UNREFERENCED_PARAMETER(driver);

    IoCompleteRequest(irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

/*
 * 
 */

VOID
xfs_unload(PDRIVER_OBJECT DriverObject)
{
#if 0
    if (RegShutdown)
	IoUnregisterShutdownNotification(XFSGlobalData.DeviceObject);
#endif

    /* delete device */
    if (XFSGlobalData.DeviceObject) {
	IoDeleteDevice(XFSGlobalData.DeviceObject);
	XFSGlobalData.DeviceObject = NULL;
    }

    /* FreeDNLC & co*/

    if ( XFS_TESTFLAGS (XFSGlobalData.flags, XFSCHAN_FLAGS_GLOBALLOCK))
	ExDeleteResourceLite(&XFSGlobalData.GlobalLock);

    xfs_log(XFSGlobalData.DeviceObject, 4711,
	    STATUS_SUCCESS, STATUS_NO_MEDIA_IN_DEVICE);

    return;
}

/*
 * Set up the IRP table for `driver'
 */

static void
InitDevice (PDRIVER_OBJECT driver)
{
#define PRINT_FUNC_ADDR(x) \
    xfs_debug(XDEBLKM, #x " %08x\n", x)

    PRINT_FUNC_ADDR (xfs_create);
    PRINT_FUNC_ADDR (xfs_close);
    PRINT_FUNC_ADDR (xfs_read);
    PRINT_FUNC_ADDR (xfs_write);
    PRINT_FUNC_ADDR (xfs_fileinfo);
    PRINT_FUNC_ADDR (xfs_flush);
    PRINT_FUNC_ADDR (xfs_dirctl);
    PRINT_FUNC_ADDR (xfs_devctl);
    PRINT_FUNC_ADDR (xfs_shutdown);
    PRINT_FUNC_ADDR (xfs_cleanup);
    PRINT_FUNC_ADDR (xfs_queryvol);
    PRINT_FUNC_ADDR (xfs_fscontrol);

#undef PRINT_FUNC_ADDR

    driver->MajorFunction[IRP_MJ_CREATE]		= xfs_create;
    driver->MajorFunction[IRP_MJ_CLOSE]			= xfs_close;
    driver->MajorFunction[IRP_MJ_READ]			= xfs_read;
    driver->MajorFunction[IRP_MJ_WRITE]			= xfs_write;
    driver->MajorFunction[IRP_MJ_QUERY_INFORMATION]	= xfs_fileinfo;
    driver->MajorFunction[IRP_MJ_SET_INFORMATION]	= xfs_fileinfo;


    driver->MajorFunction[IRP_MJ_FLUSH_BUFFERS]		= xfs_flush;
    driver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL]	= xfs_dirctl;

    driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]	= xfs_devctl;
    driver->MajorFunction[IRP_MJ_SHUTDOWN]		= xfs_shutdown;

    driver->MajorFunction[IRP_MJ_CLEANUP]		= xfs_cleanup;

    driver->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = xfs_queryvol;

    driver->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] 	= xfs_fscontrol;

#if 0
    driver->MajorFunction[IRP_MJ_PNP]			= xfs_pnp;
    driver->MajorFunction[IRP_MJ_POWER]			= xfs_power;
    driver->DriverExtension->AddDevice            	= xfs_adddevice;
#endif
    driver->DriverUnload                          	= xfs_unload;


    /* not implemented yet ... */

#if 0

    /* volume junk */
    driver->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = foo;


    /* byte range locks */
    // driver->MajorFunction[IRP_MJ_LOCK_CONTROL]		= LockControl;

    /* extended security */
    driver->MajorFunction[IRP_MJ_QUERY_SECURITY]		= Security;
    driver->MajorFunction[IRP_MJ_SET_SECURITY]		= Security;

    /* extended attributes */
    driver->MajorFunction[IRP_MJ_QUERY_EA]				= ExtendedAttr;
    driver->MajorFunction[IRP_MJ_SET_EA]				= ExtendedAttr;

    /*  fast-io */
    PtrFastIoDispatch = driver->FastIoDispatch = &(GlobalData.FastIoDispatch);

    PtrFastIoDispatch->SizeOfFastIoDispatch	= sizeof(FAST_IO_DISPATCH);
    PtrFastIoDispatch->FastIoCheckIfPossible	= FastIoCheckIfPossible;
    PtrFastIoDispatch->FastIoRead					= FastIoRead;
    PtrFastIoDispatch->FastIoWrite				= FastIoWrite;
    PtrFastIoDispatch->FastIoQueryBasicInfo	= FastIoQueryBasicInfo;
    PtrFastIoDispatch->FastIoQueryStandardInfo	= FastIoQueryStdInfo;
    PtrFastIoDispatch->FastIoLock					= FastIoLock;
    PtrFastIoDispatch->FastIoUnlockSingle		= FastIoUnlockSingle;
    PtrFastIoDispatch->FastIoUnlockAll			= FastIoUnlockAll;
    PtrFastIoDispatch->FastIoUnlockAllByKey	= FastIoUnlockAllByKey;
    PtrFastIoDispatch->AcquireFileForNtCreateSection = FastIoAcqCreateSec;
    PtrFastIoDispatch->ReleaseFileForNtCreateSection = FastIoRelCreateSec;

#if(_WIN32_WINNT >= 0x0400)
    PtrFastIoDispatch->FastIoQueryNetworkOpenInfo = FastIoQueryNetInfo;
    PtrFastIoDispatch->AcquireForModWrite		= FastIoAcqModWrite;
    PtrFastIoDispatch->ReleaseForModWrite		= FastIoRelModWrite;
    PtrFastIoDispatch->AcquireForCcFlush		= FastIoAcqCcFlush;
    PtrFastIoDispatch->ReleaseForCcFlush		= FastIoRelCcFlush;

    // MDL functionality
    PtrFastIoDispatch->MdlRead						= FastIoMdlRead;
    PtrFastIoDispatch->MdlReadComplete			= FastIoMdlReadComplete;
    PtrFastIoDispatch->PrepareMdlWrite			= FastIoPrepareMdlWrite;
    PtrFastIoDispatch->MdlWriteComplete			= FastIoMdlWriteComplete;

    // although this FSD does not support compressed read/write functionality,
    //	NTFS does, and if you design a FSD that can provide such functionality,
    //	you should consider initializing the fast io entry points for reading
    //	and/or writing compressed data ...
#endif	// (_WIN32_WINNT >= 0x0400)

    // last but not least, initialize the Cache Manager callback functions
    //	which are used in CcInitializeCacheMap()
    GlobalData.CacheMgrCallBacks.AcquireForLazyWrite = AcqLazyWrite;
    GlobalData.CacheMgrCallBacks.ReleaseFromLazyWrite = RelLazyWrite;
    GlobalData.CacheMgrCallBacks.AcquireForReadAhead = AcqReadAhead;
    GlobalData.CacheMgrCallBacks.ReleaseFromReadAhead = RelReadAhead;
#endif    

    return;
}

/*
 * Allocate the zone's for Node and CCB
 */

#define DEFAULT_ZONE_SIZE 10


static NTSTATUS
InitFS (void)
{
    NTSTATUS				RC = STATUS_SUCCESS;
    u_int32_t ccbzone_sz = 
	(4 * DEFAULT_ZONE_SIZE * AlignPointer(sizeof(XFS_CCB))) +
	sizeof(ZONE_SEGMENT_HEADER);
    u_int32_t nodezone_sz = 
	(4 * DEFAULT_ZONE_SIZE * AlignPointer(sizeof(struct xfs_node))) +
	sizeof(ZONE_SEGMENT_HEADER);
    u_int32_t linkzone_sz = 
	(4 * DEFAULT_ZONE_SIZE * AlignPointer(sizeof(struct xfs_link))) +
	sizeof(ZONE_SEGMENT_HEADER);

    /* XXX MmQuerySystemSize() and MmIsThisAnNtAsSystem() */

    KeInitializeSpinLock(&XFSGlobalData.ZoneAllocationSpinLock);

    XFSGlobalData.CCBZone = ExAllocatePool(NonPagedPool, ccbzone_sz);
    if (XFSGlobalData.CCBZone == NULL) {
	xfs_debug (XDEBLKM, "ExAllocatePool failed for CCBZone\n");
	RC = STATUS_INSUFFICIENT_RESOURCES;
	return RC;
    }
    XFSGlobalData.NodeZone = ExAllocatePool(NonPagedPool, nodezone_sz);
    if (XFSGlobalData.NodeZone == NULL) {
	xfs_debug (XDEBLKM, "ExAllocatePool failed for NodeZone\n");
	RC = STATUS_INSUFFICIENT_RESOURCES;
	return RC;
    }
    XFSGlobalData.LinkZone = ExAllocatePool(NonPagedPool, linkzone_sz);
    if (XFSGlobalData.LinkZone == NULL) {
	xfs_debug (XDEBLKM, "ExAllocatePool failed for LinkZone\n");
	RC = STATUS_INSUFFICIENT_RESOURCES;
	return RC;
    }

    RC = ExInitializeZone(&(XFSGlobalData.CCBZoneHeader),
			  AlignPointer(sizeof(XFS_CCB)),
			  XFSGlobalData.CCBZone,
			  ccbzone_sz);

    if (!NT_SUCCESS (RC)) {
	xfs_debug (XDEBLKM,
		   "ExInitializeZone failed for CCBZone with %d\n", RC);
	return RC;
    }
    RC = ExInitializeZone(&(XFSGlobalData.NodeZoneHeader),
			  AlignPointer(sizeof(struct xfs_node)),
			  XFSGlobalData.NodeZone,
			  nodezone_sz);

    if (!NT_SUCCESS (RC)) {
	xfs_debug (XDEBLKM,
		   "ExInitializeZone failed for NodeZone with %d\n", RC);
	return RC;
    }
    RC = ExInitializeZone(&(XFSGlobalData.LinkZoneHeader),
			  AlignPointer(sizeof(struct xfs_link)),
			  XFSGlobalData.LinkZone,
			  linkzone_sz);

    if (!NT_SUCCESS (RC)) {
	xfs_debug (XDEBLKM,
		   "ExInitializeZone failed for NodeZone with %d\n", RC);
	return RC;
    }
    return RC;
}


/*
* The entrypoint for the driver
*/


NTSTATUS
DriverEntry(PDRIVER_OBJECT driver,
	    PUNICODE_STRING	RegistryPath)
{
    NTSTATUS		RC = STATUS_SUCCESS;
    BOOLEAN		RegShutdown = FALSE;

    xfs_debug (XDEBLKM, "DriverEntry: Trying to load\n");
   
    RtlZeroMemory(&XFSGlobalData, sizeof(XFSGlobalData));
    XFSGlobalData.magic = XFS_DEV_DATA_MAGIC;
   
    /* init resource */
    RC = ExInitializeResourceLite(&(XFSGlobalData.GlobalLock));
    ASSERT(NT_SUCCESS(RC));
    XFS_SETFLAGS (XFSGlobalData.flags, XFSCHAN_FLAGS_GLOBALLOCK);
   
   
    /* save entry for later use */
    XFSGlobalData.DriverObject = driver;
   
#if 0
    /* init logical volume block structure */
    InitializeListHead(&(XFSGlobalData.NextVCB));
#endif
   
   
    /* Create DNLC
     *        Locking
     */
   
    /* set up function pointer */
    RC = InitFS ();
    if (!NT_SUCCESS(RC))
	try_return(RC);
    InitDevice (driver);
   
    RC = xfs_adddevice (driver, NULL);

    xfs_debug (XDEBLKM, "DriverEntry: ended up with: %d\n", (int) RC);
   
    if (NT_SUCCESS(RC)) {
	XFS_SETFLAGS (XFSGlobalData.flags, XFSCHAN_FLAGS_OPEN);
	return STATUS_SUCCESS;
    }

 try_exit: 
   
    xfs_debug (XDEBLKM, "DriverEntry: bailed out\n");

    xfs_log(XFSGlobalData.DeviceObject, 4711, RC,
	    STATUS_NO_MEDIA_IN_DEVICE);

    xfs_unload (driver);

    return RC;
}

