//{ **************************************************************************** }
//{ FileName............: DRIVERENTRY.CPP                                        }
//{ Project.............: FLEXCOP                                                }
//{ Author(s)...........: M.Majoor                                               }
//{ Original contents...: Walter Oney ('Programming the Windows Driver Model')   }
//{ Version.............: 1.00                                                   }
//{ ---------------------------------------------------------------------------- }
//{ Main program for FLEXCOP driver.                                             }
//{                                                                              }
//{ Version  Date      Comment                                                   }
//{ 1.00     20050410  - Initial release based on SAA7146A driver V2.02          }
//{ **************************************************************************** }


#include "stddcls.h"
#include "driver.h"
#include "guids.h"
#include "ioctls.h"                                        // DEBUG only
#include "version.h"


//{ **************************************************************************** }
//{ Descript : Forwards.                                                         }
//{ **************************************************************************** }
NTSTATUS AddDevice        (IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo);
VOID     DriverUnload     (IN PDRIVER_OBJECT fdo);
NTSTATUS OnRequestComplete(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN PKEVENT pev);
BOOLEAN  IsWin98();

BOOLEAN win98 = FALSE;
UNICODE_STRING servkey;


//{ **************************************************************************** }
//{ Params   : <DriverObject>  Driver object                                     }
//{            <RegistryPath>  Service registry key                              }
//{ Return   : <Result>        NTSTATUS                                          }
//{                                                                              }
//{ Descript : Entry of driver.                                                  }
//{            DriverEntry is the name conventionally given to the main entry    }
//{            point to a kernel-mode driver. The I/O Manager calls the routine. }
//{            Global initialization takes place here.                           }
//{            We save the registry key name (for which we allocate space) and   }
//{            setup all the function pointers.                                  }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT  DriverObject,
                                IN PUNICODE_STRING RegistryPath)
{
  KdPrint((DRIVERNAME " - Entering DriverEntry: DriverObject %8.8lX\n", DriverObject));
  KdPrint((DRIVERNAME " - Version %d.%2.2d, Build %x\n", VERMAJOR, VERMINOR, BUILD));

  // Insist that OS support at least the WDM level of the DDK we use
  if (!IoIsWdmVersionAvailable(1, 0))
  {
    KdPrint((DRIVERNAME " - Expected version of WDM (%d.%2.2d) not available\n", 1, 0));
    return STATUS_UNSUCCESSFUL;
  }

  // See if we're running under Win98 or NT:
	win98 = IsWin98();

#if DBG
  if (win98)
    KdPrint((DRIVERNAME " - Running under Windows 98\n"));
  else
    KdPrint((DRIVERNAME " - Running under NT\n"));
#endif

  // Save the name of the service key -> since we allocate it in <DriverEntry> we need
  // to release it in the <DriverUnload> routine
  // If you ever need to access the service registry key elsewhere in your driver,
  // it's a good idea to make a copy of the RegistryPath string here. If you're
  // going to be acting as a WMI (Windows Management Instrumentation) provider,
  // you'll need to have this string around, for example.
  servkey.Buffer = (PWSTR) ExAllocatePool(PagedPool, RegistryPath->Length + sizeof(WCHAR));
  if (!servkey.Buffer)
  {
    KdPrint((DRIVERNAME " - Unable to allocate %d bytes for copy of service key name\n", RegistryPath->Length + sizeof(WCHAR)));
    return STATUS_INSUFFICIENT_RESOURCES;
  }
  servkey.MaximumLength = RegistryPath->Length + sizeof(WCHAR);
  RtlCopyUnicodeString(&servkey, RegistryPath);
  servkey.Buffer[RegistryPath->Length / 2] = 0;            // Add a null terminator

  // Initialize function pointers
  DriverObject->DriverUnload = DriverUnload;               // Function called when driver unloads
                                                           // Because we usually do not allocate any resources in <DriverEntry> the <DriverUnload> usually does not much
  DriverObject->DriverExtension->AddDevice = AddDevice;    // Function called for each hardware instance by the PnP manager

  // Since we don't use the standard method of queing I/O request (we have our own handlers) we don't have
  // to set:
  //   DriverObject->DriverStartIO
  
  // Every WDM driver must handle PNP, POWER, and SYSTEM_CONTROL I/O requests;
  // this is where you'd specify your dispatch functions for these requests.
  // What's now IRP_MJ_SYSTEM_CONTROL was called IRP_MJ_WMI in some early beta releases of
  // the Windows 2000 DDK, which is why this dispatch function is called DispatchWmi.
  DriverObject->MajorFunction[IRP_MJ_CREATE]                  = DispatchCreate;          // Function called for <CreateFile>
  DriverObject->MajorFunction[IRP_MJ_CLOSE]                   = DispatchClose;           // Function called for <CloseHandle>
  DriverObject->MajorFunction[IRP_MJ_READ]                    = DispatchReadWrite;       // Fucntion called for <ReadFile>
  DriverObject->MajorFunction[IRP_MJ_WRITE]                   = DispatchReadWrite;       // Function called for <WriteFile>
  DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]          = DispatchControl;         // Function called for <DeviceIOControl>
  DriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = DispatchInternalControl; // Used for inter-driver DeviceIOControl
  DriverObject->MajorFunction[IRP_MJ_CLEANUP]                 = DispatchCleanup;         // Function called before IRP_MJ_CLOSE for cancelling IRP's
  DriverObject->MajorFunction[IRP_MJ_POWER]                   = DispatchPower;           // Required for WDM
  DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]          = DispatchWmi;             // Required for WDM, Windows Management Instrumentation
  DriverObject->MajorFunction[IRP_MJ_PNP]                     = DispatchPnp;             // Required for WDM
	
  KdPrint((DRIVERNAME " - DispatchControl - available device control codes:\n"));
  KdPrint(("Get version             : $%x\n", IOCTL_GET_VERSION));
  KdPrint(("Get status              : $%x\n", IOCTL_GET_STATUS));
  KdPrint(("FLEXCOP read register   : $%x\n", IOCTL_FLEXCOP_READ));
  KdPrint(("FLEXCOP write register  : $%x\n", IOCTL_FLEXCOP_WRITE));
  KdPrint(("Wait notify             : $%x\n", IOCTL_WAIT_NOTIFY));
  KdPrint(("Generate event          : $%x\n", IOCTL_GENERATE_EVENT));
  KdPrint(("Allocate  DMA buffer    : $%x\n", IOCTL_DMA_ALLOCATE));
  KdPrint(("Release   DMA buffer    : $%x\n", IOCTL_DMA_RELEASE));
  KdPrint(("Read from DMA buffer    : $%x\n", IOCTL_DMA_READ));
  KdPrint(("Write to  DMA buffer    : $%x\n", IOCTL_DMA_WRITE));

  KdPrint(("Allocate  FIFO buffer   : $%x\n", IOCTL_FIFO_ALLOCATE));
  KdPrint(("Release   FIFO buffer   : $%x\n", IOCTL_FIFO_RELEASE));
  KdPrint(("Read from FIFO buffer   : $%x\n", IOCTL_FIFO_READ));

  KdPrint(("Write to  IRQ handling  : $%x\n", IOCTL_IRQ_WRITE));
  KdPrint(("Read from IRQ handling  : $%x\n", IOCTL_IRQ_READ));

  KdPrint(("FLEXCOP read register 2 : $%x\n", IOCTL_FLEXCOP_READ2));
  KdPrint(("FLEXCOP write register 2: $%x\n", IOCTL_FLEXCOP_WRITE2));

  // Returning STATUS_SUCCESS is how you indicate success. If you were to discover
  // something wrong, you'd return an error code chosen from the standard set in
  // NTSTATUS.H or from a set of error codes that you define yourself.
  // STATUS_SUCCESS happens to be numerically 0.
  return STATUS_SUCCESS;
}


//{ **************************************************************************** }
//{ Params   : <DriverObject>  Driver object                                     }
//{ Return   : -                                                                 }
//{                                                                              }
//{ Descript : Unloading of driver. Clean up stuff we acquired during            }
//{            <DriverEntry>.                                                    }
//{            The purpose of a WDM driver's DriverUnload function is to clean up}
//{            after any global initialization that DriverEntry might have done. }
//{            There's almost nothing to do. If you made a copy of the           }
//{            RegistryPath string in DriverEntry, though, DriverUnload would be }
//{            the place to release the memory used for the copy.                }
//{            Because we only claimed some resources for the registry key in the}
//{            <DriverEntry> routine we only have to release this one.           }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma PAGEDCODE
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
  PAGED_CODE();                                            // Give a failure if we are not at paged code level (check-build version only)
  KdPrint((DRIVERNAME " - Entering DriverUnload: DriverObject %8.8lX\n", DriverObject));
  RtlFreeUnicodeString(&servkey);                          // Release the allocated space of the registry key we allocated in <DriverEntry>
}


//{ **************************************************************************** }
//{ Params   : <DriverObject>  Driver object                                     }
//{            <pdo>           Physical device object                            }
//{ Return   : <Result>        NTSTATUS                                          }
//{                                                                              }
//{ Descript : Called for each hardware instance detected.                       }
//{            In general a driver might be called upon to manage more than one  }
//{            actual device. In the WDM architecture, a driver has a special    }
//{            AddDevice function that the PnP Manager can call for each such    }
//{            device.                                                           }
//{            We need to create a device object and link it into the stack      }
//{            rooted in the <pdo>.                                              }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
NTSTATUS AddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo)
{
  PAGED_CODE();                                            // Give a failure if we are not at paged code level (check-build version only)
  KdPrint((DRIVERNAME " - Entering AddDevice: DriverObject %8.8lX, pdo %8.8lX\n", DriverObject, pdo));

  NTSTATUS status;

  // Create a functional device object to represent the hardware we're managing.
  PDEVICE_OBJECT fdo;
  #define xsize sizeof(DEVICE_EXTENSION)
  // Create a device object and an instance of our own device extension
  status = IoCreateDevice(DriverObject, xsize, NULL, DEVICETYPE, FILE_DEVICE_SECURE_OPEN, FALSE, &fdo);
  if (!NT_SUCCESS(status))
  {                                                        // If we can't create device object
    KdPrint((DRIVERNAME " - IoCreateDevice failed - %X\n", status));
    return status;
  }

  // The device extension holds the 'user' data of the driver
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

  // From this point forward, any error will have side effects that need to
  // be cleaned up. Using a try-finally block allows us to modify the program
  // easily without losing track of the side effects.
  __try
  {                                                        // Try to finish initialization
    pdx->InterruptCount = 0;                               // Reset interrupt counter
    pdx->DeviceObject = fdo;                               // Save back-pointersS
    pdx->Pdo = pdo;
    IoInitializeRemoveLock(&pdx->RemoveLock, 0, 0, 0);     // Initialize remove lock interfacing
    pdx->state = STOPPED;                                  // Device starts in the stopped state
    ULONG loop;
    for (loop = 0; loop < DMABUFFERS; loop++)              // Initialize our DMA buffer array (just to be sure)
      pdx->dmaBuffers[loop].bufferId = -1;
    for (loop = 0; loop < FIFOBUFFERS; loop++)             // Initialize our FIFO buffer array (just to be sure)
      pdx->fifoBuffers[loop].bufferId = -1;
    for (loop = 0; loop < IRQBUFFERS; loop++)              // Initialize the IRQ buffers
    {
      pdx->irqBuffers[loop].active = FALSE;
      pdx->irqBuffers[loop].eventActive = TRUE;            // By default generate notification event if someone waiting for it
      pdx->irqBuffers[loop].signalActive = FALSE;
      pdx->irqBuffers[loop].fifoActive = FALSE;
    }
    // Initialize the stuff needed for event notification of the application
//    InitializeListHead(&pdx->hlist);
//    KeInitializeSpinLock(&pdx->lockHandles);
//    pdx->pevent = NULL;                                    // No (single) event handler

    // TODO If you need several queues instead of just one, follow the suggestion in
    // driver.h about creating an array of pointers. Your code at this point would
    // then look like this:
    //	InitializeQueue(&pdx->dqRead, StartIoRead);
    //	InitializeQueue(&pdx->dqWrite, StartIoWrite);
    //	pdx->queues[0] = &pdx->dqRead;
    //	pdx->queues[1] = &pdx->dqWrite;

    InitializeQueue(&pdx->dqReadWrite, StartIo);           // Initialize queue interface

    // Declare the buffering method we'll use for read/write requests
    fdo->Flags |= DO_DIRECT_IO;

    // Initialize DPC object
    IoInitializeDpcRequest(fdo, DpcForIsr);                // Register the deferred procedure call for the interrup service routine

    // Link our device object into the stack leading to the PDO
    pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo);
    if (!pdx->LowerDeviceObject)
    {                                                      // If we can't attach device
      KdPrint((DRIVERNAME " - IoAttachDeviceToDeviceStack failed\n"));
      status = STATUS_DEVICE_REMOVED;
      __leave;
    }

    // Set power management flags in the device object
    fdo->Flags |= DO_POWER_PAGABLE;

    // Register a device interface
    status = IoRegisterDeviceInterface(pdo, &GUID_INTERFACE_FLEXCOP, NULL, &pdx->ifname);
    if (!NT_SUCCESS(status))
    {                                                      // If we are unable to register interface
      KdPrint((DRIVERNAME " - IoRegisterDeviceInterface failed - %8.8lX\n", status));
      __leave;
    }

    // Indicate that our initial power state is D0 (fully on). Also indicate that
    // we have a pagable power handler (otherwise, we'll never get idle shutdown
    // messages!)
    pdx->syspower = PowerSystemWorking;
    pdx->devpower = PowerDeviceD0;
    POWER_STATE state;
    state.DeviceState = PowerDeviceD0;
    PoSetPowerState(fdo, DevicePowerState, state);

    // Initialize list head and spin lock used to coordinate asynchronous
    // I/O Control operations
    InitializeListHead(&pdx->PendingIoctlList);
    KeInitializeSpinLock(&pdx->IoctlListLock);

    // Clear the "initializing" flag so that we can get IRPs (required!)
    fdo->Flags &= ~DO_DEVICE_INITIALIZING;

    // Register our willingness to handle WMI requests after we're ready to handle IRPs
    WmiInitialize(fdo);
  }
  __finally
  {
    if (!NT_SUCCESS(status))
    {                                                      // If we need to cleanup
      if (pdx->ifname.Buffer)
        RtlFreeUnicodeString(&pdx->ifname);                // Release memory we claimed
      if (pdx->LowerDeviceObject)
        IoDetachDevice(pdx->LowerDeviceObject);            // Remove attached device from stack
      IoDeleteDevice(fdo);
    }
  }
  return status;                                           // Return status
}


//{ **************************************************************************** }
//{ Params   : <Irp>     I/O request packet                                      }
//{            <status>  Current status                                          }
//{            <info>    Information to set                                      }
//{ Return   : <Result>  NTSTATUS                                                }
//{                                                                              }
//{ Descript : Complete request (method 1 with information).                     }
//{            Mechanically, completing an IRP entails filling in the Status and }
//{            Information members within the IRP's IoStatus block and calling   }
//{            IoCompleteRequest.                                                }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
NTSTATUS CompleteRequest(IN PIRP Irp, IN NTSTATUS status, IN ULONG_PTR info)
{
  Irp->IoStatus.Status      = status;
  Irp->IoStatus.Information = info;
  IoCompleteRequest(Irp, IO_NO_INCREMENT);
  return status;
}


//{ **************************************************************************** }
//{ Params   : <Irp>     I/O request packet                                      }
//{            <status>  Current status                                          }
//{ Return   : <Result>  NTSTATUS                                                }
//{                                                                              }
//{ Descript : Complete request (method 2 without information).                  }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
NTSTATUS CompleteRequest(IN PIRP Irp, IN NTSTATUS status)
{
  Irp->IoStatus.Status = status;
  IoCompleteRequest(Irp, IO_NO_INCREMENT);
  return status;
}


//{ **************************************************************************** }
//{ Params   : <fdo>  Functional device object                                   }
//{            <Irp>  I/O request packet                                         }
//{ Return   : <Result> NTSTATUS                                                 }
//{                                                                              }
//{ Descript : Forward packet and await completion.                              }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma PAGEDCODE
NTSTATUS ForwardAndWait(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
  ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);             // We MUST be running at this level
  PAGED_CODE();                                            // Give a failure if we are not at paged code level (check-build version only)

  // We create a kernel event object as an automatic variable.
  // KeInitializeEvent must be called at PASSIVE_LEVEL. Luckily, PnP requests are
  // always sent at PASSIVE_LEVEL, so this particular requirement is met.
  // The event object itself must occupy nonpaged memory, too.
  // For most purposes, including this one, you can treat the execution stack as
  // being nonpaged.
  KEVENT event;
  KeInitializeEvent(&event, NotificationEvent, FALSE);     // Create a kernel event object

  // We must make a copy of the stack parameters for the next driver because we're
  // going to install a completion routine.
  IoCopyCurrentIrpStackLocationToNext(Irp);
  // We specify a completion routine so that we'll know when something underneath
  // us completes this IRP. We might wait for the completion to occur, so we must
  // be sure that our completion routine is called. That's why we specify TRUE for
  // the three flag arguments to indicate that we want OnRequestComplete called when
  // the IRP completes normally, completes with an error, or is cancelled. The context
  // argument for the completion routine is the address of our event object.
  IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) OnRequestComplete,
                         (PVOID) &event, TRUE, TRUE, TRUE);// The completion routine

  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  // IoCallDriver calls the next lower driver, which can be a lower filter or the
  // physical device object (PDO) driver itself. The PDO driver will perform some
  //  processing and either complete the request immediately or return STATUS_PENDING.
  IoCallDriver(pdx->LowerDeviceObject, Irp);
  // No matter what IoCallDriver returns, we call KeWaitForSingleObject to wait
  // forever on the kernel event we created earlier. Our completion routine will
  // gain control when the IRP completes to signal this event.
  KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
  // Here, we capture the ending status of the IRP and return it to our caller
  return Irp->IoStatus.Status;
}


//{ **************************************************************************** }
//{ Params   : <fdo>  Functional device object                                   }
//{            <Irp>  I/O request packet                                         }
//{            <pev>  Event                                                      }
//{ Return   : <Result> NTSTATUS                                                 }
//{                                                                              }
//{ Descript : Function called on completed request.                             }
//{            It is called when the request was handled normally, completes with}
//{            an error or is cancelled (flags as set by <IoSetCompletionRoutine>}
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
NTSTATUS OnRequestComplete(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN PKEVENT pev)
{
  // We set the event on which ForwardAndWait can currently be blocked
  KeSetEvent(pev, 0, FALSE);
  // By returning STATUS_MORE_PROCESSING_REQUIRED, we halt the unwinding process
  // through the I/O stack. None of the completion routines installed by upper
  // filter drivers will be called at the present time, and the I/O Manager will
  // cease its work on this IRP. The situation is just as if IoCompleteRequest has
  // not been called at all—except, of course, that some lower-level completion
  // routines might have been called. At this instant, the IRP is in limbo, but
  // our ForwardAndWait routine will presently retake ownership.
  return STATUS_MORE_PROCESSING_REQUIRED;
}


//{ **************************************************************************** }
//{ Params   : <pdx>    Physical device extension                                }
//{            <enable> TRUE = enable, FALSE = disable                           }
//{ Return   : -                                                                 }
//{                                                                              }
//{ Descript : Enable/disable all interfaces.                                    }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
VOID EnableAllInterfaces(PDEVICE_EXTENSION pdx, BOOLEAN enable)
{
  IoSetDeviceInterfaceState(&pdx->ifname, enable);
}


//{ **************************************************************************** }
//{ Params   : <pdx>    Physical device extension                                }
//{ Return   : -                                                                 }
//{                                                                              }
//{ Descript : Deregister (disable) all interfaces.                              }
//{            This procedure will disable any device interfaces you registered  }
//{            (probably in AddDevice) and enabled (probably in StartDevice), and}
//{            it will release the memory occupied by their symbolic link names. }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma LOCKEDCODE
VOID DeregisterAllInterfaces(PDEVICE_EXTENSION pdx)
{
  IoSetDeviceInterfaceState(&pdx->ifname, FALSE);
  RtlFreeUnicodeString(&pdx->ifname);                      // Release allocated resource so it can not be found anymore
}


//{ **************************************************************************** }
//{ Params   : <fdo> Functional device object                                    }
//{ Return   : -                                                                 }
//{                                                                              }
//{ Descript : Remove device.                                                    }
//{            Undo all the work we did inside AddDevice.                        }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma PAGEDCODE
VOID RemoveDevice(IN PDEVICE_OBJECT fdo)
{
  PAGED_CODE();                                            // Give a failure if we are not at paged code level (check-build version only)
  PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
  NTSTATUS status;

  WmiTerminate(fdo);                                       // Let the WMI know it too

  // This call to IoDetachDevice balances the call AddDevice made to IoAttachDeviceToDeviceStack
  if (pdx->LowerDeviceObject)                              // If we are (still) on the stack
    IoDetachDevice(pdx->LowerDeviceObject);                // Remove our device from the stack

  // This call to IoDeleteDevice balances the call AddDevice made to IoCreateDevice.
  // Once this function returns, the device object will no longer exist. If the driver
  // isn't managing any other devices, the driver will shortly be unloaded from memory, too.
  IoDeleteDevice(fdo);                                     // Remove the device object
}


//{ **************************************************************************** }
//{ Params   : -                                                                 }
//{ Return   : <Result>  TRUE if W98 running                                     }
//{                                                                              }
//{ Descript : Returns TRUE if Windows 98 is the target.                         }
//{ Notes    :                                                                   }
//{ **************************************************************************** }
#pragma INITCODE
BOOLEAN IsWin98()
{
  #ifdef _X86_
    // Windows 98 (including 2nd ed) supports WDM version 1.0, whereas Win2K
    // supports 1.10.
    return !IoIsWdmVersionAvailable(1, 0x10);
  #else
    return FALSE;
  #endif
}


//{ **************************************************************************** }
//{ Descript : Support stuff.                                                    }
//{ **************************************************************************** }
#if DBG && defined(_X86_)
  #pragma LOCKEDCODE
  extern "C" void __declspec(naked) __cdecl _chkesp()
  {
    _asm je okay
    ASSERT(!DRIVERNAME " - Stack pointer mismatch!");
  okay:
    _asm ret
  }
#endif