/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    XIPDisk.c

Abstract:

    This is the XIP Disk driver for Whistler NT/Embedded.

Author:

    DavePr 18-Sep-2000 -- base one NT4 DDK ramdisk by RobertN 10-Mar-1993.

Environment:

    Kernel mode only.

Notes:

Revision History:

--*/


//
// Include files.
//

#include <ntddk.h>
#include "initguid.h"
#include "mountdev.h"

#include <ntdddisk.h>       // Disk device IOCTLs, DiskClassGuid

#include "fat.h"
#include "xip.h"
#include "XIPDisk.h"


//
// ISSUE-2000/10/11-DavePr -- haven't decided how to define DO_XIP appropriately.
//
#ifndef DO_XIP
#define DO_XIP 0x00020000
#endif

#include <string.h>


NTSTATUS
XIPDiskCreateClose(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is called by the I/O system when the XIPDisk is opened or
    closed.

    No action is performed other than completing the request successfully.

Arguments:

    DeviceObject - a pointer to the object that represents the device
    that I/O is to be done on.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    STATUS_INVALID_PARAMETER if parameters are invalid,
    STATUS_SUCCESS otherwise.

--*/

{
    PXIPDISK_EXTENSION    diskExtension = NULL;   // ptr to device extension
    PBIOS_PARAMETER_BLOCK bios;
    NTSTATUS              status;

    diskExtension = DeviceObject->DeviceExtension;
    status = XIPDispatch(XIPCMD_NOOP, NULL, 0);

    if (!NT_SUCCESS(status) || !diskExtension->BootParameters.BasePage) {
        status = STATUS_DEVICE_NOT_READY;
    } else {
        status = STATUS_SUCCESS;
    }

    Irp->IoStatus.Status = status;
    Irp->IoStatus.Information = 0;

    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    return status;
}


NTSTATUS
XIPDiskReadWrite(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    This routine is called by the I/O system to read or write to a
    device that we control.

Arguments:

    DeviceObject - a pointer to the object that represents the device
    that I/O is to be done on.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    STATUS_INVALID_PARAMETER if parameters are invalid,
    STATUS_SUCCESS otherwise.

--*/

{
    PXIPDISK_EXTENSION     diskExtension;
    PIO_STACK_LOCATION     irpSp;
    PUCHAR                 bufferAddress, diskByteAddress;
    PUCHAR                 romPageAddress = NULL;
    ULONG_PTR              ioOffset;
    ULONG                  ioLength;
    NTSTATUS               status;

    PHYSICAL_ADDRESS       physicalAddress;
    ULONG                  mappingSize;

    //
    // Set up necessary object and extension pointers.
    //
    diskExtension = DeviceObject->DeviceExtension;
    irpSp = IoGetCurrentIrpStackLocation(Irp);

    //
    // Check for invalid parameters.  It is an error for the starting offset
    // + length to go past the end of the buffer, or for the offset or length
    // not to be a proper multiple of the sector size.
    //
    // Others are possible, but we don't check them since we trust the
    // file system and they aren't deadly.
    //

    if (irpSp->Parameters.Read.ByteOffset.HighPart) {
        status = STATUS_INVALID_PARAMETER;
        goto done;
    }

    ioOffset = irpSp->Parameters.Read.ByteOffset.LowPart;
    ioLength = irpSp->Parameters.Read.Length;

    if (ioLength == 0) {
        status = STATUS_SUCCESS;
        goto done;
    }

    if (ioOffset + ioLength < ioOffset) {
        status = STATUS_INVALID_PARAMETER;
        goto done;
    }

    if ((ioOffset | ioLength) & (diskExtension->BiosParameters.BytesPerSector - 1)) {
        status = STATUS_INVALID_PARAMETER;
        goto done;

    }

    if ((ioOffset + ioLength) > (diskExtension->BootParameters.PageCount * PAGE_SIZE)) {
        status = STATUS_NONEXISTENT_SECTOR;
        goto done;
    }

    if (irpSp->MajorFunction == IRP_MJ_WRITE && diskExtension->BootParameters.ReadOnly) {
        status = STATUS_MEDIA_WRITE_PROTECTED;
        goto done;
    }

    //
    // Map the pages in the ROM into system space
    //
    mappingSize = ADDRESS_AND_SIZE_TO_SPAN_PAGES (ioOffset, ioLength) * PAGE_SIZE;

    //
    // Get a system-space pointer to the disk region.
    //
    physicalAddress.QuadPart = (diskExtension->BootParameters.BasePage + (ioOffset/PAGE_SIZE)) * PAGE_SIZE;

    romPageAddress = MmMapIoSpace(physicalAddress, mappingSize, MmCached);
    if (! romPageAddress) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto done;
    } 

    diskByteAddress = romPageAddress + (ioOffset & (PAGE_SIZE-1));

    //
    // Get a system-space pointer to the user's buffer.  A system
    // address must be used because we may already have left the
    // original caller's address space.
    //

    Irp->IoStatus.Information = irpSp->Parameters.Read.Length;

    ASSERT (Irp->MdlAddress != NULL);

    bufferAddress = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, NormalPagePriority );

    if (! bufferAddress) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto done;
    }

    status = STATUS_SUCCESS;

    switch (irpSp->MajorFunction) {
    case IRP_MJ_READ:
        RtlCopyMemory( bufferAddress, diskByteAddress, ioLength );
        break;

    case IRP_MJ_WRITE:
        RtlCopyMemory( diskByteAddress, bufferAddress, ioLength );
        break;

    default:
        ASSERT(FALSE);
        status = STATUS_INVALID_PARAMETER;
    }

done:
    if (romPageAddress) {
        MmUnmapIoSpace (romPageAddress, mappingSize);
    }

    Irp->IoStatus.Status = status;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    return status;
}


NTSTATUS
XIPDiskDeviceControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    This routine is called by the I/O system to perform a device I/O
    control function.

Arguments:

    DeviceObject - a pointer to the object that represents the device
        that I/O is to be done on.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    STATUS_SUCCESS if recognized I/O control code,
    STATUS_INVALID_DEVICE_REQUEST otherwise.

--*/

{
    PBIOS_PARAMETER_BLOCK bios;
    PXIPDISK_EXTENSION   diskExtension;
    PIO_STACK_LOCATION   irpSp;
    NTSTATUS             status;
    ULONG                info;

    //
    // Set up necessary object and extension pointers.
    //

    diskExtension = DeviceObject->DeviceExtension;
    bios = &diskExtension->BiosParameters;

    irpSp = IoGetCurrentIrpStackLocation( Irp );

    //
    // Assume failure.
    //
    status = STATUS_INVALID_DEVICE_REQUEST;
    info = 0;

    //
    // Determine which I/O control code was specified.
    //
    switch (irpSp->Parameters.DeviceIoControl.IoControlCode) {
    
    case IOCTL_DISK_GET_MEDIA_TYPES:
    case IOCTL_STORAGE_GET_MEDIA_TYPES:
    case IOCTL_DISK_GET_DRIVE_GEOMETRY:
        //
        // Return the drive geometry for the virtual disk.  Note that
        // we return values which were made up to suit the disk size.
        //

        if (irpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DISK_GEOMETRY)) {

            status = STATUS_INVALID_PARAMETER;
            
        } else {

            PDISK_GEOMETRY outputBuffer;

            outputBuffer = (PDISK_GEOMETRY) Irp->AssociatedIrp.SystemBuffer;

            outputBuffer->MediaType          = FixedMedia;
            outputBuffer->Cylinders.QuadPart = diskExtension->NumberOfCylinders;
            outputBuffer->TracksPerCylinder  = diskExtension->TracksPerCylinder;
            outputBuffer->SectorsPerTrack    = bios->SectorsPerTrack;
            outputBuffer->BytesPerSector     = bios->BytesPerSector;

            status = STATUS_SUCCESS;
            info = sizeof( DISK_GEOMETRY );
        }
        break;

#if 0
    //
    // Ignore these IOCTLs for now.
    //
    case IOCTL_DISK_SET_PARTITION_INFO: 
    case IOCTL_DISK_SET_DRIVE_LAYOUT: 
        status = STATUS_SUCCESS;
        break;
#endif

    case IOCTL_DISK_GET_PARTITION_INFO: 
        //
        // Return the information about the partition.
        //

        if (irpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(PARTITION_INFORMATION)) {

            status = STATUS_INVALID_PARAMETER;

        } else {

            PPARTITION_INFORMATION outputBuffer;
        
            outputBuffer = (PPARTITION_INFORMATION) Irp->AssociatedIrp.SystemBuffer;
        
            //
            // Fat hardwired here...
            //
            outputBuffer->PartitionType =  PARTITION_FAT_16;
            outputBuffer->BootIndicator = diskExtension->BootParameters.SystemDrive;
            outputBuffer->RecognizedPartition = TRUE;
            outputBuffer->RewritePartition = FALSE;
            outputBuffer->StartingOffset.QuadPart = 0;
            outputBuffer->PartitionLength.QuadPart = diskExtension->BootParameters.PageCount * PAGE_SIZE;
            outputBuffer->HiddenSectors =  diskExtension->BiosParameters.HiddenSectors;
        
            status = STATUS_SUCCESS;
            info = sizeof(PARTITION_INFORMATION);
        }
        break;


    case IOCTL_DISK_VERIFY:
        {
            PVERIFY_INFORMATION	verifyInformation;
            ULONG               buflen;
            ULONG_PTR           ioOffset;
            ULONG               ioLength;

            buflen = irpSp->Parameters.DeviceIoControl.InputBufferLength;

            if ( buflen < sizeof(VERIFY_INFORMATION) ) {
                status = STATUS_INVALID_PARAMETER;
                break;
            }

            verifyInformation = Irp->AssociatedIrp.SystemBuffer;

            if (verifyInformation->StartingOffset.HighPart) {
                status = STATUS_DISK_CORRUPT_ERROR;
                break;
            }

            ioOffset = verifyInformation->StartingOffset.LowPart;
            ioLength = verifyInformation->Length;

            if (ioLength == 0) {
                status = STATUS_SUCCESS;

            } else if ((ioOffset | ioLength) & (diskExtension->BiosParameters.BytesPerSector - 1)) {
                status = STATUS_INVALID_PARAMETER;

            } else if ((ioOffset + ioLength) > (diskExtension->BootParameters.PageCount * PAGE_SIZE)) {
                status = STATUS_NONEXISTENT_SECTOR;

            } else {
                status = STATUS_SUCCESS;
            }
            break;
        }

    case IOCTL_DISK_IS_WRITABLE:
        status = diskExtension->BootParameters.ReadOnly? STATUS_MEDIA_WRITE_PROTECTED : STATUS_SUCCESS;
        break;

    case IOCTL_DISK_CHECK_VERIFY:
    case IOCTL_STORAGE_CHECK_VERIFY:
    case IOCTL_STORAGE_CHECK_VERIFY2:
        status = STATUS_SUCCESS;
        break;

    default:
        //
        // The specified I/O control code is unrecognized by this driver.
        // The I/O status field in the IRP has already been set so just
        // terminate the switch.
        //

#if DBG
        DbgPrint("XIPDisk:  ERROR:  unrecognized IOCTL %x\n",
                    irpSp->Parameters.DeviceIoControl.IoControlCode);
#endif
        break;

    case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME:
        {
            PMOUNTDEV_NAME mountName;
            ULONG outlen;

            outlen = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

            if ( outlen < sizeof(MOUNTDEV_NAME) ) {
                status = STATUS_INVALID_PARAMETER;
                break;
            }

            mountName = Irp->AssociatedIrp.SystemBuffer;
            mountName->NameLength = diskExtension->DeviceName.Length;

            if ( outlen < mountName->NameLength + sizeof(WCHAR)) {
                status = STATUS_BUFFER_OVERFLOW;
                info = sizeof(MOUNTDEV_NAME);
                break;
            }

            RtlCopyMemory( mountName->Name, diskExtension->DeviceName.Buffer, mountName->NameLength);

            status = STATUS_SUCCESS;
            info = mountName->NameLength + sizeof(WCHAR);
            break;
        }

    case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID:
        {
            PMOUNTDEV_UNIQUE_ID uniqueId;
            ULONG outlen;

            outlen = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

            if (outlen < sizeof(MOUNTDEV_UNIQUE_ID)) {
                status = STATUS_INVALID_PARAMETER;
                break;
            }

            uniqueId = Irp->AssociatedIrp.SystemBuffer;
            uniqueId->UniqueIdLength = sizeof(XIPDISK_DEVICENAME);

            if (outlen < uniqueId->UniqueIdLength) {
                status = STATUS_BUFFER_OVERFLOW;
                info = sizeof(MOUNTDEV_UNIQUE_ID);
                break;
            }

            RtlCopyMemory( uniqueId->UniqueId, XIPDISK_DEVICENAME, uniqueId->UniqueIdLength );

            status = STATUS_SUCCESS;
            info = uniqueId->UniqueIdLength;
            break;
        }

        case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME:
        {
            status = STATUS_INVALID_DEVICE_REQUEST;
            break;
        }
    }

    //
    // Finish the I/O operation by simply completing the packet and returning
    // the same status as in the packet itself.
    // Note that IoCompleteRequest may deallocate Irp before returning.
    //
    Irp->IoStatus.Information = info;
    Irp->IoStatus.Status = status;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    return status;
}

VOID
XIPDiskUnloadDriver(
    IN PDRIVER_OBJECT DriverObject
    )
/*++

Routine Description:

    This routine is called by the I/O system to unload the driver.

    Any resources previously allocated must be freed.

Arguments:

    DriverObject - a pointer to the object that represents our driver.

Return Value:

    None
--*/

{
    PDEVICE_OBJECT      deviceObject = DriverObject->DeviceObject;
    PXIPDISK_EXTENSION  diskExtension = deviceObject->DeviceExtension;

    RtlFreeUnicodeString(&diskExtension->InterfaceString);
    diskExtension->InterfaceString.Buffer = NULL;

    if (deviceObject != NULL) {
        IoDeleteDevice( deviceObject );
    }
}

NTSTATUS
DriverEntry(
    IN OUT PDRIVER_OBJECT   DriverObject,
    IN     PUNICODE_STRING  RegistryPath
    )
/*++

Routine Description:
    This routine is called by the Operating System to initialize the driver.

Arguments:
    DriverObject - a pointer to a device extension object for the XIPDisk driver.

    RegistryPath - a pointer to our Services key in the registry.

Return Value:
    STATUS_SUCCESS if this disk is initialized; an error otherwise.

--*/

{
    XIP_BOOT_PARAMETERS   xipbootparameters;
    PBIOS_PARAMETER_BLOCK bios;
    NTSTATUS              status;

//  UNICODE_STRING        deviceName;
    UNICODE_STRING        realDeviceName;
    UNICODE_STRING        dosSymlink;
    UNICODE_STRING        driveLetter;

    PDEVICE_OBJECT        pdo = NULL;
    PDEVICE_OBJECT        deviceObject;

    PXIPDISK_EXTENSION    ext = NULL;   // ptr to device extension

    //
    // Read the parameters from the registry
    //
    status = XIPDispatch(XIPCMD_GETBOOTPARAMETERS, &xipbootparameters, sizeof(xipbootparameters));
    if (!NT_SUCCESS(status)) {
        return status;
    }

    if (xipbootparameters.BasePage == 0) {
        return STATUS_NO_SUCH_DEVICE;
    }

    // Initialize the driver object with this driver's entry points.
    //

    DriverObject->MajorFunction[IRP_MJ_CREATE] = XIPDiskCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = XIPDiskCreateClose;
    DriverObject->MajorFunction[IRP_MJ_READ] = XIPDiskReadWrite;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = XIPDiskReadWrite;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = XIPDiskDeviceControl;

    //
    // Create and initialize a device object for the disk.
    //
    ObReferenceObject(DriverObject);

    status = IoReportDetectedDevice(
                 DriverObject,
                 InterfaceTypeUndefined,
                 -1,
                 -1,
                 NULL,
                 NULL,
                 TRUE,
                 &pdo
             );
    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // Create the XIP root device.
    //

    RtlInitUnicodeString(&realDeviceName,  XIPDISK_DEVICENAME);

    status = IoCreateDevice( DriverObject,
                               sizeof( XIPDISK_EXTENSION ),
                               &realDeviceName,
                               FILE_DEVICE_VIRTUAL_DISK,
                               0,
                               FALSE,
                               &deviceObject );

    if (!NT_SUCCESS(status)) {
        return status;
    }

    // ISSUE-2000/10/14-DavePr -- Hardwiring the driveLetter because I haven't
    // figured out how to get the mountmgr to give out a drive letter. Naming
    // it as a form of floppy (deviceName) was one suggestion that failed (so far).
    // The dosSymlink isn't really necessary, but is another

    //
    // Create symbolic links.  Ignore failures
    //
//  RtlInitUnicodeString(&deviceName,  XIPDISK_FLOPPYNAME);
    RtlInitUnicodeString(&dosSymlink,  XIPDISK_DOSNAME);
    RtlInitUnicodeString(&driveLetter, XIPDISK_DRIVELETTER);

//  (void) IoCreateSymbolicLink(&deviceName,  &realDeviceName);
    (void) IoCreateSymbolicLink(&dosSymlink,  &realDeviceName);
    (void) IoCreateSymbolicLink(&driveLetter, &realDeviceName);

    //
    // Initialize device object and extension.
    //
    deviceObject->Flags |= DO_DIRECT_IO | DO_XIP;
    deviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;

    ext = deviceObject->DeviceExtension;

    bios = &ext->BiosParameters;

    //
    // Initialize the newly allocated disk extension from our temporary
    // Get the bios boot parameters from the kernel
    //
    ext->BootParameters = xipbootparameters;
    status = XIPDispatch(XIPCMD_GETBIOSPARAMETERS, bios, sizeof(*bios));

    //
    // Fill in the device objects
    //
    ext->DeviceObject = deviceObject;
//  ext->DeviceName = deviceName;
    ext->DeviceName = realDeviceName;

    ext->TracksPerCylinder = 1;
    ext->BytesPerCylinder  = bios->BytesPerSector * bios->SectorsPerTrack * ext->TracksPerCylinder;
    ext->NumberOfCylinders = (ULONG)(ext->BootParameters.PageCount * PAGE_SIZE / ext->BytesPerCylinder);

  
    //
    // Attach the root device
    //
    ext->TargetObject = IoAttachDeviceToDeviceStack(deviceObject, pdo);

    if (!ext->TargetObject) {
//      IoDeleteSymbolicLink(&deviceName);
        IoDeleteSymbolicLink(&dosSymlink);
        IoDeleteSymbolicLink(&driveLetter);
        IoDeleteSymbolicLink(&realDeviceName);
        IoDeleteDevice(deviceObject);
        return STATUS_NO_SUCH_DEVICE;
    }

    ext->UnderlyingPDO = pdo;

    status = IoRegisterDeviceInterface(pdo,
                                       (LPGUID)&DiskClassGuid,
                                       NULL,
                                       &ext->InterfaceString );
    if (NT_SUCCESS(status)) {
        status = IoSetDeviceInterfaceState( &ext->InterfaceString, TRUE );
        if (!NT_SUCCESS(status)) {
            DbgPrint("XIP: Warning: ignored failure %x retruned by IoSetDeviceInterface\n", status);
        }
    } else {
        DbgPrint("XIP: Warning: ignored failure %x retruned by IoRegisterDeviceInterface\n", status);
    }

    return STATUS_SUCCESS;
}